diff --git a/.changeset/chatty-parrots-trade.md b/.changeset/chatty-parrots-trade.md new file mode 100644 index 0000000000..149fc3b2e1 --- /dev/null +++ b/.changeset/chatty-parrots-trade.md @@ -0,0 +1,13 @@ +--- +'@backstage/config-loader': minor +--- + +The default environment variable substitution function will now trim whitespace characters from the substituted value. This alleviates bugs where whitespace characters are mistakenly included in environment variables. + +If you depend on the old behavior, you can override the default substitution function with your own, for example: + +```ts +ConfigSources.default({ + substitutionFunc: async name => process.env[name], +}); +``` diff --git a/.changeset/clean-drinks-poke.md b/.changeset/clean-drinks-poke.md new file mode 100644 index 0000000000..9c57c3ac56 --- /dev/null +++ b/.changeset/clean-drinks-poke.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend': minor +'@backstage/plugin-notifications': minor +--- + +Notifications-backend URL query parameter changed from `minimal_severity` to `minimumSeverity`. diff --git a/.changeset/curly-bottles-complain.md b/.changeset/curly-bottles-complain.md new file mode 100644 index 0000000000..c83a3921e2 --- /dev/null +++ b/.changeset/curly-bottles-complain.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Fix auth cookie issuance for split backend deployments by preferring to set it against the request target host instead of origin diff --git a/.changeset/lemon-months-press.md b/.changeset/lemon-months-press.md new file mode 100644 index 0000000000..ecefc64daa --- /dev/null +++ b/.changeset/lemon-months-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +No `undefined` class name used at `MarkdownContent` if no custom class name was provided. diff --git a/.changeset/light-eggs-switch.md b/.changeset/light-eggs-switch.md new file mode 100644 index 0000000000..26934f155e --- /dev/null +++ b/.changeset/light-eggs-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Allow defining custom sidebar item for page and login for the development app diff --git a/.changeset/lovely-flowers-promise.md b/.changeset/lovely-flowers-promise.md new file mode 100644 index 0000000000..5a778d903f --- /dev/null +++ b/.changeset/lovely-flowers-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cloudbuild': patch +--- + +Changed the column that serves as a hyperlink from SOURCE to BUILD. diff --git a/.changeset/mighty-humans-tan.md b/.changeset/mighty-humans-tan.md new file mode 100644 index 0000000000..0f079e70e1 --- /dev/null +++ b/.changeset/mighty-humans-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +--- + +Add examples for `publish:gerrit:review` scaffolder action & improve related tests diff --git a/.changeset/moody-pianos-notice.md b/.changeset/moody-pianos-notice.md new file mode 100644 index 0000000000..beb686fb6b --- /dev/null +++ b/.changeset/moody-pianos-notice.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-notifications-common': patch +--- + +The ordered list of notifications' severities is exported by notifications-common for reusability. diff --git a/.changeset/moody-rice-jump.md b/.changeset/moody-rice-jump.md new file mode 100644 index 0000000000..00e71aa2af --- /dev/null +++ b/.changeset/moody-rice-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Change behavior in EntityAutoCompletePicker to only hide filter if there are no available options. Previously the filter was hidden if there were <= 1 available options. diff --git a/.changeset/neat-weeks-wait.md b/.changeset/neat-weeks-wait.md new file mode 100644 index 0000000000..c16b91fd2f --- /dev/null +++ b/.changeset/neat-weeks-wait.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +--- + +Added support for AWS GovCloud (US) regions diff --git a/.changeset/rotten-cameras-leave.md b/.changeset/rotten-cameras-leave.md new file mode 100644 index 0000000000..bd702eebca --- /dev/null +++ b/.changeset/rotten-cameras-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Added ESLint rule `no-top-level-material-ui-4-imports` to migrate the Material UI imports. diff --git a/.changeset/rude-hats-bow.md b/.changeset/rude-hats-bow.md new file mode 100644 index 0000000000..0bd84617c0 --- /dev/null +++ b/.changeset/rude-hats-bow.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-signals-backend': patch +'@backstage/plugin-notifications': patch +'@backstage/plugin-signals': patch +--- + +Migrate signals and notifications to the new backend in local development diff --git a/.changeset/shaggy-books-mate.md b/.changeset/shaggy-books-mate.md new file mode 100644 index 0000000000..db1ca0bb09 --- /dev/null +++ b/.changeset/shaggy-books-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-backstage-openapi': minor +--- + +The name and title of the returned openapi doc entity are now configurable diff --git a/.changeset/sweet-planets-relate.md b/.changeset/sweet-planets-relate.md new file mode 100644 index 0000000000..7697874957 --- /dev/null +++ b/.changeset/sweet-planets-relate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org-react': patch +--- + +Added the `no-top-level-material-ui-4-imports` ESLint rule to aid with the migration to Material UI v5 diff --git a/.changeset/tame-pianos-hunt.md b/.changeset/tame-pianos-hunt.md new file mode 100644 index 0000000000..74475291ef --- /dev/null +++ b/.changeset/tame-pianos-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added `pullOptions` to `DockerContainerRunner#runContainer` method to pass down options when pulling an image. diff --git a/.changeset/tender-penguins-matter.md b/.changeset/tender-penguins-matter.md new file mode 100644 index 0000000000..5245e251c8 --- /dev/null +++ b/.changeset/tender-penguins-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Add examples for `gitlab:projectDeployToken:create` scaffolder action & improve related tests diff --git a/.changeset/warm-pugs-pretend.md b/.changeset/warm-pugs-pretend.md new file mode 100644 index 0000000000..f7adea6cc0 --- /dev/null +++ b/.changeset/warm-pugs-pretend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-apollo-explorer': minor +--- + +feat(apollo-explorer): allow callbacks using apiholder diff --git a/.changeset/weak-feet-whisper.md b/.changeset/weak-feet-whisper.md new file mode 100644 index 0000000000..1d2e4013eb --- /dev/null +++ b/.changeset/weak-feet-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Fix entity owner resolution in notifications diff --git a/.changeset/wet-buttons-buy.md b/.changeset/wet-buttons-buy.md new file mode 100644 index 0000000000..522be0fc54 --- /dev/null +++ b/.changeset/wet-buttons-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-yeoman': minor +--- + +Add examples for `run:yeoman` scaffolder action. diff --git a/.changeset/wet-plants-help.md b/.changeset/wet-plants-help.md new file mode 100644 index 0000000000..c497c68421 --- /dev/null +++ b/.changeset/wet-plants-help.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +The severity icons now get their colors from the theme. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 043728013f..b525d7e48e 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -475,3 +475,4 @@ Zolotusky zoomable zsh scrollable +severities diff --git a/docs/auth/identity-resolver--old.md b/docs/auth/identity-resolver--old.md new file mode 100644 index 0000000000..267f3e9d3c --- /dev/null +++ b/docs/auth/identity-resolver--old.md @@ -0,0 +1,379 @@ +--- +id: identity-resolver--old +title: Sign-in Identities and Resolvers (Old Backend System) +description: An introduction to Backstage user identities and sign-in resolvers in the old backend system +--- + +:::info +This documentation is written for the old backend which has been replaced by +[the new backend system](../backend-system/index.md), being the default since +Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new +backend system, you may want to read [its own article](./identity-resolver.md) +instead. Also, check out [the migration docs](../backend-system/building-backends/08-migrating.md)! +::: + +By default, every Backstage auth provider is configured only for the use-case of +access delegation. This enables Backstage to request resources and actions from +external systems on behalf of the user, for example re-triggering a build in CI. + +If you want to use an auth provider to sign in users, you need to explicitly configure +it have sign-in enabled and also tell it how the external identities should +be mapped to user identities within Backstage. + +## Quick Start + +> See [providers](../reference/plugin-auth-backend.providers.md) +> for a full list of auth providers and their built-in sign-in resolvers. + +Backstage projects created with `npx @backstage/create-app` come configured with a +sign-in resolver for GitHub guest access. This resolver makes all users share +a single "guest" identity and is only intended as a minimum requirement to quickly +get up and running. You can replace `github` for any of the other providers if you need. + +This resolver should not be used in production, as it uses a single shared identity, +and has no restrictions on who is able to sign-in. Be sure to read through the rest +of this page to understand the Backstage identity system once you need to install +a resolver for your production environment. + +The guest resolver can be useful for testing purposes too, and it looks like this: + +```ts +signIn: { + resolver(_, ctx) { + const userRef = 'user:default/guest' + return ctx.issueToken({ + claims: { + sub: userRef, + ent: [userRef], + }, + }), + }, +}, +``` + +## Backstage User Identity + +A user identity within Backstage is built up from two pieces of information, a +user [entity reference](../features/software-catalog/references.md), and a +set of ownership entity references. +When a user signs in, a Backstage token is generated with these two pieces of information, +which is then used to identify the user within the Backstage ecosystem. + +The user entity reference should uniquely identify the logged in user in Backstage. +It is encouraged that a matching user entity also exists within the Software Catalog, +but it is not required. If the user entity exists in the catalog it can be used to +store additional data about the user. There may even be some plugins that require +this for them to be able to function. + +The ownership references are also entity references, and it is likewise +encouraged that these entities exist within the catalog, but it is not a requirement. +The ownership references are used to determine what the user owns, as a set +of references that the user claims ownership though. For example, a user +Jane (`user:default/jane`) might have the ownership references `user:default/jane`, +`group:default/team-a`, and `group:default/admins`. Given these ownership claims, +any entity that is marked as owned by either of `user:jane`, `team-a`, or `admins` would +be considered owned by Jane. + +The ownership claims often contain the user entity reference itself, but it is not +required. It is also worth noting that the ownership claims can also be used to +resolve other relations similar to ownership, such as a claim for a `maintainer` or +`operator` status. + +The Backstage token that encapsulates the user identity is a JWT. The user entity +reference is stored in the `sub` claim of the payload, while the ownership references +are stored in a custom `ent` claim. Both the user and ownership references should +always be full entity references, as opposed to shorthands like just `jane` or `user:jane`. + +## Sign-in Resolvers + +Signing in a user into Backstage requires a mapping of the user identity from the +third-party auth provider to a Backstage user identity. This mapping can vary quite +a lot between different organizations and auth providers, and because of that there's +no default way to resolve user identities. The auth provider that one wants to use +for sign-in must instead be configured with a sign-in resolver, which is a function +that is responsible for creating this user identity mapping. + +The input to the sign-in resolver function is the result of a successful log in with +the given auth provider, as well as a context object that contains various helpers +for looking up users and issuing tokens. There are also a number of built-in sign-in +resolvers that can be used, which are covered a bit further down. + +Note that while it possible to configure multiple auth providers to be used for sign-in, +you should take care when doing so. It is best to make sure that the different auth +providers either do not have any user overlap, or that any users that are able to log +in with multiple providers always end up with the same Backstage identity. + +### Custom Resolver Example + +Let's look at an example of a custom sign-in resolver for the Google auth provider. +This all typically happens within your `packages/backend/src/plugins/auth.ts` file, +which is responsible for setting up and configuring the auth backend plugin. + +You provide the resolver as part of the options you pass when creating a new auth +provider factory. This means you need to replace the default Google provider with +one that you create. Be sure to also include the existing `defaultAuthProviderFactories` +if you want to keep all of the built-in auth providers installed. + +Now let's look at the example, with the rest of the commentary being made with in +the code comments: + +```ts +// File: packages/backend/src/plugins/auth.ts +import { + createRouter, + providers, + defaultAuthProviderFactories, +} from '@backstage/plugin-auth-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + ...env, + providerFactories: { + ...defaultAuthProviderFactories, + google: providers.google.create({ + signIn: { + resolver: async (info, ctx) => { + const { + profile: { email }, + } = info; + // Profiles are not always guaranteed to to have an email address. + // You can also find more provider-specific information in `info.result`. + // It typically contains a `fullProfile` object as well as ID and/or access + // tokens that you can use for additional lookups. + if (!email) { + throw new Error('User profile contained no email'); + } + + // You can add your own custom validation logic here. + // Logins can be prevented by throwing an error like the one above. + myEmailValidator(email); + + // This example resolver simply uses the local part of the email as the name. + const [name] = email.split('@'); + + // This helper function handles sign-in by looking up a user in the catalog. + // The lookup can be done either by reference, annotations, or custom filters. + // + // The helper also issues a token for the user, using the standard group + // membership logic to determine the ownership references of the user. + return ctx.signInWithCatalogUser({ + entityRef: { name }, + }); + }, + }, + }), + }, + }); +} +``` + +### Built-in Resolvers + +You don't always have to write your own custom resolver. The auth backend plugin provides +built-in resolvers for many of the common sign-in patterns. You access these via the `resolvers` +property of each of the auth provider integrations. For example, the Google provider has +a built in resolver that works just like the one we defined above: + +```ts +// File: packages/backend/src/plugins/auth.ts +export default async function createPlugin( + // ... + return await createRouter({ + // ... + providerFactories: { + // ... + google: providers.google.create({ + signIn: { + resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(), + }, + }); + } + }) +) +``` + +There are also other options, like the this one that looks up a user +by matching the `google.com/email` annotation of user entities in the catalog: + +```ts +providers.google.create({ + signIn: { + resolver: providers.google.resolvers.emailMatchingUserEntityAnnotation(), + }, +}); +``` + +## Custom Ownership Resolution + +If you want to have more control over the membership resolution and token generation +that happens during sign-in you can replace `ctx.signInWithCatalogUser` with a set +of lower-level calls: + +```ts +// File: packages/backend/src/plugins/auth.ts +import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend'; + +export default async function createPlugin( + // ... + return await createRouter({ + // ... + providerFactories: { + // ... + google: async ({ profile: { email } }, ctx) => { + if (!email) { + throw new Error('User profile contained no email'); + } + + // This step calls the catalog to look up a user entity. You could for example + // replace it with a call to a different external system. + const { entity } = await ctx.findCatalogUser({ + annotations: { + 'acme.org/email': email, + }, + }); + + // In this step we extract the ownership references from the user entity using + // the standard logic. It uses a reference to the entity itself, as well as the + // target of each `memberOf` relation where the target is of the kind `Group`. + // + // If you replace the catalog lookup with something that does not return + // an entity you will need to replace this step as well. + // + // You might also replace it if you for example want to filter out certain groups. + // + // Note that `getDefaultOwnershipEntityRefs` only includes groups to which the + // user has a direct MEMBER_OF relationship. It's perfectly fine to include + // groups that the user is transitively part of in the claims array, but the + // catalog doesn't currently provide a direct way of accessing this list of + // groups. + const ownershipRefs = getDefaultOwnershipEntityRefs(entity); + + // The last step is to issue the token, where we might provide more options in the future. + return ctx.issueToken({ + claims: { + sub: stringifyEntityRef(entity), + ent: ownershipRefs, + }, + }); + }; + } + }) +) +``` + +## Sign-In without Users in the Catalog + +While populating the catalog with organizational data unlocks more powerful ways +to browse your software ecosystem, it might not always be a viable or prioritized +option. However, even if you do not have user entities populated in your catalog, you +can still sign in users. As there are currently no built-in sign-in resolvers for +this scenario you will need to implement your own. + +Signing in a user that doesn't exist in the catalog is as simple as skipping the +catalog lookup step from the above example. Rather than looking up the user, we +instead immediately issue a token using whatever information is available. One caveat +is that it can be tricky to determine the ownership references, although it can +be achieved for example through a lookup to an external service. You typically +want to at least use the user itself as a lone ownership reference. + +Because we no longer use the catalog as an allow-list of users, it is often important +that you limit what users are allowed to sign in. This could be a simple email domain +check like in the example below, or you might for example look up the GitHub organizations +that the user belongs to using the user access token in the provided result object. + +```ts +// File: packages/backend/src/plugins/auth.ts +import { createRouter, providers } from '@backstage/plugin-auth-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; +import { + stringifyEntityRef, + DEFAULT_NAMESPACE, +} from '@backstage/catalog-model'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + ...env, + providerFactories: { + google: providers.google.create({ + signIn: { + resolver: async ({ profile }, ctx) => { + if (!profile.email) { + throw new Error( + 'Login failed, user profile does not contain an email', + ); + } + // Split the email into the local part and the domain. + const [localPart, domain] = profile.email.split('@'); + + // Next we verify the email domain. It is recommended to include this + // kind of check if you don't look up the user in an external service. + if (domain !== 'acme.org') { + throw new Error( + `Login failed, this email ${profile.email} does not belong to the expected domain`, + ); + } + + // By using `stringifyEntityRef` we ensure that the reference is formatted correctly + const userEntity = stringifyEntityRef({ + kind: 'User', + name: localPart, + namespace: DEFAULT_NAMESPACE, + }); + return ctx.issueToken({ + claims: { + sub: userEntity, + ent: [userEntity], + }, + }); + }, + }, + }), + }, + }); +} +``` + +## AuthHandler + +Similar to a custom sign-in resolver, you can also write a custom auth handler +function which is used to verify and convert the auth response into the profile +that will be presented to the user. This is where you can customize things like +display name and profile picture. + +This is also the place where you can do authorization and validation of the user +and throw errors if the user should not be allowed access in Backstage. + +```ts +// File: packages/backend/src/plugins/auth.ts +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + ... + providerFactories: { + google: providers.google.create({ + authHandler: async ({ + fullProfile // Type: passport.Profile, + idToken // Type: (Optional) string, + }) => { + // Custom validation code goes here + return { + profile: { + email, + picture, + displayName, + } + }; + } + }) + } + }) +} +``` diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 0a626ef175..39fc13fcb2 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -4,52 +4,44 @@ title: Sign-in Identities and Resolvers description: An introduction to Backstage user identities and sign-in resolvers --- +:::info +This documentation is written for [the new backend +system](../backend-system/index.md) which is the default since Backstage +[version 1.24](../releases/v1.24.0.md). If you are still on the old backend +system, you may want to read [its own article](./identity-resolver--old.md) +instead. +::: + By default, every Backstage auth provider is configured only for the use-case of access delegation. This enables Backstage to request resources and actions from external systems on behalf of the user, for example re-triggering a build in CI. -If you want to use an auth provider to sign in users, you need to explicitly configure -it have sign-in enabled and also tell it how the external identities should -be mapped to user identities within Backstage. +If you want to use an auth provider to sign in users, you need to explicitly +configure it have sign-in enabled and also tell it how the external identities +should be mapped to user identities within Backstage. You do this by either +choosing a built-in sign in resolver, or supplying your own. Both methods are +listed below. ## Quick Start -> See [providers](../reference/plugin-auth-backend.providers.md) -> for a full list of auth providers and their built-in sign-in resolvers. +Backstage projects created with `npx @backstage/create-app` come configured with +a [guest auth provider](https://backstage.io/docs/auth/guest/provider). This +provider makes all users share a single "guest" identity. This is useful for +testing purposes and quickly getting started locally, but is not safe for use in +production and that particular provider will refuse to work there. -Backstage projects created with `npx @backstage/create-app` come configured with a -sign-in resolver for GitHub guest access. This resolver makes all users share -a single "guest" identity and is only intended as a minimum requirement to quickly -get up and running. You can replace `github` for any of the other providers if you need. - -This resolver should not be used in production, as it uses a single shared identity, -and has no restrictions on who is able to sign-in. Be sure to read through the rest -of this page to understand the Backstage identity system once you need to install -a resolver for your production environment. - -The guest resolver can be useful for testing purposes too, and it looks like this: - -```ts -signIn: { - resolver(_, ctx) { - const userRef = 'user:default/guest' - return ctx.issueToken({ - claims: { - sub: userRef, - ent: [userRef], - }, - }), - }, -}, -``` +Because of this, one of the early things you want to do when standing up your +Backstage instance is to choose a production ready auth provider. See [the auth +overview page](./index.md) for a full list of providers and how to install and +configure them. ## Backstage User Identity -A user identity within Backstage is built up from two pieces of information, a -user [entity reference](../features/software-catalog/references.md), and a -set of ownership entity references. -When a user signs in, a Backstage token is generated with these two pieces of information, -which is then used to identify the user within the Backstage ecosystem. +A user identity within Backstage is built up from two main pieces of +information: a user [entity +reference](../features/software-catalog/references.md), and a set of ownership +references. When a user signs in, a Backstage token is generated which is then +used to identify the user within the Backstage ecosystem. The user entity reference should uniquely identify the logged in user in Backstage. It is encouraged that a matching user entity also exists within the Software Catalog, @@ -71,10 +63,13 @@ required. It is also worth noting that the ownership claims can also be used to resolve other relations similar to ownership, such as a claim for a `maintainer` or `operator` status. -The Backstage token that encapsulates the user identity is a JWT. The user entity -reference is stored in the `sub` claim of the payload, while the ownership references -are stored in a custom `ent` claim. Both the user and ownership references should -always be full entity references, as opposed to shorthands like just `jane` or `user:jane`. +The Backstage token that encapsulates the user identity is a JWT. The user +entity reference is stored in the `sub` claim of the payload, while the +ownership references are stored in a custom `ent` claim in the old backend +system but instead is made available through a user info API endpoint on the +auth backend in the new system. Both the user and ownership references should +always be full entity references, as opposed to shorthands like just `jane` or +`user:jane`. ## Sign-in Resolvers @@ -90,116 +85,170 @@ the given auth provider, as well as a context object that contains various helpe for looking up users and issuing tokens. There are also a number of built-in sign-in resolvers that can be used, which are covered a bit further down. -Note that while it possible to configure multiple auth providers to be used for sign-in, -you should take care when doing so. It is best to make sure that the different auth -providers either do not have any user overlap, or that any users that are able to log -in with multiple providers always end up with the same Backstage identity. +Note that while it possible to configure multiple auth providers to be used for +sign-in, you should take care when doing so. It is best to make sure that the +different auth providers either do not have any user overlap, or that any users +that are able to log in with multiple providers always end up with the same +Backstage identity. For most organizations, it makes the most sense to provide +only one sign-in method. -### Custom Resolver Example +### Using Builtin Resolvers -Let's look at an example of a custom sign-in resolver for the Google auth provider. -This all typically happens within your `packages/backend/src/plugins/auth.ts` file, -which is responsible for setting up and configuring the auth backend plugin. +Most auth providers come with a set of builtin sign in providers that you can +choose from. They target the most common use cases, and if they fit your needs, +you can pick one or more of them without having to write any code at all. You +still have to make a choice - as mentioned above, even if there are a set of +builtins, none of them are selected by default. -You provide the resolver as part of the options you pass when creating a new auth -provider factory. This means you need to replace the default Google provider with -one that you create. Be sure to also include the existing `defaultAuthProviderFactories` -if you want to keep all of the built-in auth providers installed. +You set up builtin sign in resolvers using [your app-config](../conf/index.md), +next to the respective provider's configuration. Here's an example for GitHub: -Now let's look at the example, with the rest of the commentary being made with in -the code comments: +```yaml title="in e.g. app-config.yaml" +auth: + environment: development + providers: + github: + development: + clientId: ${AUTH_GITHUB_CLIENT_ID} + clientSecret: ${AUTH_GITHUB_CLIENT_SECRET} + enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL} + signIn: + resolvers: + - resolver: usernameMatchingUserEntityName + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName +``` + +Note that in this instance it lists several resolvers, which means that the +framework will try them one by one until one succeeds. If none of them do, the +sign in attempt is rejected. + +The list of available resolvers is different for each provider, since they often +depend on the information model returned from the upstream provider service. +Consult the documentation of the respective provider to find the list. + +### Building Custom Resolvers + +If the builtins don't work for you, you can also provide a completely custom +sign-in resolver, through code. If you follow the installation instructions of +[one of the available providers](./index.md), you will likely have added a +dependency to your backend along with a line of code and some configuration. + +Using GitHub as an example, this is the relevant parts of the backend code: + +```ts title="in packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); +``` + +When you want to supply a custom sign-in resolver, as a general pattern you +remove that last import and instead construct your own provider using the +facilities from the same package. You can leave the config unchanged from +before. + +```ts title="in packages/backend/src/index.ts" +/* highlight-add-start */ +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { githubAuthenticator } from '@backstage/plugin-auth-backend-module-github-provider'; +import { + authProvidersExtensionPoint, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; + +const customAuth = createBackendModule({ + // This ID must be exactly "auth" because that's the plugin it targets + pluginId: 'auth', + // This ID must be unique, but can be anything + moduleId: 'custom-auth-provider', + register(reg) { + reg.registerInit({ + deps: { providers: authProvidersExtensionPoint }, + async init({ providers }) { + providers.registerProvider({ + // This ID must match the actual provider config, e.g. addressing + // auth.providers.github means that this must be "github". + providerId: 'github', + // Use createProxyAuthProviderFactory instead if it's one of the proxy + // based providers rather than an OAuth based one + factory: createOAuthProviderFactory({ + authenticator: githubAuthenticator, + async signInResolver(info, ctx) { + /********************************************************************* + * Custom resolver code goes here, see farther down in this article! * + * "info" is the sign in result from the upstream (github here), and * + * "ctx" contains useful utilities for token issuance etc. * + *********************************************************************/ + }, + }), + }); + }, + }); + }, +}); +/* highlight-add-end */ + +backend.add(import('@backstage/plugin-auth-backend')); +/* highlight-remove-next-line */ +backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); +/* highlight-add-next-line */ +backend.add(customAuth); +``` + +Check out [the naming patterns +article](../backend-system/architecture/07-naming-patterns.md) for what rules +apply regarding how to form valid IDs. In this example we also put the module +declaration directly in `packages/backend/src/index.ts` but that's just for +simplicity. You can place it anywhere you like, including in other packages, and +import from there if you prefer. + +The `createOAuthProviderFactory` / `createProxyAuthProviderFactory` functions +have additional options for profile and state transforms - not covered here, but +good to know about if you need them. + +So what would the body of a typical sign in resolver callback look like? Here's +an example: ```ts -// File: packages/backend/src/plugins/auth.ts -import { - createRouter, - providers, - defaultAuthProviderFactories, -} from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; +// ... +async signInResolver(info, ctx) { + const { profile: { email } } = info; -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - ...defaultAuthProviderFactories, - google: providers.google.create({ - signIn: { - resolver: async (info, ctx) => { - const { - profile: { email }, - } = info; - // Profiles are not always guaranteed to to have an email address. - // You can also find more provider-specific information in `info.result`. - // It typically contains a `fullProfile` object as well as ID and/or access - // tokens that you can use for additional lookups. - if (!email) { - throw new Error('User profile contained no email'); - } + // Profiles are not always guaranteed to to have an email address. + // You can also find more provider-specific information in `info.result`. + // It typically contains a `fullProfile` object as well as ID and/or access + // tokens that you can use for additional lookups. + if (!email) { + throw new Error('User profile contained no email'); + } - // You can add your own custom validation logic here. - // Logins can be prevented by throwing an error like the one above. - myEmailValidator(email); + // You can add your own custom validation logic here. + // Logins can be prevented by throwing an error like the one above. + myEmailValidator(email); - // This example resolver simply uses the local part of the email as the name. - const [name] = email.split('@'); + // This example resolver simply uses the local part of the email as the name. + const [name] = email.split('@'); - // This helper function handles sign-in by looking up a user in the catalog. - // The lookup can be done either by reference, annotations, or custom filters. - // - // The helper also issues a token for the user, using the standard group - // membership logic to determine the ownership references of the user. - return ctx.signInWithCatalogUser({ - entityRef: { name }, - }); - }, - }, - }), - }, + // This helper function handles sign-in by looking up a user in the catalog. + // The lookup can be done either by reference, annotations, or custom filters. + // + // The helper also issues a token for the user, using the standard group + // membership logic to determine the ownership references of the user. + // + // There are a number of other methods on the ctx, feel free to explore them! + return ctx.signInWithCatalogUser({ + entityRef: { name }, }); } ``` -### Built-in Resolvers +If you throw an error in the sign in resolver function, the sign in attempt is +immediately rejected, and the error details are presented in the user interface. -You don't always have to write your own custom resolver. The auth backend plugin provides -built-in resolvers for many of the common sign-in patterns. You access these via the `resolvers` -property of each of the auth provider integrations. For example, the Google provider has -a built in resolver that works just like the one we defined above: +The `ctx` context [has several useful +functions](https://backstage.io/docs/reference/plugin-auth-node.authresolvercontext/) +for issuing tokens in various ways. -```ts -// File: packages/backend/src/plugins/auth.ts -export default async function createPlugin( - // ... - return await createRouter({ - // ... - providerFactories: { - // ... - google: providers.google.create({ - signIn: { - resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(), - }, - }); - } - }) -) -``` - -There are also other options, like the this one that looks up a user -by matching the `google.com/email` annotation of user entities in the catalog: - -```ts -providers.google.create({ - signIn: { - resolver: providers.google.resolvers.emailMatchingUserEntityAnnotation(), - }, -}); -``` - -## Custom Ownership Resolution +### Custom Ownership Resolution If you want to have more control over the membership resolution and token generation that happens during sign-in you can replace `ctx.signInWithCatalogUser` with a set @@ -209,55 +258,48 @@ of lower-level calls: // File: packages/backend/src/plugins/auth.ts import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend'; -export default async function createPlugin( - // ... - return await createRouter({ - // ... - providerFactories: { - // ... - google: async ({ profile: { email } }, ctx) => { - if (!email) { - throw new Error('User profile contained no email'); - } +// ... +async signInResolver({ profile: { email} }, ctx) { + if (!email) { + throw new Error('User profile contained no email'); + } - // This step calls the catalog to look up a user entity. You could for example - // replace it with a call to a different external system. - const { entity } = await ctx.findCatalogUser({ - annotations: { - 'acme.org/email': email, - }, - }); + // This step calls the catalog to look up a user entity. You could for example + // replace it with a call to a different external system. + const { entity } = await ctx.findCatalogUser({ + annotations: { + 'acme.org/email': email, + }, + }); - // In this step we extract the ownership references from the user entity using - // the standard logic. It uses a reference to the entity itself, as well as the - // target of each `memberOf` relation where the target is of the kind `Group`. - // - // If you replace the catalog lookup with something that does not return - // an entity you will need to replace this step as well. - // - // You might also replace it if you for example want to filter out certain groups. - // - // Note that `getDefaultOwnershipEntityRefs` only includes groups to which the - // user has a direct MEMBER_OF relationship. It's perfectly fine to include - // groups that the user is transitively part of in the claims array, but the - // catalog doesn't currently provide a direct way of accessing this list of - // groups. - const ownershipRefs = getDefaultOwnershipEntityRefs(entity); + // In this step we extract the ownership references from the user entity using + // the standard logic. It uses a reference to the entity itself, as well as the + // target of each `memberOf` relation where the target is of the kind `Group`. + // + // If you replace the catalog lookup with something that does not return + // an entity you will need to replace this step as well. + // + // You might also replace it if you for example want to filter out certain groups. + // + // Note that `getDefaultOwnershipEntityRefs` only includes groups to which the + // user has a direct MEMBER_OF relationship. It's perfectly fine to include + // groups that the user is transitively part of in the claims array, but the + // catalog doesn't currently provide a direct way of accessing this list of + // groups. + const ownershipRefs = getDefaultOwnershipEntityRefs(entity); - // The last step is to issue the token, where we might provide more options in the future. - return ctx.issueToken({ - claims: { - sub: stringifyEntityRef(entity), - ent: ownershipRefs, - }, - }); - }; - } - }) -) + // The last step is to issue the token, where we might provide more options in the + // future. + return ctx.issueToken({ + claims: { + sub: stringifyEntityRef(entity), + ent: ownershipRefs, + }, + }); +} ``` -## Sign-In without Users in the Catalog +### Sign-In without Users in the Catalog While populating the catalog with organizational data unlocks more powerful ways to browse your software ecosystem, it might not always be a viable or prioritized @@ -278,63 +320,44 @@ check like in the example below, or you might for example look up the GitHub org that the user belongs to using the user access token in the provided result object. ```ts -// File: packages/backend/src/plugins/auth.ts -import { createRouter, providers } from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; -import { - stringifyEntityRef, - DEFAULT_NAMESPACE, -} from '@backstage/catalog-model'; +import { stringifyEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - google: providers.google.create({ - signIn: { - resolver: async ({ profile }, ctx) => { - if (!profile.email) { - throw new Error( - 'Login failed, user profile does not contain an email', - ); - } - // Split the email into the local part and the domain. - const [localPart, domain] = profile.email.split('@'); +// ... +async signInResolver({ profile }, ctx) { + if (!profile.email) { + throw new Error( + 'Login failed, user profile does not contain an email', + ); + } + // Split the email into the local part and the domain. + const [localPart, domain] = profile.email.split('@'); - // Next we verify the email domain. It is recommended to include this - // kind of check if you don't look up the user in an external service. - if (domain !== 'acme.org') { - throw new Error( - `Login failed, this email ${profile.email} does not belong to the expected domain`, - ); - } + // Next we verify the email domain. It is recommended to include this + // kind of check if you don't look up the user in an external service. + if (domain !== 'acme.org') { + throw new Error( + `Login failed, '${profile.email}' does not belong to the expected domain`, + ); + } - // By using `stringifyEntityRef` we ensure that the reference is formatted correctly - const userEntity = stringifyEntityRef({ - kind: 'User', - name: localPart, - namespace: DEFAULT_NAMESPACE, - }); - return ctx.issueToken({ - claims: { - sub: userEntity, - ent: [userEntity], - }, - }); - }, - }, - }), + // By using `stringifyEntityRef` we ensure that the reference is formatted correctly + const userEntity = stringifyEntityRef({ + kind: 'User', + name: localPart, + namespace: DEFAULT_NAMESPACE, + }); + return ctx.issueToken({ + claims: { + sub: userEntity, + ent: [userEntity], }, }); } ``` -## AuthHandler +## Profile Transforms -Similar to a custom sign-in resolver, you can also write a custom auth handler +Similar to a custom sign-in resolver, you can also write a custom profile transform function which is used to verify and convert the auth response into the profile that will be presented to the user. This is where you can customize things like display name and profile picture. @@ -343,29 +366,43 @@ This is also the place where you can do authorization and validation of the user and throw errors if the user should not be allowed access in Backstage. ```ts -// File: packages/backend/src/plugins/auth.ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ... - providerFactories: { - google: providers.google.create({ - authHandler: async ({ - fullProfile // Type: passport.Profile, - idToken // Type: (Optional) string, - }) => { - // Custom validation code goes here - return { - profile: { - email, - picture, - displayName, - } - }; - } - }) - } - }) -} +const customAuth = createBackendModule({ + // This ID must be exactly "auth" because that's the plugin it targets + pluginId: 'auth', + // This ID must be unique, but can be anything + moduleId: 'custom-auth-provider', + register(reg) { + reg.registerInit({ + deps: { providers: authProvidersExtensionPoint }, + async init({ providers }) { + providers.registerProvider({ + // This ID must match the actual provider config, e.g. addressing + // auth.providers.github means that this must be "github". + providerId: 'github', + // Use createProxyAuthProviderFactory instead if it's one of the proxy + // based providers rather than an OAuth based one + factory: createOAuthProviderFactory({ + authenticator: githubAuthenticator, + async profileTransform(result, ctx) { + /********************************************************************** + * Custom transform code goes here! * + * "info" is the sign in result from the upstream (github here), and * + * "ctx" contains useful utilities. * + **********************************************************************/ + return { + profile: { + email, + picture, + displayName, + }, + }; + }, + }), + }); + }, + }); + }, +}); ``` + +Remember to `backend.add` the created module just like above. diff --git a/microsite/data/plugins/feedback.yaml b/microsite/data/plugins/feedback.yaml new file mode 100644 index 0000000000..8e8e00165e --- /dev/null +++ b/microsite/data/plugins/feedback.yaml @@ -0,0 +1,10 @@ +--- +title: Feedback +author: Red Hat +authorUrl: https://redhat.com +category: Quality +description: A plugin for collecting user feedbacks for your application. # Max 170 characters +documentation: https://github.com/janus-idp/backstage-plugins/tree/main/plugins/feedback#readme +iconUrl: /img/plugin-feedback-logo.svg +npmPackageName: '@janus-idp/backstage-plugin-feedback' +addedDate: '2024-04-09' diff --git a/microsite/data/plugins/library-check.yaml b/microsite/data/plugins/library-check.yaml new file mode 100644 index 0000000000..68489d676f --- /dev/null +++ b/microsite/data/plugins/library-check.yaml @@ -0,0 +1,10 @@ +--- +title: Library Check +author: anakzr +authorUrl: https://github.com/anakzr +category: Services +description: Check the status of project package or library based on main registries +documentation: https://github.com/anakzr/backstage-plugin-library-check +iconUrl: /img/library-check-logo.png +npmPackageName: '@anakz/backstage-plugin-library-check' +addedDate: '2024-03-11' diff --git a/microsite/docusaurus.config.js b/microsite/docusaurus.config.js index 99be57788c..bea4b50e05 100644 --- a/microsite/docusaurus.config.js +++ b/microsite/docusaurus.config.js @@ -95,6 +95,7 @@ module.exports = { plugins: [ 'docusaurus-plugin-sass', () => ({ + name: 'yaml-loader', configureWebpack() { return { module: { diff --git a/microsite/static/img/library-check-logo.png b/microsite/static/img/library-check-logo.png new file mode 100644 index 0000000000..299b1a01e8 Binary files /dev/null and b/microsite/static/img/library-check-logo.png differ diff --git a/microsite/static/img/plugin-feedback-logo.svg b/microsite/static/img/plugin-feedback-logo.svg new file mode 100644 index 0000000000..bd8014f9c7 --- /dev/null +++ b/microsite/static/img/plugin-feedback-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 6e5aba85c8..a8e4699105 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -509,7 +509,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.22.7, @babel/parser@npm:^7.23.9": +"@babel/parser@npm:^7.23.9": version: 7.23.9 resolution: "@babel/parser@npm:7.23.9" bin: @@ -1689,9 +1689,9 @@ __metadata: languageName: node linkType: hard -"@docusaurus/core@npm:3.1.1, @docusaurus/core@npm:^3.1.1": - version: 3.1.1 - resolution: "@docusaurus/core@npm:3.1.1" +"@docusaurus/core@npm:3.2.1, @docusaurus/core@npm:^3.1.1": + version: 3.2.1 + resolution: "@docusaurus/core@npm:3.2.1" dependencies: "@babel/core": ^7.23.3 "@babel/generator": ^7.23.3 @@ -1703,14 +1703,13 @@ __metadata: "@babel/runtime": ^7.22.6 "@babel/runtime-corejs3": ^7.22.6 "@babel/traverse": ^7.22.8 - "@docusaurus/cssnano-preset": 3.1.1 - "@docusaurus/logger": 3.1.1 - "@docusaurus/mdx-loader": 3.1.1 + "@docusaurus/cssnano-preset": 3.2.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/mdx-loader": 3.2.1 "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-common": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 - "@slorber/static-site-generator-webpack-plugin": ^4.0.7 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 "@svgr/webpack": ^6.5.1 autoprefixer: ^10.4.14 babel-loader: ^9.1.3 @@ -1731,6 +1730,7 @@ __metadata: detect-port: ^1.5.1 escape-html: ^1.0.3 eta: ^2.2.0 + eval: ^0.1.8 file-loader: ^6.2.0 fs-extra: ^11.1.1 html-minifier-terser: ^7.2.0 @@ -1739,6 +1739,7 @@ __metadata: leven: ^3.1.0 lodash: ^4.17.21 mini-css-extract-plugin: ^2.7.6 + p-map: ^4.0.0 postcss: ^8.4.26 postcss-loader: ^7.3.3 prompts: ^2.4.2 @@ -1767,41 +1768,39 @@ __metadata: react-dom: ^18.0.0 bin: docusaurus: bin/docusaurus.mjs - checksum: a35851dd6dc5ff5d2c8a8efb36b9875dc33a9e067a4677e5c9437a8635970e55a3cf14bb6eba9c9e356f4189283fb401b39423d497eb17bb09a16dd0ed84b1da + checksum: 9267f08b41240cb9d399abbd8a41ff66e0082551284325db3f17fcce9643bef81d06564797a7cc4c528fe8bde2858c20666e74a0308f3ecc80f3be1dbee14bb5 languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/cssnano-preset@npm:3.1.1" +"@docusaurus/cssnano-preset@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/cssnano-preset@npm:3.2.1" dependencies: cssnano-preset-advanced: ^5.3.10 postcss: ^8.4.26 postcss-sort-media-queries: ^4.4.1 tslib: ^2.6.0 - checksum: 562d96c2ff60826459c255831cd57b12393e6f41b3827c499d43d00ec1887fbeebfea7c68aa72d9e56a5d64419847d11a5d66021acb4f1e3ce4c87b781f33954 + checksum: ee23a1229d23732d936fe1d68732d1305abf0132b43a398336fee500504a3e7566d3b0c6222f89f565e24e68e00e353765e0cbbab5611a3b35ecf88305558b6d languageName: node linkType: hard -"@docusaurus/logger@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/logger@npm:3.1.1" +"@docusaurus/logger@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/logger@npm:3.2.1" dependencies: chalk: ^4.1.2 tslib: ^2.6.0 - checksum: d9673035a7eff14c1820b6c07a07e92a21e7f8e824305a784250d025c79bf9cfc51d0e6007b7660da4f810b78793572d382db48033201902c335c916d735433b + checksum: 9d5db5253eda98871563faddb5318bcb6b17ddf5882ababad4803d526917844819751e84ee8028e794fd5507646db6409f9041fd7f41b7f7971015df11cc6376 languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/mdx-loader@npm:3.1.1" +"@docusaurus/mdx-loader@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/mdx-loader@npm:3.2.1" dependencies: - "@babel/parser": ^7.22.7 - "@babel/traverse": ^7.22.8 - "@docusaurus/logger": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 "@mdx-js/mdx": ^3.0.0 "@slorber/remark-comment": ^1.0.0 escape-html: ^1.0.3 @@ -1826,16 +1825,16 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: e6b2483b20db41c8264c47edc42e2a738239fbad9b69eb520c7dab421dc2cde4f73f6ec871ccf277dfbc285ae9b202af892771ce800c4ce56739bff6c4c3c231 + checksum: 4609faf2d8b76085a3aa86ac5ca4ac3b3420e3cfd796f1b39c46f368c82b3db0db5b1308646cf35fdad0a1f6f088d367116eb0e2a8c3fa728ed886ee37516476 languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:3.1.1, @docusaurus/module-type-aliases@npm:^3.1.1": - version: 3.1.1 - resolution: "@docusaurus/module-type-aliases@npm:3.1.1" +"@docusaurus/module-type-aliases@npm:3.2.1, @docusaurus/module-type-aliases@npm:^3.1.1": + version: 3.2.1 + resolution: "@docusaurus/module-type-aliases@npm:3.2.1" dependencies: "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/types": 3.1.1 + "@docusaurus/types": 3.2.1 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -1845,19 +1844,19 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 937e52b57af5e459df26621dba57a1e399c48ebb1bc0fe10450c0238a79ef76d26a69ba9a42317e236d50bbd6c3e0fbb10fe97f8153373331947b90125aeee38 + checksum: 37b4a40f9afebbe76e350c10c857737b544c141a988462436904ae16993a52e4429018d406e2f55ad57a533e5a108dd7cdb903434abb84721deeec0d5f195d80 languageName: node linkType: hard "@docusaurus/plugin-client-redirects@npm:^3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-client-redirects@npm:3.1.1" + version: 3.2.1 + resolution: "@docusaurus/plugin-client-redirects@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/logger": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-common": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 eta: ^2.2.0 fs-extra: ^11.1.1 lodash: ^4.17.21 @@ -1865,21 +1864,21 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 48a101b88e0f537323cb54c0de6d0a711f7987358699929f0bba17ae60038205a154b2ce179e1a1e065d2732186d23b53f4a1922ca8d875f6aea364abf77bdd7 + checksum: 84f031a9f660028cf745ad4f3b8ea8820e34fdabed75321b06a8145078b3b8c496369c902dc3f46a2bb973cfaaa433474a9891bd836494ff72c65258fa67b64c languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-content-blog@npm:3.1.1" +"@docusaurus/plugin-content-blog@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-content-blog@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/logger": 3.1.1 - "@docusaurus/mdx-loader": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-common": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/mdx-loader": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 cheerio: ^1.0.0-rc.12 feed: ^4.2.2 fs-extra: ^11.1.1 @@ -1893,21 +1892,22 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 8680e47cc1250cd0c0987c7471e8679860d867de5bf3917e0088185cfa4f5abdb7a847f652cea7c61afbce9dad8bb3cd9f8924b13b064087eab4f9654a9de05d + checksum: d95147a28aad832cd2dc39af634e1902a8a36f958dd2ff5fa6eaa47b574b58df42609a64da823951826f647337ad35c1f1c8be8a0a085913e192936f38839413 languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-content-docs@npm:3.1.1" +"@docusaurus/plugin-content-docs@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-content-docs@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/logger": 3.1.1 - "@docusaurus/mdx-loader": 3.1.1 - "@docusaurus/module-type-aliases": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/mdx-loader": 3.2.1 + "@docusaurus/module-type-aliases": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 "@types/react-router-config": ^5.0.7 combine-promises: ^1.1.0 fs-extra: ^11.1.1 @@ -1919,133 +1919,133 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 1d96477ca575e01e3e2de080075f07a7f35da7a839632d2ad2d06bf3b651fbd91dd4e70861bb16e1ccb8b2a99f0602461f4c181c04557e0a98ece1b5e5ba61de + checksum: c182466c3ff513b36a8975a3899b07ffc4b227ab45ef69eacc0a77119d6f0cd6a0727a3e886cfcf4a56e4f522f64e1e6a2647ddc57eb8493b93c03240b1d9b39 languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-content-pages@npm:3.1.1" +"@docusaurus/plugin-content-pages@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-content-pages@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/mdx-loader": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/mdx-loader": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 fs-extra: ^11.1.1 tslib: ^2.6.0 webpack: ^5.88.1 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: ccb9c58802b56c79f2b9bc4445dad316e50438fea995c2a6543637c472c4c2ccfbe3984abb462a1359bce3e3dfad84fecf6e4faab8e242dac4182146043ea005 + checksum: 3cce99f8aa863b97cbb54a50b448073222a0678528b09f5bec2196e73ec4740f412f8675ed05d283ff672756a5d3005f7a1e4d8c8f882cd0d6d5691cbccb604c languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-debug@npm:3.1.1" +"@docusaurus/plugin-debug@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-debug@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils": 3.2.1 fs-extra: ^11.1.1 react-json-view-lite: ^1.2.0 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 843780bf7eb57d41d198832ad9a7a53e45302c2a10f0221a77c4340e3b1c1dd4e92f77f2ecae3fdb8eef0aed1b7314f2ce1c8323fe25eec759bcde10e0e50149 + checksum: b3fb1c8935463afb97f233042692c247d4147c03e18ef9fb37fbf0c46d4adaefa4af0d5c357025992dadfe7b83a9fd3754946b8947bfb8b9535dca390a3668d0 languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-google-analytics@npm:3.1.1" +"@docusaurus/plugin-google-analytics@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-google-analytics@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 4efcaf9f95353485965e299a971e761b3dadac1370d6d8a6582d653535ce76e36b6788283aa1a6dfd7f55ac64fca3d7e97239f94a5163e4cf192a5c2854b1d61 + checksum: e1e881fd6adbe408029257d526759b9217f7d70e5e068c7e9241a5f0c3050b0fa46acfeb4f8a23c3f36e1739d0a3d810642d69c6648ff6801ce13b646e44e6c1 languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-google-gtag@npm:3.1.1" +"@docusaurus/plugin-google-gtag@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-google-gtag@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 "@types/gtag.js": ^0.0.12 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 65ede5352fa16492b23ac37343eb62f4855c4e306ce07552f3257894ddc2329a6244d371bf989bd034cd685965ef8c667d87322497e475d1e54f014fa44225f1 + checksum: b7758289d8453e98baf95d41e754c1e4c8fd5b1c000ba444c4bdf13fc97674a3cddf3215b6406266729e23898641b5bae297c5422c5bd079ef04773fa5a15c1b languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-google-tag-manager@npm:3.1.1" +"@docusaurus/plugin-google-tag-manager@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-google-tag-manager@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 9a1a014a39da0e5ef234c0eb427b7d97b59fd797152c24e05c13a6a11db4269a0223ad474d17714d02a3ad14a97a9072b6757554b3f274b5f0480d7409749a7c + checksum: 82355aed046b12ce0fead68339e24a3c6f2f517bc2b80c9c26c502cc49d86c1b6d0f797d5269d1d5e73ac78fd748c8a2f4528f7f3feee1137ae8e73876426426 languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/plugin-sitemap@npm:3.1.1" +"@docusaurus/plugin-sitemap@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/plugin-sitemap@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/logger": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-common": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 fs-extra: ^11.1.1 sitemap: ^7.1.1 tslib: ^2.6.0 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 69b584dc9143b816a06eac7bbd13501c201c24dadd339dfd18179f7a7ddcabfe76640fd87ab14f73f624916e74b7192eee5f3aca7e745f2d6a92b5754c8d7bb3 + checksum: b2e4c4fddd0fbdd4a6a4c93a0f9c16b1294162146eb9911ce378f33d70396f08dfa98d92aed133bba2a8df2b1710c257bf00c0657933ee6cd9c5edb36c8054dc languageName: node linkType: hard "@docusaurus/preset-classic@npm:^3.1.1": - version: 3.1.1 - resolution: "@docusaurus/preset-classic@npm:3.1.1" + version: 3.2.1 + resolution: "@docusaurus/preset-classic@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/plugin-content-blog": 3.1.1 - "@docusaurus/plugin-content-docs": 3.1.1 - "@docusaurus/plugin-content-pages": 3.1.1 - "@docusaurus/plugin-debug": 3.1.1 - "@docusaurus/plugin-google-analytics": 3.1.1 - "@docusaurus/plugin-google-gtag": 3.1.1 - "@docusaurus/plugin-google-tag-manager": 3.1.1 - "@docusaurus/plugin-sitemap": 3.1.1 - "@docusaurus/theme-classic": 3.1.1 - "@docusaurus/theme-common": 3.1.1 - "@docusaurus/theme-search-algolia": 3.1.1 - "@docusaurus/types": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/plugin-content-blog": 3.2.1 + "@docusaurus/plugin-content-docs": 3.2.1 + "@docusaurus/plugin-content-pages": 3.2.1 + "@docusaurus/plugin-debug": 3.2.1 + "@docusaurus/plugin-google-analytics": 3.2.1 + "@docusaurus/plugin-google-gtag": 3.2.1 + "@docusaurus/plugin-google-tag-manager": 3.2.1 + "@docusaurus/plugin-sitemap": 3.2.1 + "@docusaurus/theme-classic": 3.2.1 + "@docusaurus/theme-common": 3.2.1 + "@docusaurus/theme-search-algolia": 3.2.1 + "@docusaurus/types": 3.2.1 peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: c0fba593e9d1670ea21b6caefe297ee35ca22f6b8bc63037d8f3fa652d62516494d0a557f8949520424d01a4e92b563800599ec16a1a33655324b42d5f15c55a + checksum: 343c896f22bffbda9db4af7d652588f353c5f60336e545eb07be0dfe9bc29ca04a3978d88d5a8b3fa7caafc56a48b341349ffd08006885fa0d4de216cfdc5401 languageName: node linkType: hard @@ -2061,22 +2061,22 @@ __metadata: languageName: node linkType: hard -"@docusaurus/theme-classic@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/theme-classic@npm:3.1.1" +"@docusaurus/theme-classic@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/theme-classic@npm:3.2.1" dependencies: - "@docusaurus/core": 3.1.1 - "@docusaurus/mdx-loader": 3.1.1 - "@docusaurus/module-type-aliases": 3.1.1 - "@docusaurus/plugin-content-blog": 3.1.1 - "@docusaurus/plugin-content-docs": 3.1.1 - "@docusaurus/plugin-content-pages": 3.1.1 - "@docusaurus/theme-common": 3.1.1 - "@docusaurus/theme-translations": 3.1.1 - "@docusaurus/types": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-common": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/mdx-loader": 3.2.1 + "@docusaurus/module-type-aliases": 3.2.1 + "@docusaurus/plugin-content-blog": 3.2.1 + "@docusaurus/plugin-content-docs": 3.2.1 + "@docusaurus/plugin-content-pages": 3.2.1 + "@docusaurus/theme-common": 3.2.1 + "@docusaurus/theme-translations": 3.2.1 + "@docusaurus/types": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 "@mdx-js/react": ^3.0.0 clsx: ^2.0.0 copy-text-to-clipboard: ^3.2.0 @@ -2093,21 +2093,21 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 8d64676351408e0a378bf9b226cb9cc8f52c8b1f1e03811431de0c7af2aa80445a825267933a4c44ee92d6a975a549c47f4d28798d99cfc0b731f4c4e38f0ba3 + checksum: 7b38e47e9334ba6ad84f6432ec9ae81caad7f6c630b2a332617b0f32f1559b0e56f3d8857c732da62d1d7213ad0f493853bf18b1707a2f8d8bcccef32f1d81a1 languageName: node linkType: hard -"@docusaurus/theme-common@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/theme-common@npm:3.1.1" +"@docusaurus/theme-common@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/theme-common@npm:3.2.1" dependencies: - "@docusaurus/mdx-loader": 3.1.1 - "@docusaurus/module-type-aliases": 3.1.1 - "@docusaurus/plugin-content-blog": 3.1.1 - "@docusaurus/plugin-content-docs": 3.1.1 - "@docusaurus/plugin-content-pages": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-common": 3.1.1 + "@docusaurus/mdx-loader": 3.2.1 + "@docusaurus/module-type-aliases": 3.2.1 + "@docusaurus/plugin-content-blog": 3.2.1 + "@docusaurus/plugin-content-docs": 3.2.1 + "@docusaurus/plugin-content-pages": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -2119,22 +2119,22 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: ba7d2c4032c8d7f822e77bd39a77f712f258e9d4aa44aac75a72ad27c0eaa7400746025ba59e445c9c1b083d2ae02802a759361d67e80b9f7295521520ada1bc + checksum: 13de70293476e05f1b52c2d99a1b26c73bf99ac92aba3c8ddc413b5336725d2b54c56c167d12244fdb0b518ee9cdecbbfb3258fb8cc91272e9b795361b131fbb languageName: node linkType: hard -"@docusaurus/theme-search-algolia@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/theme-search-algolia@npm:3.1.1" +"@docusaurus/theme-search-algolia@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/theme-search-algolia@npm:3.2.1" dependencies: "@docsearch/react": ^3.5.2 - "@docusaurus/core": 3.1.1 - "@docusaurus/logger": 3.1.1 - "@docusaurus/plugin-content-docs": 3.1.1 - "@docusaurus/theme-common": 3.1.1 - "@docusaurus/theme-translations": 3.1.1 - "@docusaurus/utils": 3.1.1 - "@docusaurus/utils-validation": 3.1.1 + "@docusaurus/core": 3.2.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/plugin-content-docs": 3.2.1 + "@docusaurus/theme-common": 3.2.1 + "@docusaurus/theme-translations": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-validation": 3.2.1 algoliasearch: ^4.18.0 algoliasearch-helper: ^3.13.3 clsx: ^2.0.0 @@ -2146,23 +2146,23 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 42686e29faf14c482161eb399b1bc9960322f61298b2e6694d1b89374b2f4c352147b2299cf830abb0db8a9eba0bf5d342adfd1ad71101d1aad17f0955856145 + checksum: befbb86bf309f2b770ae21bc1d5c91eb6e840a5a72858cdfd3b21dbabadd1738d6d427ada7745f9d3424bb1a6e01839e20bf35c15a4c13d59b63d259e52de5df languageName: node linkType: hard -"@docusaurus/theme-translations@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/theme-translations@npm:3.1.1" +"@docusaurus/theme-translations@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/theme-translations@npm:3.2.1" dependencies: fs-extra: ^11.1.1 tslib: ^2.6.0 - checksum: 4fbf12aef4b6e49182b5fd6ee0c357b4cd1811490713a7cddeb7714eacebf1972dac10f3bb4ee56f6c5e62c297a9a7a17f4e3724e56ad28fa8125073e0c3258e + checksum: 43bdb90d143576d2e8eb56bfe2c9daa0e4250cdb2dcfd10096b86466e6ee253548ac5ef2f9a4986a5bc9a573d118fe4695ee5004f0ef00b57b720dac7f124337 languageName: node linkType: hard -"@docusaurus/types@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/types@npm:3.1.1" +"@docusaurus/types@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/types@npm:3.2.1" dependencies: "@mdx-js/mdx": ^3.0.0 "@types/history": ^4.7.11 @@ -2176,13 +2176,13 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 5041bca00cc887cc3ac8dd8c0895cc778dced2943b2d026fe7d1b8828756257093ad3df05706f344e91710b054f9df0e56d2d369a448472ec173bd81c4a8956f + checksum: 4f19e162bff627675df160ae5c33c6063646050c4de5c9698018fbd9d198300b9ce7a7333e4d1b369b42cfa42296dc9fb36547e4e37664d594deb08639e6b620 languageName: node linkType: hard -"@docusaurus/utils-common@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/utils-common@npm:3.1.1" +"@docusaurus/utils-common@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/utils-common@npm:3.2.1" dependencies: tslib: ^2.6.0 peerDependencies: @@ -2190,28 +2190,30 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: 787d930456e4034f7f57186172ad5d214c2473d135ec8c12cf19af8fc64e87eb35a684137e8b813f61e224243c7e3d122c6c646cb20e12ef216048be6a817f44 + checksum: bc0b7e74bc29134dbdb7fbc2e8f9f39f0f460923a07d0ccd7f0542088e92c47faf06bdbd253b7ba2b9250b0869118a3b7bf3faa3a075a2a35f5f8545eb3345f2 languageName: node linkType: hard -"@docusaurus/utils-validation@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/utils-validation@npm:3.1.1" +"@docusaurus/utils-validation@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/utils-validation@npm:3.2.1" dependencies: - "@docusaurus/logger": 3.1.1 - "@docusaurus/utils": 3.1.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/utils": 3.2.1 + "@docusaurus/utils-common": 3.2.1 joi: ^17.9.2 js-yaml: ^4.1.0 tslib: ^2.6.0 - checksum: 57d348a4bb2ecac34d84676674c3dc78d6e2821487d5c8a0b9945cab7a4362db5ee2ad084e4bb031b5c4ea674c5136c35090abea2272f57f3b25473c3b1680de + checksum: c7b5142083c8e4798c7f6aa1f7a06bc2e93e8e08a8a7a2c5eaf24aa6939e12e401f180f02164764805c40ec0f7179479e0ee98a935c2cb77037ca73ab33d80fd languageName: node linkType: hard -"@docusaurus/utils@npm:3.1.1": - version: 3.1.1 - resolution: "@docusaurus/utils@npm:3.1.1" +"@docusaurus/utils@npm:3.2.1": + version: 3.2.1 + resolution: "@docusaurus/utils@npm:3.2.1" dependencies: - "@docusaurus/logger": 3.1.1 + "@docusaurus/logger": 3.2.1 + "@docusaurus/utils-common": 3.2.1 "@svgr/webpack": ^6.5.1 escape-string-regexp: ^4.0.0 file-loader: ^6.2.0 @@ -2223,6 +2225,7 @@ __metadata: js-yaml: ^4.1.0 lodash: ^4.17.21 micromatch: ^4.0.5 + prompts: ^2.4.2 resolve-pathname: ^3.0.0 shelljs: ^0.8.5 tslib: ^2.6.0 @@ -2233,7 +2236,7 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: 45c39767d1adcc793515109edcb6cfbecd9cf5f979d5dda95a729809b9c30f6288fa5adba757ce131b91b3d77ff24bb7e06ad1b0011d37b45be0035b76ed7ba3 + checksum: ea862b178e303b49e644e77a663df6e42909632022918b77dc1ee69c4de46dde3f210052b1063e96a820e1443141f70e44aa51372f2bf9cfde65e080ea639889 languageName: node linkType: hard @@ -2521,17 +2524,6 @@ __metadata: languageName: node linkType: hard -"@slorber/static-site-generator-webpack-plugin@npm:^4.0.7": - version: 4.0.7 - resolution: "@slorber/static-site-generator-webpack-plugin@npm:4.0.7" - dependencies: - eval: ^0.1.8 - p-map: ^4.0.0 - webpack-sources: ^3.2.2 - checksum: a1e1d8b22dd51059524993f3fdd6861db10eb950debc389e5dd650702287fa2004eace03e6bc8f25b977bd7bc01d76a50aa271cbb73c58a8ec558945d728f307 - languageName: node - linkType: hard - "@spotify/prettier-config@npm:^15.0.0": version: 15.0.0 resolution: "@spotify/prettier-config@npm:15.0.0" @@ -11731,7 +11723,7 @@ __metadata: languageName: node linkType: hard -"webpack-sources@npm:^3.2.2, webpack-sources@npm:^3.2.3": +"webpack-sources@npm:^3.2.3": version: 3.2.3 resolution: "webpack-sources@npm:3.2.3" checksum: 989e401b9fe3536529e2a99dac8c1bdc50e3a0a2c8669cbafad31271eadd994bc9405f88a3039cd2e29db5e6d9d0926ceb7a1a4e7409ece021fe79c37d9c4607 diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts index fe927eaa5d..c85ba6e4b3 100644 --- a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts @@ -218,21 +218,19 @@ class DefaultHttpAuthService implements HttpAuthService { return { expiresAt }; } - async #getCookieOptions(req: Request): Promise<{ + async #getCookieOptions(_req: Request): Promise<{ domain: string; httpOnly: true; secure: boolean; priority: 'high'; sameSite: 'none' | 'lax'; }> { - const originHeader = req.headers.origin; - const origin = - !originHeader || originHeader === 'null' ? undefined : originHeader; - + // TODO: eventually we should read from `${req.protocol}://${req.hostname}` + // once https://github.com/backstage/backstage/issues/24169 has landed const externalBaseUrlStr = await this.#discovery.getExternalBaseUrl( this.#pluginId, ); - const externalBaseUrl = new URL(origin ?? externalBaseUrlStr); + const externalBaseUrl = new URL(externalBaseUrlStr); const secure = externalBaseUrl.protocol === 'https:' || diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 55cb3b34dc..888bc1639c 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -642,6 +642,21 @@ export { PluginDatabaseManager }; export { PluginEndpointDiscovery }; +// @public +export interface PullOptions { + // (undocumented) + [key: string]: unknown; + // (undocumented) + authconfig?: { + username?: string; + password?: string; + auth?: string; + email?: string; + serveraddress?: string; + [key: string]: unknown; + }; +} + // @public export type ReaderFactory = (options: { config: Config; @@ -740,6 +755,7 @@ export type RunContainerOptions = { envVars?: Record; pullImage?: boolean; defaultUser?: boolean; + pullOptions?: PullOptions; }; export { SearchOptions }; diff --git a/packages/backend-common/src/util/ContainerRunner.ts b/packages/backend-common/src/util/ContainerRunner.ts index 7dc12919b3..351a5da613 100644 --- a/packages/backend-common/src/util/ContainerRunner.ts +++ b/packages/backend-common/src/util/ContainerRunner.ts @@ -16,6 +16,25 @@ import { Writable } from 'stream'; +/** + * Allows defining access credentials for a registry + * Follows dockerode auth configuration: + * {@link https://github.com/apocas/dockerode?tab=readme-ov-file#pull-from-private-repos} + * + * @public + */ +export interface PullOptions { + authconfig?: { + username?: string; + password?: string; + auth?: string; + email?: string; + serveraddress?: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + /** * Options passed to the {@link ContainerRunner.runContainer} method. * @@ -31,6 +50,7 @@ export type RunContainerOptions = { envVars?: Record; pullImage?: boolean; defaultUser?: boolean; + pullOptions?: PullOptions; }; /** diff --git a/packages/backend-common/src/util/DockerContainerRunner.test.ts b/packages/backend-common/src/util/DockerContainerRunner.test.ts index 737bf3d178..f5d0e18480 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.test.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.test.ts @@ -83,6 +83,30 @@ describe('DockerContainerRunner', () => { expect(mockDocker.run).toHaveBeenCalled(); }); + it('should pull the docker container with authentication', async () => { + await containerTaskApi.runContainer({ + imageName, + args, + pullOptions: { + authconfig: { + auth: 'aaaaaaaaa', + }, + }, + }); + + expect(mockDocker.pull).toHaveBeenCalledWith( + imageName, + { + authconfig: { + auth: 'aaaaaaaaa', + }, + }, + expect.any(Function), + ); + + expect(mockDocker.run).toHaveBeenCalled(); + }); + it('should not pull the docker container when pullImage is false', async () => { await containerTaskApi.runContainer({ imageName, diff --git a/packages/backend-common/src/util/DockerContainerRunner.ts b/packages/backend-common/src/util/DockerContainerRunner.ts index 986470eb98..89a17fff51 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.ts @@ -47,6 +47,7 @@ export class DockerContainerRunner implements ContainerRunner { envVars = {}, pullImage = true, defaultUser = false, + pullOptions = {}, } = options; // Show a better error message when Docker is unavailable. @@ -61,7 +62,7 @@ export class DockerContainerRunner implements ContainerRunner { if (pullImage) { await new Promise((resolve, reject) => { - this.dockerClient.pull(imageName, {}, (err, stream) => { + this.dockerClient.pull(imageName, pullOptions, (err, stream) => { if (err) return reject(err); stream.pipe(logStream, { end: false }); stream.on('end', () => resolve()); diff --git a/packages/backend-common/src/util/index.ts b/packages/backend-common/src/util/index.ts index 3e7b762519..39bade08be 100644 --- a/packages/backend-common/src/util/index.ts +++ b/packages/backend-common/src/util/index.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -export type { ContainerRunner, RunContainerOptions } from './ContainerRunner'; +export type { + ContainerRunner, + RunContainerOptions, + PullOptions, +} from './ContainerRunner'; export { DockerContainerRunner } from './DockerContainerRunner'; export type { KubernetesContainerRunnerOptions, diff --git a/packages/cli/src/lib/bundler/backend.ts b/packages/cli/src/lib/bundler/backend.ts index 3094450d4b..598b77bc51 100644 --- a/packages/cli/src/lib/bundler/backend.ts +++ b/packages/cli/src/lib/bundler/backend.ts @@ -30,7 +30,7 @@ export async function serveBackend(options: BackendServeOptions) { // not include dependencies in node_modules. So we set it here at runtime as well. (process.env as { NODE_ENV: string }).NODE_ENV = 'development'; - const compiler = webpack(config, (err: Error | undefined) => { + const compiler = webpack(config, (err: Error | null) => { if (err) { console.error(err); } else console.log('Build succeeded'); diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 66572dd960..bc8f217bbb 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -25,7 +25,6 @@ export interface BaseConfigSourcesOptions { remote?: Pick; // (undocumented) rootDir?: string; - // (undocumented) substitutionFunc?: EnvFunc; // (undocumented) watch?: boolean; diff --git a/packages/config-loader/src/sources/ConfigSources.ts b/packages/config-loader/src/sources/ConfigSources.ts index 03fc6d83d6..ecff39da2d 100644 --- a/packages/config-loader/src/sources/ConfigSources.ts +++ b/packages/config-loader/src/sources/ConfigSources.ts @@ -74,6 +74,14 @@ export interface BaseConfigSourcesOptions { watch?: boolean; rootDir?: string; remote?: Pick; + + /** + * A custom substitution function that overrides the default one. + * + * @remarks + * The substitution function handles syntax like `${MY_ENV_VAR}` in configuration values. + * The default substitution will read the value from the environment and trim whitespace. + */ substitutionFunc?: SubstitutionFunc; } diff --git a/packages/config-loader/src/sources/transform/apply.test.ts b/packages/config-loader/src/sources/transform/apply.test.ts index 9f1dfbc672..3e5a4bd5e6 100644 --- a/packages/config-loader/src/sources/transform/apply.test.ts +++ b/packages/config-loader/src/sources/transform/apply.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { applyConfigTransforms } from './apply'; +import { applyConfigTransforms, createConfigTransformer } from './apply'; describe('applyConfigTransforms', () => { it('should apply no transforms to input', async () => { @@ -84,3 +84,32 @@ describe('applyConfigTransforms', () => { }); }); }); + +describe('createConfigTransformer', () => { + const origEnv = process.env; + process.env = { + ...process.env, + SECRET: 'my-secret', + PADDED_SECRET: ' \nmy-space \t', + }; + + afterAll(() => { + process.env = origEnv; + }); + + it('should substitute environment variables', async () => { + const transformer = createConfigTransformer({}); + + await expect( + transformer({ + testAlone: '${SECRET}', + testMiddle: 'hello ${SECRET}!', + testSpace: 'hello ${PADDED_SECRET}!', + }), + ).resolves.toEqual({ + testAlone: 'my-secret', + testMiddle: 'hello my-secret!', + testSpace: 'hello my-space!', + }); + }); +}); diff --git a/packages/config-loader/src/sources/transform/apply.ts b/packages/config-loader/src/sources/transform/apply.ts index 48d9c5a1e8..89ca647a1a 100644 --- a/packages/config-loader/src/sources/transform/apply.ts +++ b/packages/config-loader/src/sources/transform/apply.ts @@ -105,8 +105,10 @@ export function createConfigTransformer(options: { substitutionFunc?: SubstitutionFunc; readFile?(path: string): Promise; }): ConfigTransformer { - const { substitutionFunc = async name => process.env[name], readFile } = - options; + const { + substitutionFunc = async name => process.env[name]?.trim(), + readFile, + } = options; const substitutionTransform = createSubstitutionTransform(substitutionFunc); const transforms = [substitutionTransform]; if (readFile) { diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx index 4f28da0118..d9d82437e4 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.test.tsx @@ -45,6 +45,36 @@ describe('', () => { ).toBeInTheDocument(); }); + it('Render MarkdownContent component without custom class', async () => { + await renderInTestApp( + , + ); + const content = screen.getByText('https://example.com', { selector: 'p' }); + + expect( + Array.from(content.parentElement?.classList?.values() ?? []).map(cls => + cls.replace(/-\d+$/, ''), + ), + ).toEqual(['BackstageMarkdownContent-markdown']); + }); + + it('Render MarkdownContent component with custom class', async () => { + await renderInTestApp( + , + ); + const content = screen.getByText('https://example.com', { selector: 'p' }); + + expect( + Array.from(content.parentElement?.classList?.values() ?? []).map(cls => + cls.replace(/-\d+$/, ''), + ), + ).toEqual(['BackstageMarkdownContent-markdown', 'custom-class']); + }); + it('render MarkdownContent component with CodeSnippet for code blocks', async () => { await renderInTestApp( , diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index b422a62b6c..f536b20106 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -127,7 +127,7 @@ export function MarkdownContent(props: Props) { return ( >; registerApi< @@ -50,4 +54,10 @@ export const EntityGridItem: ( entity: Entity; }, ) => JSX.Element; + +// @public +export const SidebarSignOutButton: (props: { + icon?: IconComponent; + text?: string; +}) => React_2.JSX.Element; ``` diff --git a/packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx b/packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx new file mode 100644 index 0000000000..9cfe888d4d --- /dev/null +++ b/packages/dev-utils/src/components/SidebarSignOutButton/SidebarSignOutButton.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { SidebarItem } from '@backstage/core-components'; +import LockIcon from '@material-ui/icons/Lock'; +import { + IconComponent, + identityApiRef, + useApi, +} from '@backstage/core-plugin-api'; + +/** + * Button for sidebar that signs out user + * + * @public + */ +export const SidebarSignOutButton = (props: { + icon?: IconComponent; + text?: string; +}) => { + const identityApi = useApi(identityApiRef); + + return ( + identityApi.signOut()} + icon={props.icon ?? LockIcon} + text={props.text ?? 'Sign Out'} + /> + ); +}; diff --git a/packages/dev-utils/src/components/SidebarSignOutButton/index.ts b/packages/dev-utils/src/components/SidebarSignOutButton/index.ts new file mode 100644 index 0000000000..6182e1fa12 --- /dev/null +++ b/packages/dev-utils/src/components/SidebarSignOutButton/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2024 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 './SidebarSignOutButton'; diff --git a/packages/dev-utils/src/components/index.ts b/packages/dev-utils/src/components/index.ts index d71b424a34..8959b6e7b1 100644 --- a/packages/dev-utils/src/components/index.ts +++ b/packages/dev-utils/src/components/index.ts @@ -15,3 +15,4 @@ */ export * from './EntityGridItem'; +export * from './SidebarSignOutButton'; diff --git a/packages/dev-utils/src/devApp/render.test.tsx b/packages/dev-utils/src/devApp/render.test.tsx index 2da6ef370a..7fc0024cc3 100644 --- a/packages/dev-utils/src/devApp/render.test.tsx +++ b/packages/dev-utils/src/devApp/render.test.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { createDevApp } from './render'; -import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; const anyEnv = (process.env = { ...process.env }) as any; @@ -28,6 +28,7 @@ describe('DevAppBuilder', () => { context: 'test', data: { app: { title: 'Test App' }, + backend: { baseUrl: 'http://localhost' }, }, }, ]; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 79834cddc8..3b3c993a6b 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -25,6 +25,8 @@ import { SidebarPage, SidebarSpace, SidebarSpacer, + SignInPage, + SignInProviderConfig, } from '@backstage/core-components'; import { AnyApiFactory, @@ -44,10 +46,11 @@ import { } from '@backstage/integration-react'; import Box from '@material-ui/core/Box'; import BookmarkIcon from '@material-ui/icons/Bookmark'; -import React, { ComponentType, ReactNode, PropsWithChildren } from 'react'; +import React, { ComponentType, PropsWithChildren, ReactNode } from 'react'; import { createRoutesFromChildren, Route } from 'react-router-dom'; import { SidebarThemeSwitcher } from './SidebarThemeSwitcher'; import 'react-dom'; +import { SidebarSignOutButton } from '../components'; let ReactDOMPromise: Promise< typeof import('react-dom') | typeof import('react-dom/client') @@ -94,6 +97,7 @@ export class DevAppBuilder { private readonly rootChildren = new Array(); private readonly routes = new Array(); private readonly sidebarItems = new Array(); + private readonly signInProviders = new Array(); private defaultPage?: string; private themes?: Array; @@ -128,6 +132,16 @@ export class DevAppBuilder { return this; } + /** + * Adds a new sidebar item to the dev app. + * + * Useful for adding only sidebar items without a corresponding page. + */ + addSidebarItem(sidebarItem: JSX.Element): DevAppBuilder { + this.sidebarItems.push(sidebarItem); + return this; + } + /** * Adds a page component along with accompanying sidebar item. * @@ -151,6 +165,7 @@ export class DevAppBuilder { />, ); } + this.routes.push( { + return ( + + ); + }, + }, bindRoutes: ({ bind }) => { for (const plugin of this.plugins ?? []) { const targets: Record> = {}; @@ -217,6 +252,7 @@ export class DevAppBuilder { + {this.routes} diff --git a/plugins/apollo-explorer/README.md b/plugins/apollo-explorer/README.md index 23ea71e934..bc06612cab 100644 --- a/plugins/apollo-explorer/README.md +++ b/plugins/apollo-explorer/README.md @@ -69,3 +69,36 @@ That's it! You should now see an `Apollo Explorer` item in your sidebar, and if Once you authenticate, your graph is ready to use 🚀 ![Logged In](./docs/img/logged-in.png) + +### Authentication Tokens for Apollo Studio + +If you need to utilize an ApiRef to supply a token to Apollo, you may do so using an ApiHolder. + +In `packages/app/src/App.tsx` perform the following modifications from above. The import `ssoAuthApiRef` is used as an example and **does not exist**. + +```typescript +import { ApolloExplorerPage, EndpointProps } from '@backstage/plugin-apollo-explorer'; +import { ssoAuthApiRef } from '@companyxyz/devkit'; +import { ApiHolder } from '@backstage/core-plugin-api'; + +async function authCallback(options: { apiHolder: ApiHolder }): Promise<{token: string}> { + const sso = options.apiHolder.get(ssoAuthApiRef) + return await sso.getToken() +} + +const routes = ( + + {/* other routes... */} + + } + /> +``` diff --git a/plugins/apollo-explorer/api-report.md b/plugins/apollo-explorer/api-report.md index 7d06876e89..5371bc7219 100644 --- a/plugins/apollo-explorer/api-report.md +++ b/plugins/apollo-explorer/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { ApiHolder } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { JSONObject } from '@apollo/explorer/src/helpers/types'; import { JSX as JSX_2 } from 'react'; @@ -14,23 +15,7 @@ import { RouteRef } from '@backstage/core-plugin-api'; export const ApolloExplorerPage: (props: { title?: string | undefined; subtitle?: string | undefined; - endpoints: { - title: string; - graphRef: string; - persistExplorerState?: boolean | undefined; - initialState?: - | { - document?: string | undefined; - variables?: JSONObject | undefined; - headers?: Record | undefined; - displayOptions: { - docsPanelState?: 'closed' | 'open' | undefined; - showHeadersAndEnvVars?: boolean | undefined; - theme?: 'dark' | 'light' | undefined; - }; - } - | undefined; - }[]; + endpoints: EndpointProps[]; }) => JSX_2.Element; // @public @@ -40,4 +25,27 @@ export const apolloExplorerPlugin: BackstagePlugin< }, {} >; + +// @public +export type AuthCallback = (options: { apiHolder: ApiHolder }) => Promise<{ + token: string; +}>; + +// @public +export type EndpointProps = { + title: string; + graphRef: string; + authCallback?: AuthCallback; + persistExplorerState?: boolean; + initialState?: { + document?: string; + variables?: JSONObject; + headers?: Record; + displayOptions: { + docsPanelState?: 'open' | 'closed'; + showHeadersAndEnvVars?: boolean; + theme?: 'dark' | 'light'; + }; + }; +}; ``` diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 4516ab6404..e843dd4cba 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -36,7 +36,9 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", + "@material-ui/lab": "^4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-use": "^17.5.0", "use-deep-compare-effect": "^1.8.1" }, "devDependencies": { diff --git a/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx b/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx index cf7ef16339..d276774003 100644 --- a/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx +++ b/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx @@ -19,9 +19,11 @@ import Divider from '@material-ui/core/Divider'; import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; -import { JSONObject } from '@apollo/explorer/src/helpers/types'; import { ApolloExplorer } from '@apollo/explorer/react'; import { Content } from '@backstage/core-components'; +import { HandleRequest } from '@apollo/explorer/src/helpers/postMessageRelayHelpers'; +import { EndpointProps } from '../ApolloExplorerPage'; +import { useApiHolder } from '@backstage/core-plugin-api'; const useStyles = makeStyles(theme => ({ tabs: { @@ -38,30 +40,41 @@ const useStyles = makeStyles(theme => ({ }, })); -export type ApolloEndpointProps = { - title: string; - graphRef: string; - persistExplorerState?: boolean; - initialState?: { - document?: string; - variables?: JSONObject; - headers?: Record; - displayOptions: { - docsPanelState?: 'open' | 'closed'; - showHeadersAndEnvVars?: boolean; - theme?: 'dark' | 'light'; - }; - }; +type Props = { + endpoints: EndpointProps[]; + authCallback?: () => Promise<{ token: string }>; }; -type Props = { - endpoints: ApolloEndpointProps[]; +export const handleAuthRequest = ({ + authCallback, +}: { + authCallback: Props['authCallback']; +}): HandleRequest => { + const handleRequest: HandleRequest = async (endpointUrl, options) => + fetch(endpointUrl, { + ...options, + headers: { + ...options.headers, + ...(authCallback && { + Authorization: `Bearer ${(await authCallback()).token}`, + }), + }, + }); + return handleRequest; }; export const ApolloExplorerBrowser = ({ endpoints }: Props) => { const classes = useStyles(); const [tabIndex, setTabIndex] = useState(0); + const apiHolder = useApiHolder(); + + const getAuthCallback = (index: number) => { + const authCallback = endpoints[index].authCallback; + if (authCallback === undefined) return undefined; + return () => authCallback({ apiHolder }); + }; + return (
{ diff --git a/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx b/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx index f90af9bc17..53b6a54194 100644 --- a/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx +++ b/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx @@ -18,10 +18,17 @@ import React from 'react'; import { Content, Header, Page } from '@backstage/core-components'; import { ApolloExplorerBrowser } from '../ApolloExplorerBrowser'; import { JSONObject } from '@apollo/explorer/src/helpers/types'; +import { ApiHolder } from '@backstage/core-plugin-api'; -type EndpointProps = { +/** + * Export types to be used with {@link @backstage/apollo-explorer#ApolloExplorerPage}. + * + * @public + */ +export type EndpointProps = { title: string; graphRef: string; + authCallback?: AuthCallback; persistExplorerState?: boolean; initialState?: { document?: string; @@ -35,6 +42,15 @@ type EndpointProps = { }; }; +/** + * Export types to be used with {@link @backstage/apollo-explorer#ApolloExplorerPage}. + * + * @public + */ +export type AuthCallback = (options: { + apiHolder: ApiHolder; +}) => Promise<{ token: string }>; + type Props = { title?: string | undefined; subtitle?: string | undefined; @@ -43,6 +59,7 @@ type Props = { export const ApolloExplorerPage = (props: Props) => { const { title, subtitle, endpoints } = props; + return (
diff --git a/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts b/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts index 8f62872fe6..3ca6d12fd0 100644 --- a/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts +++ b/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { ApolloExplorerPage } from './ApolloExplorerPage'; +export type { EndpointProps, AuthCallback } from './ApolloExplorerPage'; diff --git a/plugins/apollo-explorer/src/index.ts b/plugins/apollo-explorer/src/index.ts index 565557e79f..471ec9e710 100644 --- a/plugins/apollo-explorer/src/index.ts +++ b/plugins/apollo-explorer/src/index.ts @@ -20,3 +20,7 @@ * @packageDocumentation */ export { apolloExplorerPlugin, ApolloExplorerPage } from './plugin'; +export type { + EndpointProps, + AuthCallback, +} from './components/ApolloExplorerPage'; diff --git a/plugins/app-backend/migrations/20240113144027_assets-namespace.js b/plugins/app-backend/migrations/20240113144027_assets-namespace.js index b533d35dfa..6e94ac1531 100644 --- a/plugins/app-backend/migrations/20240113144027_assets-namespace.js +++ b/plugins/app-backend/migrations/20240113144027_assets-namespace.js @@ -20,19 +20,63 @@ * @param {import('knex').Knex} knex */ exports.up = async function up(knex) { + const isMySQL = knex.client.config.client.includes('mysql'); + await knex.schema.alterTable('static_assets_cache', table => { // The namespace is used to allow operations on asset groups, e.g. delete all assets in a namespace - table.string('namespace').comment('The namespace of the file'); - table.index('namespace', 'static_asset_cache_namespace_idx'); + table + .string('namespace') + .defaultTo('default') + .comment('The namespace of the file'); + + if (!isMySQL) { + table.dropPrimary(); + table.primary(['namespace', 'path']); + } }); + + if (isMySQL) { + await knex.schema.raw( + 'drop index static_assets_cache_path_idx on static_assets_cache;', + ); + await knex.schema.raw( + 'create unique index static_assets_cache_path_idx on static_assets_cache(namespace, path(254));', + ); + } }; /** * @param {import('knex').Knex} knex */ exports.down = async function down(knex) { + const isMySQL = knex.client.config.client.includes('mysql'); + + await knex + .delete() + .from('static_assets_cache') + .whereNot('namespace', 'default'); + + if (isMySQL) { + await knex.schema.raw( + 'drop index static_assets_cache_path_idx on static_assets_cache;', + ); + } + await knex.schema.alterTable('static_assets_cache', table => { - table.dropIndex([], 'static_asset_cache_namespace_idx'); + if (!isMySQL) { + table.dropPrimary(); + } + table.dropColumn('namespace'); + + if (!isMySQL) { + table.primary(['path']); + } }); + + if (isMySQL) { + await knex.schema.raw( + 'create unique index static_assets_cache_path_idx on static_assets_cache(path(254));', + ); + } }; diff --git a/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts b/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts index d975751676..41408396fe 100644 --- a/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts +++ b/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts @@ -36,9 +36,7 @@ function createDatabaseManager( jest.setTimeout(60_000); describe('StaticAssetsStore', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( 'should store and retrieve assets, %p', diff --git a/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts b/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts index 3474b84327..05d6424013 100644 --- a/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts +++ b/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts @@ -50,7 +50,7 @@ export interface StaticAssetsStoreOptions { export class StaticAssetsStore implements StaticAssetProvider { #db: Knex; #logger: LoggerService; - #namespace: string | null; + #namespace: string; static async create(options: StaticAssetsStoreOptions) { const { database } = options; @@ -68,7 +68,7 @@ export class StaticAssetsStore implements StaticAssetProvider { private constructor(client: Knex, logger: LoggerService, namespace?: string) { this.#db = client; this.#logger = logger; - this.#namespace = namespace ?? null; + this.#namespace = namespace ?? 'default'; } /** @@ -120,7 +120,7 @@ export class StaticAssetsStore implements StaticAssetProvider { content: await asset.content(), namespace: this.#namespace, }) - .onConflict('path') + .onConflict(['namespace', 'path']) .ignore(); } } diff --git a/plugins/app-backend/src/migrations.test.ts b/plugins/app-backend/src/migrations.test.ts new file mode 100644 index 0000000000..8a413559d2 --- /dev/null +++ b/plugins/app-backend/src/migrations.test.ts @@ -0,0 +1,141 @@ +/* + * 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. + */ + +import { Knex } from 'knex'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import fs from 'fs'; + +const migrationsDir = `${__dirname}/../migrations`; +const migrationsFiles = fs.readdirSync(migrationsDir).sort(); + +async function migrateUpOnce(knex: Knex): Promise { + await knex.migrate.up({ directory: migrationsDir }); +} + +async function migrateDownOnce(knex: Knex): Promise { + await knex.migrate.down({ directory: migrationsDir }); +} + +async function migrateUntilBefore(knex: Knex, target: string): Promise { + const index = migrationsFiles.indexOf(target); + if (index === -1) { + throw new Error(`Migration ${target} not found`); + } + for (let i = 0; i < index; i++) { + await migrateUpOnce(knex); + } +} + +jest.setTimeout(60_000); + +describe('migrations', () => { + const databases = TestDatabases.create(); + + it.each(databases.eachSupportedId())( + '20211229105307_init.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20211229105307_init.js'); + await migrateUpOnce(knex); + + await knex('static_assets_cache').insert({ + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: knex.fn.now(), + }); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: expect.anything(), + }, + ]); + + await migrateDownOnce(knex); + + // This looks odd - you might expect a .toThrow at the end but that + // actually is flaky for some reason specifically on sqlite when + // performing multiple runs in sequence + await expect(knex('static_assets_cache')).rejects.toEqual( + expect.anything(), + ); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + '20240113144027_assets-namespace.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20240113144027_assets-namespace.js'); + + await knex('static_assets_cache').insert({ + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: knex.fn.now(), + }); + + await migrateUpOnce(knex); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + namespace: 'default', + last_modified_at: expect.anything(), + }, + ]); + + await knex('static_assets_cache').insert({ + path: 'main.js', + content: Buffer.from('other-script'), + namespace: 'other', + last_modified_at: knex.fn.now(), + }); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + namespace: 'default', + last_modified_at: expect.anything(), + }, + { + path: 'main.js', + content: Buffer.from('other-script'), + namespace: 'other', + last_modified_at: expect.anything(), + }, + ]); + + await migrateDownOnce(knex); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: expect.anything(), + }, + ]); + + await knex.destroy(); + }, + ); +}); diff --git a/plugins/app-backend/src/setupTests.ts b/plugins/app-backend/src/setupTests.ts index d3232290a7..4ed508a382 100644 --- a/plugins/app-backend/src/setupTests.ts +++ b/plugins/app-backend/src/setupTests.ts @@ -14,4 +14,8 @@ * limitations under the License. */ -export {}; +import { TestDatabases } from '@backstage/backend-test-utils'; + +TestDatabases.setDefaults({ + ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], +}); diff --git a/plugins/auth-backend-module-aws-alb-provider/src/helpers.test.ts b/plugins/auth-backend-module-aws-alb-provider/src/helpers.test.ts index cb103debb0..0bbcf9b5c1 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/helpers.test.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/helpers.test.ts @@ -26,7 +26,13 @@ import { makeProfileInfo, provisionKeyCache } from './helpers'; jest.mock('crypto'); const cryptoMock = crypto as jest.Mocked; -describe('helpers', () => { +describe.each([ + ['eu-west-1', 'https://public-keys.auth.elb.eu-west-1.amazonaws.com/kid'], + [ + 'us-gov-west-1', + 'https://s3-us-gov-west-1.amazonaws.com/aws-elb-public-keys-prod-us-gov-west-1/kid', + ], +])('helpers', (region, url) => { const server = setupServer(); setupRequestMockHandlers(server); @@ -37,7 +43,7 @@ describe('helpers', () => { jest.clearAllMocks(); server.use( http.get( - 'https://public-keys.auth.elb.eu-west-1.amazonaws.com/kid', + url, () => new HttpResponse( `-----BEGIN PUBLIC KEY----- @@ -51,12 +57,12 @@ yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw== }); it('should create a key', () => { - const getKey = provisionKeyCache('eu-west-1', nodeCache); + const getKey = provisionKeyCache(region, nodeCache); expect(getKey).toBeDefined(); }); it('should return a key from cache', async () => { - const getKey = provisionKeyCache('eu-west-1', nodeCache); + const getKey = provisionKeyCache(region, nodeCache); cryptoMock.createPublicKey.mockReturnValueOnce('key'); nodeCache.get = jest.fn().mockReturnValue('key'); @@ -67,7 +73,7 @@ yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw== }); it('should update cache if key is not found', async () => { - const getKey = provisionKeyCache('eu-west-1', nodeCache); + const getKey = provisionKeyCache(region, nodeCache); nodeCache.get = jest.fn().mockReturnValue(undefined); jest.spyOn(nodeCache, 'set'); @@ -80,7 +86,7 @@ yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw== }); it('should throw error if key is not found', async () => { - const getKey = provisionKeyCache('eu-west-1', nodeCache); + const getKey = provisionKeyCache(region, nodeCache); nodeCache.get = jest.fn().mockReturnValue(undefined); cryptoMock.createPublicKey.mockReturnValue(undefined); @@ -91,7 +97,7 @@ yOlxJ2VW88mLAQGJ7HPAvOdylxZsItMnzCuqNzZvie8m/NJsOjhDncVkrw== }); it('should throw if key is not present in request header', async () => { - const getKey = provisionKeyCache('eu-west-1', nodeCache); + const getKey = provisionKeyCache(region, nodeCache); nodeCache.get = jest.fn().mockReturnValue(undefined); diff --git a/plugins/auth-backend-module-aws-alb-provider/src/helpers.ts b/plugins/auth-backend-module-aws-alb-provider/src/helpers.ts index e846b2484e..24026e2c9c 100644 --- a/plugins/auth-backend-module-aws-alb-provider/src/helpers.ts +++ b/plugins/auth-backend-module-aws-alb-provider/src/helpers.ts @@ -70,6 +70,18 @@ export const makeProfileInfo = ( }; }; +const getPublicKeyEndpoint = (region: string) => { + if (region.startsWith('us-gov')) { + return `https://s3-${encodeURIComponent( + region, + )}.amazonaws.com/aws-elb-public-keys-prod-${encodeURIComponent(region)}`; + } + + return `https://public-keys.auth.elb.${encodeURIComponent( + region, + )}.amazonaws.com`; +}; + export const provisionKeyCache = (region: string, keyCache: NodeCache) => { return async (header: JWTHeaderParameters): Promise => { if (!header.kid) { @@ -79,10 +91,9 @@ export const provisionKeyCache = (region: string, keyCache: NodeCache) => { if (optionalCacheKey) { return crypto.createPublicKey(optionalCacheKey); } + const keyText: string = await fetch( - `https://public-keys.auth.elb.${encodeURIComponent( - region, - )}.amazonaws.com/${encodeURIComponent(header.kid)}`, + `${getPublicKeyEndpoint(region)}/${encodeURIComponent(header.kid)}`, ).then(response => response.text()); const keyValue = crypto.createPublicKey(keyText); diff --git a/plugins/catalog-backend-module-backstage-openapi/README.md b/plugins/catalog-backend-module-backstage-openapi/README.md index 821a1b7878..8df5b5d6c9 100644 --- a/plugins/catalog-backend-module-backstage-openapi/README.md +++ b/plugins/catalog-backend-module-backstage-openapi/README.md @@ -2,7 +2,7 @@ ## Summary -This module installs an entity provider that exports a single entity, your Backstage instance documentation, which merges as many backend plugins as you have defined in the config value `catalog.providers.openapi.plugins`. +This module installs an entity provider that exports a single entity, your Backstage instance documentation, which merges as many backend plugins as you have defined in the config value `catalog.providers.backstageOpenapi.plugins`. ## Notes @@ -10,7 +10,7 @@ This module installs an entity provider that exports a single entity, your Backs ## Installation -To your new backend file, add +To your new backend file, add: ```ts title="packages/backend/src/index.ts" backend.add( @@ -18,7 +18,7 @@ backend.add( ); ``` -Add a list of plugins to your config like, +Add a list of plugins and optional entity overrides to your config. For example: ```yaml title="app-config.yaml" catalog: @@ -28,6 +28,12 @@ catalog: - catalog - todo - search + entityOverrides: # All optional + metadata: + name: 'my name' + title: 'my title' + spec: + owner: 'my team' ``` We will attempt to load each plugin's OpenAPI spec hosted at `${pluginRoute}/openapi.json`. These are automatically added if you are using `@backstage/backend-openapi-utils`'s `createValidatedOpenApiRouter`. diff --git a/plugins/catalog-backend-module-backstage-openapi/config.d.ts b/plugins/catalog-backend-module-backstage-openapi/config.d.ts index ce51640495..376c702999 100644 --- a/plugins/catalog-backend-module-backstage-openapi/config.d.ts +++ b/plugins/catalog-backend-module-backstage-openapi/config.d.ts @@ -25,6 +25,10 @@ export interface Config { * A list of plugins, whose OpenAPI specs you want to collate in `InternalOpenApiDocumentationProvider`. */ plugins: string[]; + /** + * Properties to override on the final entity object. + */ + entityOverrides?: object; }; }; }; diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 5d1c109f5c..d9ee9ec6e4 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -39,6 +39,7 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "cross-fetch": "^4.0.0", + "lodash": "^4.17.21", "openapi-merge": "^1.3.2", "uuid": "^9.0.0" }, diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index dd94000ad7..971a5fbc47 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -38,7 +38,8 @@ import { DiscoveryService, LoggerService, } from '@backstage/backend-plugin-api'; -import * as uuid from 'uuid'; +import uuid from 'uuid'; +import lodash from 'lodash'; import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; const HTTP_VERBS: (keyof PathItemObject)[] = [ @@ -229,13 +230,26 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { const pluginsToMerge = this.config.getStringArray( 'catalog.providers.backstageOpenapi.plugins', ); - logger.info(`Loading specs from from ${pluginsToMerge}.`); - const documentationEntity: ApiEntity = { + const configToMerge = this.config.getOptional( + 'catalog.providers.backstageOpenapi.entityOverrides', + ); + + const baseConfig = { + metadata: { + name: 'internal_backstage_openapi_doc', + title: 'Backstage API', + }, + spec: { + lifecycle: 'production', + owner: 'backstage', + }, + }; + + logger.info(`Loading specs from ${pluginsToMerge}.`); + const requiredConfig = { apiVersion: 'backstage.io/v1beta1', kind: 'API', metadata: { - name: 'INTERNAL_instance_openapi_doc', - title: 'Your Backstage Instance documentation', annotations: { [ANNOTATION_LOCATION]: 'internal-package:@backstage/plugin-catalog-backend-module-backstage-openapi', @@ -245,8 +259,6 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { }, spec: { type: 'openapi', - lifecycle: 'production', - owner: 'backstage', definition: JSON.stringify( await loadSpecs({ baseUrl: this.config.getString('backend.baseUrl'), @@ -258,6 +270,16 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { ), }, }; + + // Overwrite baseConfig with options from config file. + const mergedConfig = lodash.merge(baseConfig, configToMerge); + + // Overwite mergedConfig with requiredConfig (i.e., spec.type and spec.definition) to avoid bad configuration. + const documentationEntity = lodash.merge( + mergedConfig, + requiredConfig, + ) as ApiEntity; + await this.connection?.applyMutation({ type: 'full', entities: [ diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx index 94af52f374..26dee40ec3 100644 --- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx @@ -30,7 +30,7 @@ interface EntityFilters extends DefaultEntityFilters { options?: EntityOptionFilter; } -const options = ['option1', 'option2', 'option3', 'option4']; +const defaultOptions = ['option1', 'option2', 'option3', 'option4']; class EntityOptionFilter implements EntityFilter { constructor(readonly values: string[]) {} @@ -46,20 +46,25 @@ class EntityOptionFilter implements EntityFilter { } } -describe('', () => { - const mockCatalogApi: Partial> = { +const makeMockCatalogApi = ( + opts: string[] = defaultOptions, +): Partial> => { + return { getEntityFacets: jest.fn().mockResolvedValue({ facets: { - 'spec.options': options.map((value, idx) => ({ value, count: idx })), + 'spec.options': opts.map((value, idx) => ({ value, count: idx })), }, }), }; +}; +describe('', () => { beforeEach(() => { jest.clearAllMocks(); }); it('renders all options', async () => { + const mockCatalogApi = makeMockCatalogApi(); render( @@ -83,12 +88,51 @@ describe('', () => { }); fireEvent.click(screen.getByTestId('options-picker-expand')); - options.forEach(option => { + defaultOptions.forEach(option => { expect(screen.getByText(option)).toBeInTheDocument(); }); }); + it('hides filter if there are no available options', async () => { + const mockCatalogApi = makeMockCatalogApi([]); + render( + + + + label="Options" + path="spec.options" + name="options" + Filter={EntityOptionFilter} + /> + + , + ); + await waitFor(() => { + expect(screen.queryByText('Options')).not.toBeInTheDocument(); + }); + }); + + it('renders filter if there is one available option', async () => { + const mockCatalogApi = makeMockCatalogApi(['option1']); + render( + + + + label="Options" + path="spec.options" + name="options" + Filter={EntityOptionFilter} + /> + + , + ); + await waitFor(() => { + expect(screen.queryByText('Options')).toBeInTheDocument(); + }); + }); + it('renders unique options in alphabetical order', async () => { + const mockCatalogApi = makeMockCatalogApi(); render( @@ -116,6 +160,7 @@ describe('', () => { }); it('renders options with counts', async () => { + const mockCatalogApi = makeMockCatalogApi(); render( @@ -144,6 +189,7 @@ describe('', () => { }); it('respects the query parameter filter value', async () => { + const mockCatalogApi = makeMockCatalogApi(); const updateFilters = jest.fn(); const queryParameters = { options: ['option3'] }; render( @@ -172,6 +218,7 @@ describe('', () => { }); it('adds options to filters', async () => { + const mockCatalogApi = makeMockCatalogApi(); const updateFilters = jest.fn(); render( @@ -201,6 +248,7 @@ describe('', () => { }); it('removes options from filters', async () => { + const mockCatalogApi = makeMockCatalogApi(); const updateFilters = jest.fn(); render( @@ -234,6 +282,7 @@ describe('', () => { }); it('responds to external queryParameters changes', async () => { + const mockCatalogApi = makeMockCatalogApi(); const updateFilters = jest.fn(); const rendered = render( @@ -280,6 +329,7 @@ describe('', () => { }); it('filters available values by kind as default', async () => { + const mockCatalogApi = makeMockCatalogApi(); render( ', () => { }); it('can be supplied with filters for available values', async () => { + const mockCatalogApi = makeMockCatalogApi(); render( diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index 5dc9005126..15dc31b730 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -48,11 +48,18 @@ const generatedColumns: TableColumn[] = [ field: 'id', type: 'numeric', width: '150px', - render: (row: Partial) => ( - - {row.id?.substring(0, 8)} - - ), + render: (row: Partial) => { + const LinkWrapper = () => { + const routeLink = useRouteRef(buildRouteRef); + return ( + + {row.id?.substring(0, 8)} + + ); + }; + + return ; + }, }, { title: 'Trigger Name', @@ -67,18 +74,11 @@ const generatedColumns: TableColumn[] = [ field: 'source', highlight: true, width: '200px', - render: (row: Partial) => { - const LinkWrapper = () => { - const routeLink = useRouteRef(buildRouteRef); - return ( - - {row.message} - - ); - }; - - return ; - }, + render: (row: Partial) => ( + + {row.message} + + ), }, { title: 'Ref', diff --git a/plugins/notifications-backend/dev/index.ts b/plugins/notifications-backend/dev/index.ts new file mode 100644 index 0000000000..fe35606dd2 --- /dev/null +++ b/plugins/notifications-backend/dev/index.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { notificationService } from '@backstage/plugin-notifications-node'; + +const notificationsDebug = createBackendPlugin({ + pluginId: 'notifications-debug', + register(env) { + env.registerInit({ + deps: { + notifications: notificationService, + lifecycle: coreServices.lifecycle, + }, + async init({ notifications, lifecycle }) { + let interval: NodeJS.Timeout | undefined; + lifecycle.addStartupHook(async () => { + interval = setInterval(async () => { + await notifications.send({ + recipients: { + type: 'broadcast', + }, + payload: { title: 'Test notification' }, + }); + }, 5000); + }); + + lifecycle.addShutdownHook(async () => { + if (interval) { + clearInterval(interval); + } + }); + }, + }); + }, +}); + +const backend = createBackend(); +backend.add(import('@backstage/plugin-events-backend/alpha')); +backend.add(import('@backstage/plugin-signals-backend')); +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +backend.add(import('../src')); +backend.add(notificationsDebug); + +backend.start(); diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 15297856fb..cdb7642301 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -51,8 +51,13 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", + "@backstage/plugin-events-backend": "workspace:^", + "@backstage/plugin-signals-backend": "workspace:^", "@types/express": "^4.17.6", "@types/supertest": "^2.0.8", "msw": "^1.0.0", diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index f4f1f174be..ae548a9892 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -25,6 +25,7 @@ import { import { Notification, NotificationSeverity, + notificationSeverities, } from '@backstage/plugin-notifications-common'; import { Knex } from 'knex'; @@ -49,16 +50,9 @@ const NOTIFICATION_COLUMNS = [ 'saved', ]; -const severities: NotificationSeverity[] = [ - 'critical', - 'high', - 'normal', - 'low', -]; - export const normalizeSeverity = (input?: string): NotificationSeverity => { let lower = (input ?? 'normal').toLowerCase() as NotificationSeverity; - if (severities.indexOf(lower) < 0) { + if (notificationSeverities.indexOf(lower) < 0) { lower = 'normal'; } return lower; @@ -219,8 +213,8 @@ export class DatabaseNotificationsStore implements NotificationsStore { } // or match both if undefined if (options.minimumSeverity !== undefined) { - const idx = severities.indexOf(options.minimumSeverity); - const equalOrHigher = severities.slice(0, idx + 1); + const idx = notificationSeverities.indexOf(options.minimumSeverity); + const equalOrHigher = notificationSeverities.slice(0, idx + 1); query.whereIn('severity', equalOrHigher); } diff --git a/plugins/notifications-backend/src/run.ts b/plugins/notifications-backend/src/run.ts deleted file mode 100644 index d299ed23e9..0000000000 --- a/plugins/notifications-backend/src/run.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 0bce3d4bb3..30829a578b 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -28,6 +28,7 @@ import { isGroupEntity, isUserEntity, RELATION_HAS_MEMBER, + RELATION_OWNED_BY, RELATION_PARENT_OF, stringifyEntityRef, } from '@backstage/catalog-model'; @@ -103,17 +104,13 @@ export async function createRouter( return []; } + const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations']; + const refs = Array.isArray(entityRef) ? entityRef : [entityRef]; const entities = await catalogClient.getEntitiesByRefs( { entityRefs: refs, - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'relations', - 'spec.owner', - ], + fields, }, { token }, ); @@ -126,29 +123,17 @@ export async function createRouter( return [stringifyEntityRef(entity)]; } else if (isGroupEntity(entity) && entity.relations) { const users = entity.relations - .filter( - relation => - relation.type === RELATION_HAS_MEMBER && relation.targetRef, - ) + .filter(relation => relation.type === RELATION_HAS_MEMBER) .map(r => r.targetRef); const childGroupRefs = entity.relations - .filter( - relation => - relation.type === RELATION_PARENT_OF && relation.targetRef, - ) + .filter(relation => relation.type === RELATION_PARENT_OF) .map(r => r.targetRef); const childGroups = await catalogClient.getEntitiesByRefs( { entityRefs: childGroupRefs, - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'relations', - 'spec.owner', - ], + fields, }, { token }, ); @@ -156,13 +141,16 @@ export async function createRouter( childGroups.items.map(mapEntity), ); return [...users, ...childGroupUsers.flat(2)]; - } else if (!isGroupEntity(entity) && entity.spec?.owner) { - const owner = await catalogClient.getEntityByRef( - entity.spec.owner as string, - { token }, - ); - if (owner) { - return mapEntity(owner); + } else if (entity.relations) { + const ownerRef = entity.relations.find( + relation => relation.type === RELATION_OWNED_BY, + )?.targetRef; + + if (ownerRef) { + const owner = await catalogClient.getEntityByRef(ownerRef, { token }); + if (owner) { + return mapEntity(owner); + } } } @@ -274,9 +262,9 @@ export async function createRouter( } opts.createdAfter = new Date(sinceEpoch); } - if (req.query.minimal_severity) { + if (req.query.minimumSeverity) { opts.minimumSeverity = normalizeSeverity( - req.query.minimal_severity.toString(), + req.query.minimumSeverity.toString(), ); } @@ -470,7 +458,7 @@ export async function createRouter( if (!recipients || !title) { logger.error(`Invalid notification request received`); - throw new InputError(); + throw new InputError(`Invalid notification request received`); } const origin = credentials.principal.subject; @@ -496,8 +484,10 @@ export async function createRouter( try { users = await getUsersForEntityRef(entityRef); } catch (e) { - throw new InputError(); + logger.error(`Failed to resolve notification receivers: ${e}`); + throw new InputError('Failed to resolve notification receivers', e); } + const userNotifications = await sendUserNotifications( baseNotification, users, diff --git a/plugins/notifications-backend/src/service/standaloneServer.ts b/plugins/notifications-backend/src/service/standaloneServer.ts deleted file mode 100644 index 5445d9b6b4..0000000000 --- a/plugins/notifications-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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. - */ -import { - createLegacyAuthAdapters, - createServiceBuilder, - HostDiscovery, - loadBackendConfig, - PluginDatabaseManager, - ServerTokenManager, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; -import Knex from 'knex'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -import { Request } from 'express'; -import { - CatalogApi, - CatalogRequestOptions, - GetEntitiesByRefsRequest, -} from '@backstage/catalog-client'; -import { DefaultSignalsService } from '@backstage/plugin-signals-node'; -import { - EventParams, - EventsService, - EventsServiceSubscribeOptions, -} from '@backstage/plugin-events-node'; -import { - AuthService, - HttpAuthService, - UserInfoService, -} from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'notifications-backend' }); - logger.debug('Starting application server...'); - - const config = await loadBackendConfig({ logger, argv: process.argv }); - const db = Knex(config.get('backend.database')); - - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const discovery = HostDiscovery.fromConfig(config); - - const dbMock: PluginDatabaseManager = { - async getClient() { - return db; - }, - }; - - const catalogApi = { - async getEntitiesByRefs( - _request: GetEntitiesByRefsRequest, - __options?: CatalogRequestOptions, - ) { - return { - items: [ - { - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - metadata: { name: 'user', namespace: 'default' }, - spec: {}, - }, - ], - }; - }, - } as Partial as CatalogApi; - - const identityMock: IdentityApi = { - async getIdentity({ request }: { request: Request }) { - const token = request.headers.authorization?.split(' ')[1]; - return { - identity: { - type: 'user', - ownershipEntityRefs: [], - userEntityRef: 'user:default/guest', - }, - token: token || 'no-token', - }; - }, - }; - - const mockSubscribers: EventsServiceSubscribeOptions[] = []; - const events: EventsService = { - async publish(params: EventParams): Promise { - mockSubscribers.forEach(sub => sub.onEvent(params)); - }, - async subscribe(subscription: EventsServiceSubscribeOptions) { - mockSubscribers.push(subscription); - }, - }; - - const signalService = DefaultSignalsService.create({ events }); - // TODO: Move to use services instead this hack - const { auth, httpAuth, userInfo } = createLegacyAuthAdapters< - any, - { auth: AuthService; httpAuth: HttpAuthService; userInfo: UserInfoService } - >({ - identity: identityMock, - tokenManager, - discovery, - }); - - const router = await createRouter({ - logger, - database: dbMock, - catalog: catalogApi, - discovery, - signals: signalService, - auth, - httpAuth, - userInfo, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/notifications', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); diff --git a/plugins/notifications-common/api-report.md b/plugins/notifications-common/api-report.md index f7fed1a5d0..3ddce24393 100644 --- a/plugins/notifications-common/api-report.md +++ b/plugins/notifications-common/api-report.md @@ -39,6 +39,9 @@ export type NotificationReadSignal = { notification_ids: string[]; }; +// @public +export const notificationSeverities: NotificationSeverity[]; + // @public (undocumented) export type NotificationSeverity = 'critical' | 'high' | 'normal' | 'low'; diff --git a/plugins/notifications-common/src/constants.ts b/plugins/notifications-common/src/constants.ts new file mode 100644 index 0000000000..3c0610737c --- /dev/null +++ b/plugins/notifications-common/src/constants.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NotificationSeverity } from './types'; + +/** Ordered list of severities used by the Notifications. + * + * @public */ +export const notificationSeverities: NotificationSeverity[] = [ + 'critical', + 'high', + 'normal', + 'low', +]; diff --git a/plugins/notifications-common/src/index.ts b/plugins/notifications-common/src/index.ts index 8d9f26f07a..7d74c75be8 100644 --- a/plugins/notifications-common/src/index.ts +++ b/plugins/notifications-common/src/index.ts @@ -21,3 +21,4 @@ */ export * from './types'; +export * from './constants'; diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index f4ea31f4ab..f4d9176a70 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -15,13 +15,14 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { notificationsPlugin } from '../src/plugin'; +import { NotificationsPage, notificationsPlugin } from '../src/plugin'; +import { NotificationsSidebarItem } from '../src'; createDevApp() .registerPlugin(notificationsPlugin) .addPage({ - element:
, - title: 'Root Page', + element: , path: '/notifications', }) + .addSidebarItem() .render(); diff --git a/plugins/notifications/src/api/NotificationsClient.ts b/plugins/notifications/src/api/NotificationsClient.ts index c4fffded9e..10ad58bd92 100644 --- a/plugins/notifications/src/api/NotificationsClient.ts +++ b/plugins/notifications/src/api/NotificationsClient.ts @@ -68,7 +68,7 @@ export class NotificationsClient implements NotificationsApi { queryString.append('createdAfter', options.createdAfter.toISOString()); } if (options?.minimumSeverity !== undefined) { - queryString.append('minimal_severity', options.minimumSeverity); + queryString.append('minimumSeverity', options.minimumSeverity); } const urlSegment = `?${queryString}`; diff --git a/plugins/notifications/src/components/NotificationsTable/SeverityIcon.tsx b/plugins/notifications/src/components/NotificationsTable/SeverityIcon.tsx index 37680db3c2..062c2ae5b7 100644 --- a/plugins/notifications/src/components/NotificationsTable/SeverityIcon.tsx +++ b/plugins/notifications/src/components/NotificationsTable/SeverityIcon.tsx @@ -19,21 +19,39 @@ import NormalIcon from '@material-ui/icons/CheckOutlined'; import CriticalIcon from '@material-ui/icons/ErrorOutline'; import HighIcon from '@material-ui/icons/WarningOutlined'; import LowIcon from '@material-ui/icons/InfoOutlined'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles(theme => ({ + critical: { + color: theme.palette.status.error, + }, + high: { + color: theme.palette.status.warning, + }, + normal: { + color: theme.palette.status.ok, + }, + low: { + color: theme.palette.status.running, + }, +})); export const SeverityIcon = ({ severity, }: { severity?: NotificationSeverity; }) => { + const classes = useStyles(); + switch (severity) { case 'critical': - return ; + return ; case 'high': - return ; + return ; case 'low': - return ; + return ; case 'normal': default: - return ; + return ; } }; diff --git a/plugins/org-react/.eslintrc.js b/plugins/org-react/.eslintrc.js index e2a53a6ad2..e487f765b2 100644 --- a/plugins/org-react/.eslintrc.js +++ b/plugins/org-react/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/plugins/org-react/src/components/GroupListPicker/GroupListPickerButton.tsx b/plugins/org-react/src/components/GroupListPicker/GroupListPickerButton.tsx index f4255a18be..b7a31f89f2 100644 --- a/plugins/org-react/src/components/GroupListPicker/GroupListPickerButton.tsx +++ b/plugins/org-react/src/components/GroupListPicker/GroupListPickerButton.tsx @@ -15,7 +15,9 @@ */ import React from 'react'; -import { makeStyles, Typography, Button } from '@material-ui/core'; +import Typography from '@material-ui/core/Typography'; +import Button from '@material-ui/core/Button'; +import { makeStyles } from '@material-ui/core/styles'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import PeopleIcon from '@material-ui/icons/People'; diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.examples.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.examples.test.ts new file mode 100644 index 0000000000..d1bc594d54 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.examples.test.ts @@ -0,0 +1,245 @@ +/* + * 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. + */ + +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + commitAndPushRepo: jest.fn(), + }; +}); + +import { createPublishGerritReviewAction } from './gerritReview'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { commitAndPushRepo } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import yaml from 'yaml'; +import { examples } from './gerritReview.examples'; + +describe('publish:gerrit:review', () => { + const config = new ConfigReader({ + integrations: { + gerrit: [ + { + host: 'gerrithost.org', + username: 'gerrituser', + password: 'usertoken', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createPublishGerritReviewAction({ integrations, config }); + const mockContext = createMockActionContext({ + input: { + repoUrl: + 'gerrithost.org?owner=owner&workspace=parent&project=project&repo=repo', + gitCommitMessage: 'Review from backstage', + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`Should ${examples[0].description}`, async () => { + expect.assertions(3); + + const input = yaml.parse(examples[0].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(commitAndPushRepo).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('Initial Commit Message'), + gitAuthorInfo: {}, + branch: 'master', + remoteRef: 'refs/for/master', + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/master', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'reviewUrl', + expect.stringMatching(new RegExp('^https://gerrithost.org/#/q/I')), + ); + }); + + it(`Should ${examples[1].description}`, async () => { + expect.assertions(3); + + const input = yaml.parse(examples[1].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(commitAndPushRepo).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('Initial Commit Message'), + gitAuthorInfo: { + email: undefined, + name: 'Test User', + }, + branch: 'master', + remoteRef: 'refs/for/master', + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/master', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'reviewUrl', + expect.stringMatching(new RegExp('^https://gerrithost.org/#/q/I')), + ); + }); + + it(`Should ${examples[2].description}`, async () => { + expect.assertions(3); + + const input = yaml.parse(examples[2].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(commitAndPushRepo).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('Initial Commit Message'), + gitAuthorInfo: { + email: 'test.user@example.com', + name: 'Test User', + }, + branch: 'master', + remoteRef: 'refs/for/master', + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/master', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'reviewUrl', + expect.stringMatching(new RegExp('^https://gerrithost.org/#/q/I')), + ); + }); + + it(`Should ${examples[3].description}`, async () => { + expect.assertions(3); + + const input = yaml.parse(examples[3].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(commitAndPushRepo).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('Initial Commit Message'), + gitAuthorInfo: {}, + branch: 'develop', + remoteRef: 'refs/for/develop', + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/develop', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'reviewUrl', + expect.stringMatching(new RegExp('^https://gerrithost.org/#/q/I')), + ); + }); + + it(`Should ${examples[4].description}`, async () => { + expect.assertions(3); + + const input = yaml.parse(examples[4].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(commitAndPushRepo).toHaveBeenCalledWith({ + dir: `${mockContext.workspacePath}/src`, + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('Initial Commit Message'), + gitAuthorInfo: {}, + branch: 'master', + remoteRef: 'refs/for/master', + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/master', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'reviewUrl', + expect.stringMatching(new RegExp('^https://gerrithost.org/#/q/I')), + ); + }); + + it(`Should ${examples[5].description}`, async () => { + expect.assertions(3); + + const input = yaml.parse(examples[5].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(commitAndPushRepo).toHaveBeenCalledWith({ + dir: `${mockContext.workspacePath}/src`, + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('Initial Commit Message'), + gitAuthorInfo: { + email: 'test.user@example.com', + name: 'Test User', + }, + branch: 'develop', + remoteRef: 'refs/for/develop', + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/develop', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'reviewUrl', + expect.stringMatching(new RegExp('^https://gerrithost.org/#/q/I')), + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); +}); diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.examples.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.examples.ts new file mode 100644 index 0000000000..fc01fd1ce3 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.examples.ts @@ -0,0 +1,126 @@ +/* + * 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. + */ + +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Creates a new Gerrit review with minimal options', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit:review', + name: 'Publish new gerrit review', + input: { + repoUrl: 'gerrithost.org?repo=repo&owner=owner', + gitCommitMessage: 'Initial Commit Message', + }, + }, + ], + }), + }, + { + description: 'Creates a new Gerrit review with gitAuthorName', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit:review', + name: 'Publish new gerrit review', + input: { + repoUrl: 'gerrithost.org?repo=repo&owner=owner', + gitCommitMessage: 'Initial Commit Message', + gitAuthorName: 'Test User', + }, + }, + ], + }), + }, + { + description: 'Creates a new Gerrit review with gitAuthorEmail', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit:review', + name: 'Publish new gerrit review', + input: { + repoUrl: 'gerrithost.org?repo=repo&owner=owner', + gitCommitMessage: 'Initial Commit Message', + gitAuthorName: 'Test User', + gitAuthorEmail: 'test.user@example.com', + }, + }, + ], + }), + }, + { + description: 'Creates a new Gerrit review with custom branch', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit:review', + name: 'Publish new gerrit review', + input: { + repoUrl: 'gerrithost.org?repo=repo&owner=owner', + gitCommitMessage: 'Initial Commit Message', + branch: 'develop', + }, + }, + ], + }), + }, + { + description: 'Creates a new Gerrit review with custom sourcePath', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit:review', + name: 'Publish new gerrit review', + input: { + repoUrl: 'gerrithost.org?repo=repo&owner=owner', + gitCommitMessage: 'Initial Commit Message', + sourcePath: './src', + }, + }, + ], + }), + }, + { + description: 'Creates a new Gerrit review with all properties', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit:review', + name: 'Publish new gerrit review', + input: { + repoUrl: 'gerrithost.org?repo=repo&owner=owner', + gitCommitMessage: 'Initial Commit Message', + gitAuthorName: 'Test User', + gitAuthorEmail: 'test.user@example.com', + branch: 'develop', + sourcePath: './src', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts index bd8d0321ab..b4f844b08f 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts @@ -24,6 +24,7 @@ import { getRepoSourceDirectory, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; +import { examples } from './gerritReview.examples'; const generateGerritChangeId = (): string => { const changeId = crypto.randomBytes(20).toString('hex'); @@ -50,6 +51,7 @@ export function createPublishGerritReviewAction(options: { }>({ id: 'publish:gerrit:review', description: 'Creates a new Gerrit review.', + examples, schema: { input: { type: 'object', diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index ff23e39a9e..38f94193ea 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -60,7 +60,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", - "jest-date-mock": "^1.0.8" + "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" } } diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts index 7298a502b6..feea436a1e 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts @@ -18,7 +18,6 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- import { createGitlabIssueAction, IssueType } from './createGitlabIssueAction'; import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; -import { advanceTo, clear } from 'jest-date-mock'; const mockGitlabClient = { Issues: { @@ -36,11 +35,13 @@ jest.mock('@gitbeaker/rest', () => ({ describe('gitlab:issues:create', () => { beforeEach(() => { jest.clearAllMocks(); - advanceTo(new Date(1988, 5, 3, 12, 0, 0)); // Set the desired date and time + jest.useFakeTimers({ + now: new Date(1988, 5, 3, 12, 0, 0), + }); }); afterEach(() => { - clear(); // Reset the date mock after each test + jest.useRealTimers(); }); const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts new file mode 100644 index 0000000000..dd3b694670 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.test.ts @@ -0,0 +1,179 @@ +/* + * 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. + */ + +import { createGitlabProjectDeployTokenAction } from './createGitlabProjectDeployTokenAction'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import yaml from 'yaml'; +import { examples } from './createGitlabProjectDeployTokenAction.examples'; + +const mockGitlabClient = { + ProjectDeployTokens: { + add: jest.fn(), + }, +}; +jest.mock('@gitbeaker/node', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:create-deploy-token', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + { + host: 'hosted.gitlab.com', + apiBaseUrl: 'https://api.hosted.gitlab.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createGitlabProjectDeployTokenAction({ integrations }); + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + name: 'tokenname', + username: 'tokenuser', + scopes: ['read_repository'], + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`Should ${examples[0].description}`, async () => { + mockGitlabClient.ProjectDeployTokens.add.mockResolvedValue({ + token: 'TOKEN', + username: 'User', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[0].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectDeployTokens.add).toHaveBeenCalledWith( + '456', + 'tokenname', + undefined, + { username: undefined }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('deploy_token', 'TOKEN'); + expect(mockContext.output).toHaveBeenCalledWith('user', 'User'); + }); + + it(`Should ${examples[1].description}`, async () => { + mockGitlabClient.ProjectDeployTokens.add.mockResolvedValue({ + token: 'TOKEN', + username: 'User', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[1].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectDeployTokens.add).toHaveBeenCalledWith( + '789', + 'tokenname', + ['read_registry', 'write_repository'], + { username: undefined }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('deploy_token', 'TOKEN'); + expect(mockContext.output).toHaveBeenCalledWith('user', 'User'); + }); + + it(`Should ${examples[2].description}`, async () => { + mockGitlabClient.ProjectDeployTokens.add.mockResolvedValue({ + token: 'TOKEN', + username: 'User', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[2].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectDeployTokens.add).toHaveBeenCalledWith( + '101112', + 'my-custom-token', + undefined, + { username: undefined }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('deploy_token', 'TOKEN'); + expect(mockContext.output).toHaveBeenCalledWith('user', 'User'); + }); + + it(`Should ${examples[3].description}`, async () => { + mockGitlabClient.ProjectDeployTokens.add.mockResolvedValue({ + token: 'TOKEN', + username: 'User', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[3].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectDeployTokens.add).toHaveBeenCalledWith( + 42, + 'tokenname', + undefined, + { username: undefined }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('deploy_token', 'TOKEN'); + expect(mockContext.output).toHaveBeenCalledWith('user', 'User'); + }); + + it(`Should ${examples[4].description}`, async () => { + mockGitlabClient.ProjectDeployTokens.add.mockResolvedValue({ + token: 'TOKEN', + username: 'User', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[4].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectDeployTokens.add).toHaveBeenCalledWith( + 42, + 'tokenname', + undefined, + { username: 'tokenuser' }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('deploy_token', 'TOKEN'); + expect(mockContext.output).toHaveBeenCalledWith('user', 'User'); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts new file mode 100644 index 0000000000..326747d45a --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.examples.ts @@ -0,0 +1,109 @@ +/* + * 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. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Create a GitLab project deploy token with minimal options.', + example: yaml.stringify({ + steps: [ + { + id: 'createDeployToken', + action: 'gitlab:projectDeployToken:create', + name: 'Create GitLab Project Deploy Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '456', + name: 'tokenname', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project deploy token with custom scopes.', + example: yaml.stringify({ + steps: [ + { + id: 'createDeployToken', + action: 'gitlab:projectDeployToken:create', + name: 'Create GitLab Project Deploy Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '789', + name: 'tokenname', + scopes: ['read_registry', 'write_repository'], + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project deploy token with a specified name.', + example: yaml.stringify({ + steps: [ + { + id: 'createDeployToken', + action: 'gitlab:projectDeployToken:create', + name: 'Create GitLab Project Deploy Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + name: 'my-custom-token', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project deploy token with a numeric project ID.', + example: yaml.stringify({ + steps: [ + { + id: 'createDeployToken', + action: 'gitlab:projectDeployToken:create', + name: 'Create GitLab Project Deploy Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 42, + name: 'tokenname', + }, + }, + ], + }), + }, + + { + description: 'Create a GitLab project deploy token with a custom username', + example: yaml.stringify({ + steps: [ + { + id: 'createDeployToken', + action: 'gitlab:projectDeployToken:create', + name: 'Create GitLab Project Deploy Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 42, + name: 'tokenname', + username: 'tokenuser', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts index 1665d23b39..87d4314b6f 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectDeployTokenAction.ts @@ -22,6 +22,7 @@ import commonGitlabConfig from '../commonGitlabConfig'; import { getToken } from '../util'; import { InputError } from '@backstage/errors'; import { z } from 'zod'; +import { examples } from './createGitlabProjectDeployTokenAction.examples'; /** * Creates a `gitlab:projectDeployToken:create` Scaffolder action. @@ -35,6 +36,7 @@ export const createGitlabProjectDeployTokenAction = (options: { const { integrations } = options; return createTemplateAction({ id: 'gitlab:projectDeployToken:create', + examples, schema: { input: commonGitlabConfig.merge( z.object({ diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 1f82a64962..92de03b93d 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -45,6 +45,7 @@ "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", "@backstage/types": "workspace:^", "winston": "^3.2.1", + "yaml": "^2.0.0", "yeoman-environment": "^3.9.1" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts new file mode 100644 index 0000000000..4875089090 --- /dev/null +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.test.ts @@ -0,0 +1,115 @@ +/* + * 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. + */ + +import { yeomanRun } from './yeomanRun'; + +jest.mock('./yeomanRun'); + +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import os from 'os'; +import { createRunYeomanAction } from './yeoman'; +import type { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { JsonObject } from '@backstage/types'; +import yaml from 'yaml'; +import { examples } from './yeoman.examples'; + +describe('run:yeoman', () => { + const mockTmpDir = os.tmpdir(); + + let mockContext: ActionContext<{ + namespace: string; + args?: string[]; + options?: JsonObject; + }>; + + const action = createRunYeomanAction(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`should ${examples[0].description}`, async () => { + const namespace = yaml.parse(examples[0].example).steps[0].input.namespace; + const input = yaml.parse(examples[0].example).steps[0].input; + mockContext = createMockActionContext({ + input: input, + workspacePath: mockTmpDir, + }); + + await action.handler(mockContext); + expect(yeomanRun).toHaveBeenCalledWith( + mockTmpDir, + namespace, + undefined, + undefined, + ); + }); + + it(`should ${examples[1].description}`, async () => { + const namespace = yaml.parse(examples[1].example).steps[0].input.namespace; + const input = yaml.parse(examples[1].example).steps[0].input; + const args: string[] = yaml.parse(examples[1].example).steps[0].input.args; + mockContext = createMockActionContext({ + input: input, + workspacePath: mockTmpDir, + }); + + await action.handler(mockContext); + expect(yeomanRun).toHaveBeenCalledWith( + mockTmpDir, + namespace, + args, + undefined, + ); + }); + + it(`should ${examples[2].description}`, async () => { + const namespace = yaml.parse(examples[2].example).steps[0].input.namespace; + const input = yaml.parse(examples[2].example).steps[0].input; + const options = yaml.parse(examples[2].example).steps[0].input.options; + mockContext = createMockActionContext({ + input: input, + workspacePath: mockTmpDir, + }); + + await action.handler(mockContext); + expect(yeomanRun).toHaveBeenCalledWith( + mockTmpDir, + namespace, + undefined, + options, + ); + }); + + it(`should ${examples[3].description}`, async () => { + const namespace = yaml.parse(examples[3].example).steps[0].input.namespace; + const input = yaml.parse(examples[3].example).steps[0].input; + const options = yaml.parse(examples[3].example).steps[0].input.options; + const args: string[] = yaml.parse(examples[1].example).steps[0].input.args; + mockContext = createMockActionContext({ + input: input, + workspacePath: mockTmpDir, + }); + + await action.handler(mockContext); + expect(yeomanRun).toHaveBeenCalledWith( + mockTmpDir, + namespace, + args, + options, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.ts new file mode 100644 index 0000000000..067cd71fab --- /dev/null +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.examples.ts @@ -0,0 +1,87 @@ +/* + * 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. + */ + +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Run a Yeoman generator with minimal options', + example: yaml.stringify({ + steps: [ + { + id: 'run:yeoman', + action: 'run:yeoman', + name: 'Running a yeoman generator', + input: { + namespace: 'node:app', + }, + }, + ], + }), + }, + { + description: + 'Run a yeoman generator with arguments to pass on to Yeoman for templating', + example: yaml.stringify({ + steps: [ + { + id: 'run:yeoman', + action: 'run:yeoman', + name: 'Running a yeoman generator', + input: { + namespace: 'node:app', + args: ['arg1', 'arg2'], + }, + }, + ], + }), + }, + { + description: + 'Run a yeoman generator with options to pass on to Yeoman for templating', + example: yaml.stringify({ + steps: [ + { + id: 'run:yeoman', + action: 'run:yeoman', + name: 'Running a yeoman generator', + input: { + namespace: 'node:app', + options: { option1: 'value1', option2: 'value2' }, + }, + }, + ], + }), + }, + { + description: 'Run a yeoman generator with all options', + example: yaml.stringify({ + steps: [ + { + id: 'run:yeoman', + action: 'run:yeoman', + name: 'Running a yeoman generator', + input: { + namespace: 'node:app', + args: ['arg1', 'arg2'], + options: { option1: 'value1', option2: 'value2' }, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts index 62d62807ae..a8bbbf945d 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts @@ -17,6 +17,7 @@ import { JsonObject } from '@backstage/types'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { yeomanRun } from './yeomanRun'; +import { examples } from './yeoman.examples'; /** * Creates a `run:yeoman` Scaffolder action. @@ -35,6 +36,7 @@ export function createRunYeomanAction() { }>({ id: 'run:yeoman', description: 'Runs Yeoman on an installed Yeoman generator', + examples, schema: { input: { type: 'object', diff --git a/plugins/scaffolder-react/.eslintrc.js b/plugins/scaffolder-react/.eslintrc.js index e2a53a6ad2..e487f765b2 100644 --- a/plugins/scaffolder-react/.eslintrc.js +++ b/plugins/scaffolder-react/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx b/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx index 4c86ef0c62..7731cac3f8 100644 --- a/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx +++ b/plugins/scaffolder-react/src/next/components/Form/DescriptionFieldTemplate.tsx @@ -16,7 +16,8 @@ import React from 'react'; import { MarkdownContent } from '@backstage/core-components'; -import { makeStyles, Typography } from '@material-ui/core'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; import { DescriptionFieldProps, FormContextType, diff --git a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx index da233eeba0..1bb1656fd0 100644 --- a/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx +++ b/plugins/scaffolder-react/src/next/components/ScaffolderField/ScaffolderField.tsx @@ -16,7 +16,8 @@ import React, { PropsWithChildren, ReactElement } from 'react'; import { MarkdownContent } from '@backstage/core-components'; -import { FormControl, makeStyles } from '@material-ui/core'; +import FormControl from '@material-ui/core/FormControl'; +import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles(theme => ({ markdownDescription: { diff --git a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx index 9b6920108b..a5310a5806 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx @@ -15,16 +15,12 @@ */ import React from 'react'; import { ErrorListProps } from '@rjsf/utils'; -import { - List, - ListItem, - ListItemIcon, - ListItemText, - Paper, - Theme, - createStyles, - makeStyles, -} from '@material-ui/core'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Paper from '@material-ui/core/Paper'; +import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'; import ErrorIcon from '@material-ui/icons/Error'; const useStyles = makeStyles((_theme: Theme) => diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index d0ce60fd79..62effbd14a 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -15,14 +15,12 @@ */ import { useAnalytics, useApiHolder } from '@backstage/core-plugin-api'; import { JsonValue } from '@backstage/types'; -import { - Stepper as MuiStepper, - Step as MuiStep, - StepLabel as MuiStepLabel, - Button, - makeStyles, - LinearProgress, -} from '@material-ui/core'; +import MuiStepper from '@material-ui/core/Stepper'; +import MuiStep from '@material-ui/core/Step'; +import MuiStepLabel from '@material-ui/core/StepLabel'; +import Button from '@material-ui/core/Button'; +import LinearProgress from '@material-ui/core/LinearProgress'; +import { makeStyles } from '@material-ui/core/styles'; import { type IChangeEvent } from '@rjsf/core'; import { ErrorSchema } from '@rjsf/utils'; import React, { diff --git a/plugins/scaffolder-react/src/next/components/TaskSteps/StepIcon.tsx b/plugins/scaffolder-react/src/next/components/TaskSteps/StepIcon.tsx index 58f2eb9050..c397b09a52 100644 --- a/plugins/scaffolder-react/src/next/components/TaskSteps/StepIcon.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskSteps/StepIcon.tsx @@ -15,7 +15,9 @@ */ import React from 'react'; -import { CircularProgress, makeStyles, StepIconProps } from '@material-ui/core'; +import CircularProgress from '@material-ui/core/CircularProgress'; +import { StepIconProps } from '@material-ui/core/StepIcon'; +import { makeStyles } from '@material-ui/core/styles'; import RemoveCircleOutline from '@material-ui/icons/RemoveCircleOutline'; import PanoramaFishEyeIcon from '@material-ui/icons/PanoramaFishEye'; import classNames from 'classnames'; diff --git a/plugins/scaffolder-react/src/next/components/TaskSteps/StepTime.tsx b/plugins/scaffolder-react/src/next/components/TaskSteps/StepTime.tsx index 14c347fda8..ab236965c2 100644 --- a/plugins/scaffolder-react/src/next/components/TaskSteps/StepTime.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskSteps/StepTime.tsx @@ -17,7 +17,7 @@ import React, { useCallback, useState } from 'react'; import useInterval from 'react-use/esm/useInterval'; import { DateTime, Interval } from 'luxon'; import humanizeDuration from 'humanize-duration'; -import { Typography } from '@material-ui/core'; +import Typography from '@material-ui/core/Typography'; import { useMountEffect } from '@react-hookz/web'; export const StepTime = (props: { diff --git a/plugins/scaffolder-react/src/next/components/TaskSteps/TaskBorder.tsx b/plugins/scaffolder-react/src/next/components/TaskSteps/TaskBorder.tsx index d7a96e3864..a33cdad49e 100644 --- a/plugins/scaffolder-react/src/next/components/TaskSteps/TaskBorder.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskSteps/TaskBorder.tsx @@ -15,7 +15,8 @@ */ import React from 'react'; -import { LinearProgress, makeStyles } from '@material-ui/core'; +import LinearProgress from '@material-ui/core/LinearProgress'; +import { makeStyles } from '@material-ui/core/styles'; const useStyles = makeStyles(theme => ({ failed: { diff --git a/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx b/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx index d74d298778..b391e6fce8 100644 --- a/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx @@ -14,15 +14,13 @@ * limitations under the License. */ import React from 'react'; -import { - Stepper as MuiStepper, - Step as MuiStep, - StepButton as MuiStepButton, - StepLabel as MuiStepLabel, - StepIconProps, - Box, - Paper, -} from '@material-ui/core'; +import MuiStepper from '@material-ui/core/Stepper'; +import MuiStep from '@material-ui/core/Step'; +import MuiStepButton from '@material-ui/core/StepButton'; +import MuiStepLabel from '@material-ui/core/StepLabel'; +import { StepIconProps } from '@material-ui/core/StepIcon'; +import Box from '@material-ui/core/Box'; +import Paper from '@material-ui/core/Paper'; import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { StepIcon } from './StepIcon'; import { StepTime } from './StepTime'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx index 287d813ca7..5aa138340e 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import { CardHeader } from './CardHeader'; -import { ThemeProvider } from '@material-ui/core'; +import { ThemeProvider } from '@material-ui/core/styles'; import { lightTheme } from '@backstage/theme'; import { MockStorageApi, diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx index 1180c8f106..467d26ce76 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardHeader.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { Theme, makeStyles, useTheme } from '@material-ui/core'; +import { Theme, makeStyles, useTheme } from '@material-ui/core/styles'; import { ItemCardHeader } from '@backstage/core-components'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { FavoriteEntity } from '@backstage/plugin-catalog-react'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/CardLink.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/CardLink.tsx index 839d6c7ac1..263a1356b7 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/CardLink.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/CardLink.tsx @@ -16,7 +16,7 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; -import { makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; interface CardLinkProps { diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx index 2d8ff1e458..4fb2ca0e4a 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx @@ -22,18 +22,15 @@ import { getEntityRelations, } from '@backstage/plugin-catalog-react'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { - Box, - Card, - CardActions, - CardContent, - Chip, - Divider, - Button, - Grid, - makeStyles, - Theme, -} from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import Chip from '@material-ui/core/Chip'; +import Divider from '@material-ui/core/Divider'; +import Button from '@material-ui/core/Button'; +import Grid from '@material-ui/core/Grid'; +import { makeStyles, Theme } from '@material-ui/core/styles'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; import { CardHeader } from './CardHeader'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx index 9c85cacfc2..24f8a21109 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCategoryPicker/TemplateCategoryPicker.tsx @@ -17,18 +17,16 @@ import React from 'react'; import capitalize from 'lodash/capitalize'; import { Progress } from '@backstage/core-components'; -import { - Box, - Checkbox, - FormControlLabel, - TextField, - Typography, - makeStyles, -} from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Checkbox from '@material-ui/core/Checkbox'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import TextField from '@material-ui/core/TextField'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { Autocomplete } from '@material-ui/lab'; +import Autocomplete from '@material-ui/lab/Autocomplete'; import { useEntityTypeFilter } from '@backstage/plugin-catalog-react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx index 800ec67a16..a180ff0e8c 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateGroups/TemplateGroups.tsx @@ -21,7 +21,7 @@ import { TemplateEntityV1beta3, } from '@backstage/plugin-scaffolder-common'; import { Progress, Link } from '@backstage/core-components'; -import { Typography } from '@material-ui/core'; +import Typography from '@material-ui/core/Typography'; import { errorApiRef, IconComponent, useApi } from '@backstage/core-plugin-api'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; import { TemplateGroup } from '../TemplateGroup/TemplateGroup'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index d3811e5011..7d90623077 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -18,7 +18,8 @@ import { ScaffolderOutputText, ScaffolderTaskOutput, } from '@backstage/plugin-scaffolder-react'; -import { Box, Paper } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Paper from '@material-ui/core/Paper'; import React, { useEffect, useMemo, useState } from 'react'; import { LinkOutputs } from './LinkOutputs'; import { TextOutputs } from './TextOutputs'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx index 7877653197..59c6c6a49a 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/LinkOutputs.tsx @@ -15,7 +15,8 @@ */ import { IconComponent, useApp, useRouteRef } from '@backstage/core-plugin-api'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; -import { Button, makeStyles } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import LinkIcon from '@material-ui/icons/Link'; import { parseEntityRef } from '@backstage/catalog-model'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx index 74a5eabb8f..5da1dab476 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { IconComponent, useApp } from '@backstage/core-plugin-api'; -import { Button } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; import DescriptionIcon from '@material-ui/icons/Description'; import React from 'react'; import { ScaffolderTaskOutput } from '../../../api'; diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx index e16af5abb9..9afc18c438 100644 --- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx +++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.tsx @@ -22,7 +22,7 @@ import { Progress, } from '@backstage/core-components'; import { stringifyEntityRef } from '@backstage/catalog-model'; -import { makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; import { errorApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; import { useTemplateParameterSchema } from '../../hooks/useTemplateParameterSchema'; import { Stepper, type StepperProps } from '../Stepper/Stepper'; diff --git a/plugins/signals-backend/dev/index.ts b/plugins/signals-backend/dev/index.ts new file mode 100644 index 0000000000..39021a9fcd --- /dev/null +++ b/plugins/signals-backend/dev/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { signalService } from '@backstage/plugin-signals-node'; + +const signalDebug = createBackendPlugin({ + pluginId: 'signals-debug', + register(env) { + env.registerInit({ + deps: { + signals: signalService, + lifecycle: coreServices.lifecycle, + }, + async init({ signals, lifecycle }) { + let interval: NodeJS.Timeout | undefined; + lifecycle.addStartupHook(async () => { + interval = setInterval(async () => { + await signals.publish<{ date: string }>({ + channel: 'debug', + message: { date: new Date().toISOString() }, + recipients: { type: 'broadcast' }, + }); + }, 1000); + }); + + lifecycle.addShutdownHook(async () => { + if (interval) { + clearInterval(interval); + } + }); + }, + }); + }, +}); + +const backend = createBackend(); +backend.add(import('@backstage/plugin-events-backend/alpha')); +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); +backend.add(import('../src')); +backend.add(signalDebug); + +backend.start(); diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 24682829e8..a6ebb228e4 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -45,7 +45,11 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-defaults": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^", + "@backstage/plugin-events-backend": "workspace:^", "@types/supertest": "^2.0.8", "msw": "^1.0.0", "supertest": "^6.2.4" diff --git a/plugins/signals-backend/src/run.ts b/plugins/signals-backend/src/run.ts deleted file mode 100644 index d299ed23e9..0000000000 --- a/plugins/signals-backend/src/run.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - */ -import { getRootLogger } from '@backstage/backend-common'; -import yn from 'yn'; -import { startStandaloneServer } from './service/standaloneServer'; - -const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; -const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); -const logger = getRootLogger(); - -startStandaloneServer({ port, enableCors, logger }).catch(err => { - logger.error(err); - process.exit(1); -}); - -process.on('SIGINT', () => { - logger.info('CTRL+C pressed; exiting.'); - process.exit(0); -}); diff --git a/plugins/signals-backend/src/service/standaloneServer.ts b/plugins/signals-backend/src/service/standaloneServer.ts deleted file mode 100644 index efb7ecedee..0000000000 --- a/plugins/signals-backend/src/service/standaloneServer.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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. - */ -import { - createServiceBuilder, - HostDiscovery, - loadBackendConfig, -} from '@backstage/backend-common'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; -import { DefaultSignalsService } from '@backstage/plugin-signals-node'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { - EventParams, - EventsService, - EventsServiceSubscribeOptions, -} from '@backstage/plugin-events-node'; -import { - BackstageCredentials, - BackstageUserInfo, - UserInfoService, -} from '@backstage/backend-plugin-api'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'signals-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = HostDiscovery.fromConfig(config); - - const identity = DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }); - - const mockSubscribers: EventsServiceSubscribeOptions[] = []; - const events: EventsService = { - async publish(params: EventParams): Promise { - mockSubscribers.forEach(sub => sub.onEvent(params)); - }, - async subscribe(subscription: EventsServiceSubscribeOptions) { - mockSubscribers.push(subscription); - }, - }; - - const signals = DefaultSignalsService.create({ - events, - }); - - const userInfo: UserInfoService = { - async getUserInfo(_: BackstageCredentials): Promise { - return { - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }; - }, - }; - - const router = await createRouter({ - logger, - identity, - events, - discovery, - userInfo, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/signals', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - let server: Promise; - try { - server = service.start(); - - setInterval(() => { - signals.publish({ - recipients: { type: 'broadcast' }, - channel: 'test', - message: { hello: 'world' }, - }); - }, 5000); - } catch (err) { - logger.error(err); - process.exit(1); - } - return server; -} - -module.hot?.accept(); diff --git a/plugins/signals-react/.eslintrc.js b/plugins/signals-react/.eslintrc.js index e2a53a6ad2..e487f765b2 100644 --- a/plugins/signals-react/.eslintrc.js +++ b/plugins/signals-react/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/plugins/signals/dev/index.tsx b/plugins/signals/dev/index.tsx index 2b22424eee..629c820a8e 100644 --- a/plugins/signals/dev/index.tsx +++ b/plugins/signals/dev/index.tsx @@ -16,20 +16,33 @@ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; import { signalsPlugin } from '../src/plugin'; -import { Content, Header, Page } from '@backstage/core-components'; +import { CodeSnippet, Content, Header, Page } from '@backstage/core-components'; import Typography from '@material-ui/core/Typography'; +import { useSignal } from '@backstage/plugin-signals-react'; + +const SignalsDebugPage = () => { + const { lastSignal } = useSignal('debug'); + return ( + +
+ + Last signal: + + {lastSignal ? ( + + ) : ( + 'Not received' + )} + + + + ); +}; createDevApp() .registerPlugin(signalsPlugin) .addPage({ title: 'Debug', - element: ( - -
- - TODO - - - ), + element: , }) .render(); diff --git a/storybook/yarn.lock b/storybook/yarn.lock index c6f3d8f362..fff8bd444d 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -3250,13 +3250,13 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/ast@npm:1.11.6" +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: "@webassemblyjs/helper-numbers": 1.11.6 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 - checksum: 38ef1b526ca47c210f30975b06df2faf1a8170b1636ce239fc5738fc231ce28389dd61ecedd1bacfc03cbe95b16d1af848c805652080cb60982836eb4ed2c6cf + checksum: 31bcc64147236bd7b1b6d29d1f419c1f5845c785e1e42dc9e3f8ca2e05a029e9393a271b84f3a5bff2a32d35f51ff59e2181a6e5f953fe88576acd6750506202 languageName: node linkType: hard @@ -3274,10 +3274,10 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-buffer@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" - checksum: b14d0573bf680d22b2522e8a341ec451fddd645d1f9c6bd9012ccb7e587a2973b86ab7b89fe91e1c79939ba96095f503af04369a3b356c8023c13a5893221644 +"@webassemblyjs/helper-buffer@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.12.1" + checksum: c3ffb723024130308db608e86e2bdccd4868bbb62dffb0a9a1530606496f79c87f8565bd8e02805ce64912b71f1a70ee5fb00307258b0c082c3abf961d097eca languageName: node linkType: hard @@ -3299,15 +3299,15 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-wasm-section@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" +"@webassemblyjs/helper-wasm-section@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 - "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/ast": 1.12.1 + "@webassemblyjs/helper-buffer": 1.12.1 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 - "@webassemblyjs/wasm-gen": 1.11.6 - checksum: b2cf751bf4552b5b9999d27bbb7692d0aca75260140195cb58ea6374d7b9c2dc69b61e10b211a0e773f66209c3ddd612137ed66097e3684d7816f854997682e9 + "@webassemblyjs/wasm-gen": 1.12.1 + checksum: c19810cdd2c90ff574139b6d8c0dda254d42d168a9e5b3d353d1bc085f1d7164ccd1b3c05592a45a939c47f7e403dc8d03572bb686642f06a3d02932f6f0bc8f languageName: node linkType: hard @@ -3336,68 +3336,68 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-edit@npm:1.11.6" +"@webassemblyjs/wasm-edit@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 - "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/ast": 1.12.1 + "@webassemblyjs/helper-buffer": 1.12.1 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 - "@webassemblyjs/helper-wasm-section": 1.11.6 - "@webassemblyjs/wasm-gen": 1.11.6 - "@webassemblyjs/wasm-opt": 1.11.6 - "@webassemblyjs/wasm-parser": 1.11.6 - "@webassemblyjs/wast-printer": 1.11.6 - checksum: 29ce75870496d6fad864d815ebb072395a8a3a04dc9c3f4e1ffdc63fc5fa58b1f34304a1117296d8240054cfdbc38aca88e71fb51483cf29ffab0a61ef27b481 + "@webassemblyjs/helper-wasm-section": 1.12.1 + "@webassemblyjs/wasm-gen": 1.12.1 + "@webassemblyjs/wasm-opt": 1.12.1 + "@webassemblyjs/wasm-parser": 1.12.1 + "@webassemblyjs/wast-printer": 1.12.1 + checksum: ae23642303f030af888d30c4ef37b08dfec7eab6851a9575a616e65d1219f880d9223913a39056dd654e49049d76e97555b285d1f7e56935047abf578cce0692 languageName: node linkType: hard -"@webassemblyjs/wasm-gen@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" +"@webassemblyjs/wasm-gen@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/ast": 1.12.1 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 "@webassemblyjs/ieee754": 1.11.6 "@webassemblyjs/leb128": 1.11.6 "@webassemblyjs/utf8": 1.11.6 - checksum: a645a2eecbea24833c3260a249704a7f554ef4a94c6000984728e94bb2bc9140a68dfd6fd21d5e0bbb09f6dfc98e083a45760a83ae0417b41a0196ff6d45a23a + checksum: 5787626bb7f0b033044471ddd00ce0c9fe1ee4584e8b73e232051e3a4c99ba1a102700d75337151c8b6055bae77eefa4548960c610a5e4a504e356bd872138ff languageName: node linkType: hard -"@webassemblyjs/wasm-opt@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" +"@webassemblyjs/wasm-opt@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 - "@webassemblyjs/helper-buffer": 1.11.6 - "@webassemblyjs/wasm-gen": 1.11.6 - "@webassemblyjs/wasm-parser": 1.11.6 - checksum: b4557f195487f8e97336ddf79f7bef40d788239169aac707f6eaa2fa5fe243557c2d74e550a8e57f2788e70c7ae4e7d32f7be16101afe183d597b747a3bdd528 + "@webassemblyjs/ast": 1.12.1 + "@webassemblyjs/helper-buffer": 1.12.1 + "@webassemblyjs/wasm-gen": 1.12.1 + "@webassemblyjs/wasm-parser": 1.12.1 + checksum: 0e8fa8a0645304a1e18ff40d3db5a2e9233ebaa169b19fcc651d6fc9fe2cac0ce092ddee927318015ae735d9cd9c5d97c0cafb6a51dcd2932ac73587b62df991 languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/ast": 1.12.1 "@webassemblyjs/helper-api-error": 1.11.6 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 "@webassemblyjs/ieee754": 1.11.6 "@webassemblyjs/leb128": 1.11.6 "@webassemblyjs/utf8": 1.11.6 - checksum: 8200a8d77c15621724a23fdabe58d5571415cda98a7058f542e670ea965dd75499f5e34a48675184947c66f3df23adf55df060312e6d72d57908e3f049620d8a + checksum: 176015de3551ac068cd4505d837414f258d9ade7442bd71efb1232fa26c9f6d7d4e11a5c816caeed389943f409af7ebff6899289a992d7a70343cb47009d21a8 languageName: node linkType: hard -"@webassemblyjs/wast-printer@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wast-printer@npm:1.11.6" +"@webassemblyjs/wast-printer@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wast-printer@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/ast": 1.12.1 "@xtuc/long": 4.2.2 - checksum: d2fa6a4c427325ec81463e9c809aa6572af6d47f619f3091bf4c4a6fc34f1da3df7caddaac50b8e7a457f8784c62cd58c6311b6cb69b0162ccd8d4c072f79cf8 + checksum: 2974b5dda8d769145ba0efd886ea94a601e61fb37114c14f9a9a7606afc23456799af652ac3052f284909bd42edc3665a76bc9b50f95f0794c053a8a1757b713 languageName: node linkType: hard @@ -5357,13 +5357,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0": - version: 5.15.0 - resolution: "enhanced-resolve@npm:5.15.0" +"enhanced-resolve@npm:^5.16.0": + version: 5.16.0 + resolution: "enhanced-resolve@npm:5.16.0" dependencies: graceful-fs: ^4.2.4 tapable: ^2.2.0 - checksum: fbd8cdc9263be71cc737aa8a7d6c57b43d6aa38f6cc75dde6fcd3598a130cc465f979d2f4d01bb3bf475acb43817749c79f8eef9be048683602ca91ab52e4f11 + checksum: ccfd01850ecf2aa51e8554d539973319ff7d8a539ef1e0ba3460a0ccad6223c4ef6e19165ee64161b459cd8a48df10f52af4434c60023c65fde6afa32d475f7e languageName: node linkType: hard @@ -6387,10 +6387,10 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 languageName: node linkType: hard @@ -11635,13 +11635,13 @@ __metadata: languageName: node linkType: hard -"watchpack@npm:^2.2.0, watchpack@npm:^2.4.0": - version: 2.4.0 - resolution: "watchpack@npm:2.4.0" +"watchpack@npm:^2.2.0, watchpack@npm:^2.4.1": + version: 2.4.1 + resolution: "watchpack@npm:2.4.1" dependencies: glob-to-regexp: ^0.4.1 graceful-fs: ^4.1.2 - checksum: 23d4bc58634dbe13b86093e01c6a68d8096028b664ab7139d58f0c37d962d549a940e98f2f201cecdabd6f9c340338dc73ef8bf094a2249ef582f35183d1a131 + checksum: 5b0179348655dcdf19cac7cb4ff923fdc024d630650c0bf6bec8899cf47c60e19d4f810a88dba692ed0e7f684cf0fcffea86efdbf6c35d81f031e328043b7fab languageName: node linkType: hard @@ -11754,24 +11754,24 @@ __metadata: linkType: hard "webpack@npm:^5.73.0": - version: 5.90.3 - resolution: "webpack@npm:5.90.3" + version: 5.91.0 + resolution: "webpack@npm:5.91.0" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^1.0.5 - "@webassemblyjs/ast": ^1.11.5 - "@webassemblyjs/wasm-edit": ^1.11.5 - "@webassemblyjs/wasm-parser": ^1.11.5 + "@webassemblyjs/ast": ^1.12.1 + "@webassemblyjs/wasm-edit": ^1.12.1 + "@webassemblyjs/wasm-parser": ^1.12.1 acorn: ^8.7.1 acorn-import-assertions: ^1.9.0 browserslist: ^4.21.10 chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.15.0 + enhanced-resolve: ^5.16.0 es-module-lexer: ^1.2.1 eslint-scope: 5.1.1 events: ^3.2.0 glob-to-regexp: ^0.4.1 - graceful-fs: ^4.2.9 + graceful-fs: ^4.2.11 json-parse-even-better-errors: ^2.3.1 loader-runner: ^4.2.0 mime-types: ^2.1.27 @@ -11779,14 +11779,14 @@ __metadata: schema-utils: ^3.2.0 tapable: ^2.1.1 terser-webpack-plugin: ^5.3.10 - watchpack: ^2.4.0 + watchpack: ^2.4.1 webpack-sources: ^3.2.3 peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: de0c824ac220f41cc1153ac33e081d46260b104c4f2fda26f011cdf7a73f74cc091f288cb1fc16f88a36e35bac44e0aa85fc9922fdf3109dfb361f46b20f3fcc + checksum: f1073715dbb1ed5c070affef293d800a867708bcbc5aba4d8baee87660e0cf53c55966a6f36fab078d1d6c9567cdcd0a9086bdfb607cab87ea68c6449791b9a3 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index ecb750cb65..4489b5c214 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3192,12 +3192,12 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.8, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.23.8 - resolution: "@babel/runtime@npm:7.23.8" +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.24.4 + resolution: "@babel/runtime@npm:7.24.4" dependencies: regenerator-runtime: ^0.14.0 - checksum: 0bd5543c26811153822a9f382fd39886f66825ff2a397a19008011376533747cd05c33a91f6248c0b8b0edf0448d7c167ebfba34786088f1b7eb11c65be7dfc3 + checksum: 2f27d4c0ffac7ae7999ac0385e1106f2a06992a8bdcbf3da06adcac7413863cd08c198c2e4e970041bbea849e17f02e1df18875539b6afba76c781b6b59a07c3 languageName: node linkType: hard @@ -4680,10 +4680,12 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@material-ui/core": ^4.12.2 + "@material-ui/lab": ^4.0.0-alpha.61 "@testing-library/dom": ^9.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^15.0.0 "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + react-use: ^17.5.0 use-deep-compare-effect: ^1.8.1 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -5487,6 +5489,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" cross-fetch: ^4.0.0 + lodash: ^4.17.21 openapi-merge: ^1.3.2 openapi3-ts: ^3.1.2 uuid: ^9.0.0 @@ -7877,6 +7880,7 @@ __metadata: resolution: "@backstage/plugin-notifications-backend@workspace:plugins/notifications-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-client": "workspace:^" @@ -7884,10 +7888,14 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-notifications-common": "workspace:^" "@backstage/plugin-notifications-node": "workspace:^" + "@backstage/plugin-signals-backend": "workspace:^" "@backstage/plugin-signals-node": "workspace:^" "@types/express": ^4.17.6 "@types/supertest": ^2.0.8 @@ -8635,7 +8643,6 @@ __metadata: "@gitbeaker/core": ^35.8.0 "@gitbeaker/node": ^35.8.0 "@gitbeaker/rest": ^39.25.0 - jest-date-mock: ^1.0.8 luxon: ^3.0.0 yaml: ^2.0.0 zod: ^3.22.4 @@ -8693,6 +8700,7 @@ __metadata: "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" winston: ^3.2.1 + yaml: ^2.0.0 yeoman-environment: ^3.9.1 languageName: unknown linkType: soft @@ -9245,10 +9253,14 @@ __metadata: resolution: "@backstage/plugin-signals-backend@workspace:plugins/signals-backend" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/plugin-auth-backend": "workspace:^" + "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" + "@backstage/plugin-events-backend": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@backstage/plugin-signals-node": "workspace:^" "@backstage/types": "workspace:^" @@ -10583,13 +10595,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0": - version: 6.26.2 - resolution: "@codemirror/view@npm:6.26.2" + version: 6.26.3 + resolution: "@codemirror/view@npm:6.26.3" dependencies: "@codemirror/state": ^6.4.0 style-mod: ^4.1.0 w3c-keyname: ^2.2.4 - checksum: d996983362cce28e19094b9b9caccc4c136eb6bc42d673636d81c77064995ce93c5241af35e9236ef84fd8b4289c253c01280ce6f570a82ddc2432aebe458033 + checksum: fdee35fb5e0bbba7b6f1a9b43a865880911bbfafd30360da5dda21b35f81ba2d080ff66b6c3d94dbe946b6b7ec98a76208786360b8f030ef10bcb054b816de05 languageName: node linkType: hard @@ -10691,7 +10703,7 @@ __metadata: languageName: node linkType: hard -"@emotion/babel-plugin@npm:^11.11.0": +"@emotion/babel-plugin@npm:^11.10.5, @emotion/babel-plugin@npm:^11.11.0": version: 11.11.0 resolution: "@emotion/babel-plugin@npm:11.11.0" dependencies: @@ -10710,7 +10722,7 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.11.0": +"@emotion/cache@npm:^11.10.5, @emotion/cache@npm:^11.11.0": version: 11.11.0 resolution: "@emotion/cache@npm:11.11.0" dependencies: @@ -10746,7 +10758,7 @@ __metadata: languageName: node linkType: hard -"@emotion/is-prop-valid@npm:^1.1.0, @emotion/is-prop-valid@npm:^1.2.2": +"@emotion/is-prop-valid@npm:^1.1.0, @emotion/is-prop-valid@npm:^1.2.0, @emotion/is-prop-valid@npm:^1.2.2": version: 1.2.2 resolution: "@emotion/is-prop-valid@npm:1.2.2" dependencies: @@ -10769,6 +10781,30 @@ __metadata: languageName: node linkType: hard +"@emotion/react@npm:11.10.5": + version: 11.10.5 + resolution: "@emotion/react@npm:11.10.5" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.10.5 + "@emotion/cache": ^11.10.5 + "@emotion/serialize": ^1.1.1 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.0 + "@emotion/utils": ^1.2.0 + "@emotion/weak-memoize": ^0.3.0 + hoist-non-react-statics: ^3.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@babel/core": + optional: true + "@types/react": + optional: true + checksum: 32b67b28e9b6d6c53b970072680697f04c2521441050bdeb19a1a7f0164af549b4dad39ff375eda1b6a3cf1cc86ba2c6fa55460ec040e6ebbca3e9ec58353cf7 + languageName: node + linkType: hard + "@emotion/react@npm:^11.10.5": version: 11.11.4 resolution: "@emotion/react@npm:11.11.4" @@ -10790,7 +10826,7 @@ __metadata: languageName: node linkType: hard -"@emotion/serialize@npm:^1.1.2, @emotion/serialize@npm:^1.1.3, @emotion/serialize@npm:^1.1.4": +"@emotion/serialize@npm:^1.1.1, @emotion/serialize@npm:^1.1.2, @emotion/serialize@npm:^1.1.3, @emotion/serialize@npm:^1.1.4": version: 1.1.4 resolution: "@emotion/serialize@npm:1.1.4" dependencies: @@ -10810,6 +10846,29 @@ __metadata: languageName: node linkType: hard +"@emotion/styled@npm:11.10.5": + version: 11.10.5 + resolution: "@emotion/styled@npm:11.10.5" + dependencies: + "@babel/runtime": ^7.18.3 + "@emotion/babel-plugin": ^11.10.5 + "@emotion/is-prop-valid": ^1.2.0 + "@emotion/serialize": ^1.1.1 + "@emotion/use-insertion-effect-with-fallbacks": ^1.0.0 + "@emotion/utils": ^1.2.0 + peerDependencies: + "@babel/core": ^7.0.0 + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@babel/core": + optional: true + "@types/react": + optional: true + checksum: 1cec5f6aeb227a7255141031e8594f38ad83902413472aae0a46c27e5f9769c01e23c1ad39adee408d8a2168a697464314d1a0c4f50b31a5d25ea506b2d7bbc8 + languageName: node + linkType: hard + "@emotion/styled@npm:^11.10.5": version: 11.11.5 resolution: "@emotion/styled@npm:11.11.5" @@ -10851,7 +10910,7 @@ __metadata: languageName: node linkType: hard -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.1": +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.0.0, @emotion/use-insertion-effect-with-fallbacks@npm:^1.0.1": version: 1.0.1 resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.0.1" peerDependencies: @@ -10860,14 +10919,14 @@ __metadata: languageName: node linkType: hard -"@emotion/utils@npm:^1.2.1": +"@emotion/utils@npm:^1.2.0, @emotion/utils@npm:^1.2.1": version: 1.2.1 resolution: "@emotion/utils@npm:1.2.1" checksum: e0b44be0705b56b079c55faff93952150be69e79b660ae70ddd5b6e09fc40eb1319654315a9f34bb479d7f4ec94be6068c061abbb9e18b9778ae180ad5d97c73 languageName: node linkType: hard -"@emotion/weak-memoize@npm:^0.3.1": +"@emotion/weak-memoize@npm:^0.3.0, @emotion/weak-memoize@npm:^0.3.1": version: 0.3.1 resolution: "@emotion/weak-memoize@npm:0.3.1" checksum: b2be47caa24a8122622ea18cd2d650dbb4f8ad37b636dc41ed420c2e082f7f1e564ecdea68122b546df7f305b159bf5ab9ffee872abd0f052e687428459af594 @@ -11258,56 +11317,6 @@ __metadata: languageName: node linkType: hard -"@f/animate@npm:^1.0.1": - version: 1.0.1 - resolution: "@f/animate@npm:1.0.1" - dependencies: - "@f/elapsed-time": ^1.0.0 - "@f/raf": ^1.0.0 - "@f/tween": ^1.0.0 - checksum: 660b44d297b1a6e5e28c48438633e00ded40f8e51adb4bfb26fc809b60c8c056f567f2e61f4f842c8fd2cf8a85dffaa1c2502bd0963eb926135f2d66d9f997db - languageName: node - linkType: hard - -"@f/elapsed-time@npm:^1.0.0": - version: 1.0.0 - resolution: "@f/elapsed-time@npm:1.0.0" - dependencies: - "@f/timestamp": ^1.0.0 - checksum: 5934d24a667bae4e0089408aa362c0ee1503945adf73ca0ff0d09fed33d62d070f0323eb9a54f42c37bd0545330a0f63da13319f66cc89fef59b8917a3278b5f - languageName: node - linkType: hard - -"@f/map-obj@npm:^1.2.2": - version: 1.2.2 - resolution: "@f/map-obj@npm:1.2.2" - checksum: d67db6074e1daa7a3c0f8c2e54094bff48b54d11817fddc886631dd8a17586d0040e7116282a374cc1d3d643d05862d49a1062ea5b2d03b9069507c09ae888a4 - languageName: node - linkType: hard - -"@f/raf@npm:^1.0.0": - version: 1.0.3 - resolution: "@f/raf@npm:1.0.3" - checksum: 800344caf4c649c0bcdefd4febbf3f0eed96bb9b7e54b5bc6eebd7d781c9e6d98ba22fb39da41474080d97ac21916bd1e82051558b43dabcbbf410d2dd0899c1 - languageName: node - linkType: hard - -"@f/timestamp@npm:^1.0.0": - version: 1.0.0 - resolution: "@f/timestamp@npm:1.0.0" - checksum: 8c37acb40cf67952309a214be2ddfd5c4f6634a414efc47406e8d1c1624e74662f584f6b48f3382c742f256aea1537e730e74c6078e52be3a28d0624cc7306a0 - languageName: node - linkType: hard - -"@f/tween@npm:^1.0.0": - version: 1.0.1 - resolution: "@f/tween@npm:1.0.1" - dependencies: - "@f/map-obj": ^1.2.2 - checksum: e37155c43875becc9eca8425b8dc83e64a78fa327738b33fcb3946418369568aa77007ade39591991379187381da829702e5cb84729dd2a0bc3bb4b7c94e4f2b - languageName: node - linkType: hard - "@faker-js/faker@npm:5.5.3": version: 5.5.3 resolution: "@faker-js/faker@npm:5.5.3" @@ -11315,38 +11324,38 @@ __metadata: languageName: node linkType: hard -"@floating-ui/core@npm:^1.5.3": - version: 1.5.3 - resolution: "@floating-ui/core@npm:1.5.3" +"@floating-ui/core@npm:^1.0.0": + version: 1.6.0 + resolution: "@floating-ui/core@npm:1.6.0" dependencies: - "@floating-ui/utils": ^0.2.0 - checksum: 72af8563e1742791acee82e86f82a0fbca7445809988d31eea3fd5771909463aa7655a6cb001cc244f8fe3a9de600420257e4dfb887ca33e2a31ac47b52e39a2 + "@floating-ui/utils": ^0.2.1 + checksum: 2e25c53b0c124c5c9577972f8ae21d081f2f7895e6695836a53074463e8c65b47722744d6d2b5a993164936da006a268bcfe87fe68fd24dc235b1cb86bed3127 languageName: node linkType: hard -"@floating-ui/dom@npm:^1.5.4": - version: 1.5.4 - resolution: "@floating-ui/dom@npm:1.5.4" +"@floating-ui/dom@npm:^1.6.1": + version: 1.6.3 + resolution: "@floating-ui/dom@npm:1.6.3" dependencies: - "@floating-ui/core": ^1.5.3 + "@floating-ui/core": ^1.0.0 "@floating-ui/utils": ^0.2.0 - checksum: 5e6f05532ff4e6daf9f2d91534184d8f942ddb8fd260c2543a49bdf0c0ff69fd0867937ce1d023126008724ac238f8fc89b5e48f82cdf9f8355a1d04edd085bd + checksum: 81cbb18ece3afc37992f436e469e7fabab2e433248e46fff4302d12493a175b0c64310f8a971e6e1eda7218df28ace6b70237b0f3c22fe12a21bba05b5579555 languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.5": - version: 2.0.6 - resolution: "@floating-ui/react-dom@npm:2.0.6" +"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.8": + version: 2.0.8 + resolution: "@floating-ui/react-dom@npm:2.0.8" dependencies: - "@floating-ui/dom": ^1.5.4 + "@floating-ui/dom": ^1.6.1 peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 3608537be6cae5f0442d3f826379b8e4a9ce5c4bdecf1d2b34e6709842d80444be1a00eca3641d680e2e6405d833092f58005d1b120a9d39ffd341c60b0c017c + checksum: 5da7f13a69281e38859a3203a608fe9de1d850b332b355c10c0c2427c7b7209a0374c10f6295b6577c1a70237af8b678340bd4cc0a4b1c66436a94755d81e526 languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0": +"@floating-ui/utils@npm:^0.2.0, @floating-ui/utils@npm:^0.2.1": version: 0.2.1 resolution: "@floating-ui/utils@npm:0.2.1" checksum: 9ed4380653c7c217cd6f66ae51f20fdce433730dbc77f95b5abfb5a808f5fdb029c6ae249b4e0490a816f2453aa6e586d9a873cd157fdba4690f65628efc6e06 @@ -12656,44 +12665,6 @@ __metadata: languageName: node linkType: hard -"@material-ui/core@npm:^3.9.3": - version: 3.9.4 - resolution: "@material-ui/core@npm:3.9.4" - dependencies: - "@babel/runtime": ^7.2.0 - "@material-ui/system": ^3.0.0-alpha.0 - "@material-ui/utils": ^3.0.0-alpha.2 - "@types/jss": ^9.5.6 - "@types/react-transition-group": ^2.0.8 - brcast: ^3.0.1 - classnames: ^2.2.5 - csstype: ^2.5.2 - debounce: ^1.1.0 - deepmerge: ^3.0.0 - dom-helpers: ^3.2.1 - hoist-non-react-statics: ^3.2.1 - is-plain-object: ^2.0.4 - jss: ^9.8.7 - jss-camel-case: ^6.0.0 - jss-default-unit: ^8.0.2 - jss-global: ^3.0.0 - jss-nested: ^6.0.1 - jss-props-sort: ^6.0.0 - jss-vendor-prefixer: ^7.0.0 - normalize-scroll-left: ^0.1.2 - popper.js: ^1.14.1 - prop-types: ^15.6.0 - react-event-listener: ^0.6.2 - react-transition-group: ^2.2.1 - recompose: 0.28.0 - 0.30.0 - warning: ^4.0.1 - peerDependencies: - react: ^16.3.0 - react-dom: ^16.3.0 - checksum: c3923bfba4ef4449fbdcd37712ec0d11393ffa396c575a05b5f9530ca1e714028882d39402eb6918dad49f2eb1c53b261cfd0f2dfed494c2dfd96ee335336b44 - languageName: node - linkType: hard - "@material-ui/core@npm:^4.12.2, @material-ui/core@npm:^4.12.4, @material-ui/core@npm:^4.9.13": version: 4.12.4 resolution: "@material-ui/core@npm:4.12.4" @@ -12872,21 +12843,6 @@ __metadata: languageName: node linkType: hard -"@material-ui/system@npm:^3.0.0-alpha.0": - version: 3.0.0-alpha.2 - resolution: "@material-ui/system@npm:3.0.0-alpha.2" - dependencies: - "@babel/runtime": ^7.2.0 - deepmerge: ^3.0.0 - prop-types: ^15.6.0 - warning: ^4.0.1 - peerDependencies: - react: ^16.3.0 - react-dom: ^16.3.0 - checksum: 3aacb8b4afe2a820844b90905cfcd86ab934108cd719f11713ee46f8a4da30cb8c50e86219e1440b3c6dd333a09d4123cd1990d9c51862c1d56ec2b36e4d7ded - languageName: node - linkType: hard - "@material-ui/system@npm:^4.12.2": version: 4.12.2 resolution: "@material-ui/system@npm:4.12.2" @@ -12930,20 +12886,6 @@ __metadata: languageName: node linkType: hard -"@material-ui/utils@npm:^3.0.0-alpha.2": - version: 3.0.0-alpha.3 - resolution: "@material-ui/utils@npm:3.0.0-alpha.3" - dependencies: - "@babel/runtime": ^7.2.0 - prop-types: ^15.6.0 - react-is: ^16.6.3 - peerDependencies: - react: ^16.3.0 - react-dom: ^16.3.0 - checksum: 589a16245338c374b604a793d9f7a05bd932c9710f78261e46176b74a61d5d308b3e36dab688988ee5177d689c53d9e9da4649eca009ea11d8aac948db27af50 - languageName: node - linkType: hard - "@material-ui/utils@npm:^4.11.2, @material-ui/utils@npm:^4.11.3, @material-ui/utils@npm:^4.7.1": version: 4.11.3 resolution: "@material-ui/utils@npm:4.11.3" @@ -13155,14 +13097,37 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-beta.32": - version: 5.0.0-beta.32 - resolution: "@mui/base@npm:5.0.0-beta.32" +"@mui/base@npm:5.0.0-alpha.112": + version: 5.0.0-alpha.112 + resolution: "@mui/base@npm:5.0.0-alpha.112" dependencies: - "@babel/runtime": ^7.23.8 - "@floating-ui/react-dom": ^2.0.5 - "@mui/types": ^7.2.13 - "@mui/utils": ^5.15.5 + "@babel/runtime": ^7.20.7 + "@emotion/is-prop-valid": ^1.2.0 + "@mui/types": ^7.2.3 + "@mui/utils": ^5.11.2 + "@popperjs/core": ^2.11.6 + clsx: ^1.2.1 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: fd19240ac16859d62f057fd80261fc4e0dbc960a648b601bd7beaeb44b88c059f654b0989f33cea655ad61106832bffa0cd8446730c1be7671b6cde383dbce80 + languageName: node + linkType: hard + +"@mui/base@npm:5.0.0-beta.40": + version: 5.0.0-beta.40 + resolution: "@mui/base@npm:5.0.0-beta.40" + dependencies: + "@babel/runtime": ^7.23.9 + "@floating-ui/react-dom": ^2.0.8 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 "@popperjs/core": ^2.11.8 clsx: ^2.1.0 prop-types: ^15.8.1 @@ -13173,30 +13138,76 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 5f27be8914c072ffcbe6720de9aa6129180e68927657e8bcbc03a6f322d1ee6c6740a199d72ed0b490a7b29b79cc0c59d1e05a427089b17f4cbc9cc756e67506 + checksum: 9c084ee67de372411a71af5eca9a5367db9f5bce57bb43973629c522760fe64fa2a43d2934dccd24d6dcbcd0ed399c5fc5c461226c86104f5767de1c9b8deba2 languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/core-downloads-tracker@npm:5.15.5" - checksum: 4c9b1281ebe8d17d402e22f7f50c347c0b3918b1ed17af721f4de5ce282d90bc6d90fe9730595998b2bbb2f7ebe57fc55d4c858f31754fccdb606af472a59dc8 +"@mui/core-downloads-tracker@npm:^5.11.2, @mui/core-downloads-tracker@npm:^5.15.15": + version: 5.15.15 + resolution: "@mui/core-downloads-tracker@npm:5.15.15" + checksum: 3e99a04e03f66d5fa5f0c23cdce0f9fa2331ba08c99a75dc2347ccaa1c6ed520153e04aaeb0d613c9dca099a3e6242558a6284c33d93f95cc65e3243b17860bc languageName: node linkType: hard -"@mui/material@npm:^5.12.2": - version: 5.15.5 - resolution: "@mui/material@npm:5.15.5" +"@mui/icons-material@npm:5.11.0": + version: 5.11.0 + resolution: "@mui/icons-material@npm:5.11.0" dependencies: - "@babel/runtime": ^7.23.8 - "@mui/base": 5.0.0-beta.32 - "@mui/core-downloads-tracker": ^5.15.5 - "@mui/system": ^5.15.5 - "@mui/types": ^7.2.13 - "@mui/utils": ^5.15.5 - "@types/react-transition-group": ^4.4.10 - clsx: ^2.1.0 - csstype: ^3.1.2 + "@babel/runtime": ^7.20.6 + peerDependencies: + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 764c1185b3432f0228f3c5217b0e218b10f106fa96d305dfc62c0ef5afd2a7a087b0d664fd0a8171282e195c18d3ee073d5f037901a2bed1a1519a70fbb0501c + languageName: node + linkType: hard + +"@mui/lab@npm:5.0.0-alpha.114": + version: 5.0.0-alpha.114 + resolution: "@mui/lab@npm:5.0.0-alpha.114" + dependencies: + "@babel/runtime": ^7.20.7 + "@mui/base": 5.0.0-alpha.112 + "@mui/system": ^5.11.2 + "@mui/types": ^7.2.3 + "@mui/utils": ^5.11.2 + clsx: ^1.2.1 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material": ^5.0.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 0b6441e7f7a69e2a6910f044ef2a222462a219f61e3a05c023484508e7f14a5fe6baeddf08b37bceab357c7cb0eed90324ac42ecd8e59a6eb6e1c801ea01504a + languageName: node + linkType: hard + +"@mui/material@npm:5.11.2": + version: 5.11.2 + resolution: "@mui/material@npm:5.11.2" + dependencies: + "@babel/runtime": ^7.20.7 + "@mui/base": 5.0.0-alpha.112 + "@mui/core-downloads-tracker": ^5.11.2 + "@mui/system": ^5.11.2 + "@mui/types": ^7.2.3 + "@mui/utils": ^5.11.2 + "@types/react-transition-group": ^4.4.5 + clsx: ^1.2.1 + csstype: ^3.1.1 prop-types: ^15.8.1 react-is: ^18.2.0 react-transition-group: ^4.4.5 @@ -13213,16 +13224,49 @@ __metadata: optional: true "@types/react": optional: true - checksum: dbfcb31810c674d9ab3b9145752433de3917d9c0d1b491bdff84c44b8f1124e8fe8ab04fa09b974b497983b7bd3011b86fb441ad365f979f971d3ddb46712060 + checksum: 4f2baa4b4d8f7842b081e9e919172020f7055e33c24af36c5a458eac1a76ff3e896651fdf21daa523a68ac0bfb3670864c1b4dec9fae6d8fadf2b602f14f95ea languageName: node linkType: hard -"@mui/private-theming@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/private-theming@npm:5.15.5" +"@mui/material@npm:^5.12.2": + version: 5.15.15 + resolution: "@mui/material@npm:5.15.15" dependencies: - "@babel/runtime": ^7.23.8 - "@mui/utils": ^5.15.5 + "@babel/runtime": ^7.23.9 + "@mui/base": 5.0.0-beta.40 + "@mui/core-downloads-tracker": ^5.15.15 + "@mui/system": ^5.15.15 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 + "@types/react-transition-group": ^4.4.10 + clsx: ^2.1.0 + csstype: ^3.1.3 + prop-types: ^15.8.1 + react-is: ^18.2.0 + react-transition-group: ^4.4.5 + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: ee0dc22fc4d617f7cf69f2451b6d5139978e6c5319e3056e7719159aff786ee3b80abd07691e230371811d9b5b574aef4559d7855bfe2f8493d596d960a91ab7 + languageName: node + linkType: hard + +"@mui/private-theming@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/private-theming@npm:5.15.14" + dependencies: + "@babel/runtime": ^7.23.9 + "@mui/utils": ^5.15.14 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -13230,17 +13274,17 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 3a5f7f190aa69a0ad69a34f77e54ee2fdcb088d21a61d4720b76d098d178e1c3f6a470fc5e7a30d351db10efbc062cb11af75b8789e2f38eaa2ca612bcb0f851 + checksum: 1b1ef54e8281c9b13fcc58f4c39682efc610946a68402283c19fcfbce8a7d7a231d61b536d6df9bf7a59a1426591bd403a453a59eb8efb9689437fb58554dc8c languageName: node linkType: hard -"@mui/styled-engine@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/styled-engine@npm:5.15.5" +"@mui/styled-engine@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/styled-engine@npm:5.15.14" dependencies: - "@babel/runtime": ^7.23.8 + "@babel/runtime": ^7.23.9 "@emotion/cache": ^11.11.0 - csstype: ^3.1.2 + csstype: ^3.1.3 prop-types: ^15.8.1 peerDependencies: "@emotion/react": ^11.4.1 @@ -13251,21 +13295,21 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 42bb7f50ed33ec88f799bd90a00337689837ee0b2d603681fe69c9a14850e5ae1d037505365ce78cd564307cc2c0654c46cece8d9250adb21c77b1de316e3c45 + checksum: 23b45c859a4f0d2b10933d06a6082c0ff093f7b6d8d32a2bfe3a6e515fe46d7a38ca9e7150d45c025a2e98d963bae9a5991d131cf4748b62670075ef0fa321ed languageName: node linkType: hard "@mui/styles@npm:^5.14.18": - version: 5.15.5 - resolution: "@mui/styles@npm:5.15.5" + version: 5.15.15 + resolution: "@mui/styles@npm:5.15.15" dependencies: - "@babel/runtime": ^7.23.8 + "@babel/runtime": ^7.23.9 "@emotion/hash": ^0.9.1 - "@mui/private-theming": ^5.15.5 - "@mui/types": ^7.2.13 - "@mui/utils": ^5.15.5 + "@mui/private-theming": ^5.15.14 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 clsx: ^2.1.0 - csstype: ^3.1.2 + csstype: ^3.1.3 hoist-non-react-statics: ^3.3.2 jss: ^10.10.0 jss-plugin-camel-case: ^10.10.0 @@ -13282,21 +13326,21 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: dc131c0e7763f7ac4fd96a1db3dd85abf59de5429a73f7cf9ed15d423fb9bb5883835c1951d36e8804da9ab2ed13d603ed349f12e7cd15885f8279d90d13511b + checksum: 5e436a8a22ae9a2e6fcb63ead3a6eff2092cc9731fce0b22d04ab9c67639a173a0e27fb617628e7720592862118a32248be458bedfabaf1de5448543a97b0307 languageName: node linkType: hard -"@mui/system@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/system@npm:5.15.5" +"@mui/system@npm:^5.11.2, @mui/system@npm:^5.15.15": + version: 5.15.15 + resolution: "@mui/system@npm:5.15.15" dependencies: - "@babel/runtime": ^7.23.8 - "@mui/private-theming": ^5.15.5 - "@mui/styled-engine": ^5.15.5 - "@mui/types": ^7.2.13 - "@mui/utils": ^5.15.5 + "@babel/runtime": ^7.23.9 + "@mui/private-theming": ^5.15.14 + "@mui/styled-engine": ^5.15.14 + "@mui/types": ^7.2.14 + "@mui/utils": ^5.15.14 clsx: ^2.1.0 - csstype: ^3.1.2 + csstype: ^3.1.3 prop-types: ^15.8.1 peerDependencies: "@emotion/react": ^11.5.0 @@ -13310,27 +13354,27 @@ __metadata: optional: true "@types/react": optional: true - checksum: 3f736f120d65fa14588cd7554a9ef63c9d4480716807d7ee5543b9f25d936b5ac0396a51a565c53b2905114fed5f94880c896f091e8d269260bfc53e8a8e2fb5 + checksum: 9ca96d5f66b2a9d6471909cc98c671eea5ec0a6d58a7ec071073b9e5200b95c3f017f0ca5cc946abc7f83074bd11830ca18f5e30bc98e25cd6ca217bd1b3a26f languageName: node linkType: hard -"@mui/types@npm:^7.2.13": - version: 7.2.13 - resolution: "@mui/types@npm:7.2.13" +"@mui/types@npm:^7.2.14, @mui/types@npm:^7.2.3": + version: 7.2.14 + resolution: "@mui/types@npm:7.2.14" peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 peerDependenciesMeta: "@types/react": optional: true - checksum: 58dfc96f9654288519ff01d6b54e6a242f05cadad51210deb85710a81be4fa1501a116c8968e2614b16c748fc1f407dc23beeeeae70fa37fceb6c6de876ff70d + checksum: 615c9f9110933157f5d3c4fee69d6e70b98fc0d9ebc3b63079b6a1e23e6b389748687a25ab4ac15b56166fc228885da87c3929503b41fa322cfdee0f6d411206 languageName: node linkType: hard -"@mui/utils@npm:^5.14.15, @mui/utils@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/utils@npm:5.15.5" +"@mui/utils@npm:^5.11.2, @mui/utils@npm:^5.14.15, @mui/utils@npm:^5.15.14": + version: 5.15.14 + resolution: "@mui/utils@npm:5.15.14" dependencies: - "@babel/runtime": ^7.23.8 + "@babel/runtime": ^7.23.9 "@types/prop-types": ^15.7.11 prop-types: ^15.8.1 react-is: ^18.2.0 @@ -13340,7 +13384,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: fe71ce69e23cb4c00886138da1422e4baa66959cd10f97b2aa00f7324a038b13084ea4a0da123774c0018f48ddb6ce4f3cb20a915ec3c5c1ff5495bf337f665f + checksum: 36543ba7e3b65fb3219ed27e8f1455aff15b47a74c9b642c63e60774e22baa6492a196079e72bcfa5a570421dab32160398f892110bd444428bcf8b266b11893 languageName: node linkType: hard @@ -14530,15 +14574,15 @@ __metadata: linkType: hard "@opensearch-project/opensearch@npm:^2.2.1": - version: 2.6.0 - resolution: "@opensearch-project/opensearch@npm:2.6.0" + version: 2.7.0 + resolution: "@opensearch-project/opensearch@npm:2.7.0" dependencies: aws4: ^1.11.0 debug: ^4.3.1 hpagent: ^1.2.0 ms: ^2.1.3 secure-json-parse: ^2.4.0 - checksum: d958bb83bdf4975f342da9d355aa39f3daaed322fa72fae715501cf760101e3ef50e3e216cab5b008df5f7d7976d5feee7541a6dd9d1f6791ffb01134fafa227 + checksum: 6905373048d3896e158de8a229b1110828cf0cbffee3180fcd98b114a025f11e8938124863106544df09d7aa587b41843e2818fe03f34e33a27abcd1601740fe languageName: node linkType: hard @@ -14639,13 +14683,13 @@ __metadata: linkType: hard "@playwright/test@npm:^1.32.3": - version: 1.43.0 - resolution: "@playwright/test@npm:1.43.0" + version: 1.43.1 + resolution: "@playwright/test@npm:1.43.1" dependencies: - playwright: 1.43.0 + playwright: 1.43.1 bin: playwright: cli.js - checksum: c7c0c6e8fde4e9dceea22d48384642b78c3d539fb0512edeaa581dc4dc07cedfd166dba92c5e9e69a378b7fd63362b142450dbdd5c9b65d76c4def162a5c470d + checksum: f9db387b488a03125e5dc22dd7ffed9c661d1f2428188912a35a2235b3bd9d826b390e7600d04998639994f5a96695b9dc9034ca9cb59e261d2fdee93a60df3f languageName: node linkType: hard @@ -14823,7 +14867,7 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.11.8": +"@popperjs/core@npm:^2.11.6, @popperjs/core@npm:^2.11.8": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" checksum: e5c69fdebf52a4012f6a1f14817ca8e9599cb1be73dd1387e1785e2ed5e5f0862ff817f420a87c7fc532add1f88a12e25aeb010ffcbdc98eace3d55ce2139cf0 @@ -15796,107 +15840,107 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.14.1" +"@rollup/rollup-android-arm-eabi@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.14.2" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-android-arm64@npm:4.14.1" +"@rollup/rollup-android-arm64@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-android-arm64@npm:4.14.2" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-darwin-arm64@npm:4.14.1" +"@rollup/rollup-darwin-arm64@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-darwin-arm64@npm:4.14.2" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-darwin-x64@npm:4.14.1" +"@rollup/rollup-darwin-x64@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-darwin-x64@npm:4.14.2" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.14.1" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.14.2" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.14.1" +"@rollup/rollup-linux-arm64-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.14.2" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.14.1" +"@rollup/rollup-linux-arm64-musl@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.14.2" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.14.1" - conditions: os=linux & cpu=ppc64le & libc=glibc +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.14.2" + conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.14.1" +"@rollup/rollup-linux-riscv64-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.14.2" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.14.1" +"@rollup/rollup-linux-s390x-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.14.2" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.14.1" +"@rollup/rollup-linux-x64-gnu@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.14.2" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.14.1" +"@rollup/rollup-linux-x64-musl@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.14.2" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.14.1" +"@rollup/rollup-win32-arm64-msvc@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.14.2" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.14.1" +"@rollup/rollup-win32-ia32-msvc@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.14.2" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.14.1": - version: 4.14.1 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.14.1" +"@rollup/rollup-win32-x64-msvc@npm:4.14.2": + version: 4.14.2 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.14.2" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -17826,11 +17870,11 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.5.0": - version: 0.5.8 - resolution: "@swc/helpers@npm:0.5.8" + version: 0.5.9 + resolution: "@swc/helpers@npm:0.5.9" dependencies: tslib: ^2.4.0 - checksum: 001bb4ef9c59083fd4325e295f800b6ef61b8a2109424060a7dcc73b3c01c6ecbbf90a53db05fddf33025c301d1350e425798dc6d4ffc685f9b1051ad3177dae + checksum: ef2f076560711adbc37157e796046c358dc5c91716ce2ddc8d1a6d4a71e0769e145b28d1d1c7689c57d1087509b40646af84e15d623cade7ab653ebf50270bda languageName: node linkType: hard @@ -18019,8 +18063,8 @@ __metadata: linkType: hard "@testing-library/react@npm:^15.0.0": - version: 15.0.1 - resolution: "@testing-library/react@npm:15.0.1" + version: 15.0.2 + resolution: "@testing-library/react@npm:15.0.2" dependencies: "@babel/runtime": ^7.12.5 "@testing-library/dom": ^10.0.0 @@ -18028,7 +18072,7 @@ __metadata: peerDependencies: react: ^18.0.0 react-dom: ^18.0.0 - checksum: 9992087563279bc975e5be69c0d380ecee84eb9e5c5e474b8b01b95804477ecae0978c61ded15821335d926fec058ab6c23442150584b0f19bee72c561ad19b7 + checksum: 32be030a5540164403f3cd8d6f46ad907119b2bc0a0df43ce6b925cdd520d5003298d06af1ee94d7bd764f294d74e802b6509caf2cf10afc77e67407baa9c1fb languageName: node linkType: hard @@ -19008,16 +19052,6 @@ __metadata: languageName: node linkType: hard -"@types/jss@npm:^9.5.6": - version: 9.5.8 - resolution: "@types/jss@npm:9.5.8" - dependencies: - csstype: ^2.0.0 - indefinite-observable: ^1.0.1 - checksum: 6e51707529a15f2f5ff94555ecb555d29427fd10c4f3d2d29474934292e365c2dfaa4ad30b2ab46946201cbca7f6e8df56513a22fc6ceca10a979db8338935c5 - languageName: node - linkType: hard - "@types/keyv@npm:*, @types/keyv@npm:^3.1.1": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -19516,16 +19550,7 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:^2.0.8": - version: 2.9.2 - resolution: "@types/react-transition-group@npm:2.9.2" - dependencies: - "@types/react": "*" - checksum: 6f30fffc221339de90bd3e999f32328618cfaedfcaa501603f5ddc5bed1c2dbaa0d51347c3a25e5f8fd3041c671aabd2b7bfbcad611a7636adcd9da4e0666fa5 - languageName: node - linkType: hard - -"@types/react-transition-group@npm:^4.2.0, @types/react-transition-group@npm:^4.4.10": +"@types/react-transition-group@npm:^4.2.0, @types/react-transition-group@npm:^4.4.10, @types/react-transition-group@npm:^4.4.5": version: 4.4.10 resolution: "@types/react-transition-group@npm:4.4.10" dependencies: @@ -20504,13 +20529,13 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/ast@npm:1.11.6" +"@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/ast@npm:1.12.1" dependencies: "@webassemblyjs/helper-numbers": 1.11.6 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 - checksum: 38ef1b526ca47c210f30975b06df2faf1a8170b1636ce239fc5738fc231ce28389dd61ecedd1bacfc03cbe95b16d1af848c805652080cb60982836eb4ed2c6cf + checksum: 31bcc64147236bd7b1b6d29d1f419c1f5845c785e1e42dc9e3f8ca2e05a029e9393a271b84f3a5bff2a32d35f51ff59e2181a6e5f953fe88576acd6750506202 languageName: node linkType: hard @@ -20528,10 +20553,10 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-buffer@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" - checksum: b14d0573bf680d22b2522e8a341ec451fddd645d1f9c6bd9012ccb7e587a2973b86ab7b89fe91e1c79939ba96095f503af04369a3b356c8023c13a5893221644 +"@webassemblyjs/helper-buffer@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.12.1" + checksum: c3ffb723024130308db608e86e2bdccd4868bbb62dffb0a9a1530606496f79c87f8565bd8e02805ce64912b71f1a70ee5fb00307258b0c082c3abf961d097eca languageName: node linkType: hard @@ -20553,15 +20578,15 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-wasm-section@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" +"@webassemblyjs/helper-wasm-section@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 - "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/ast": 1.12.1 + "@webassemblyjs/helper-buffer": 1.12.1 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 - "@webassemblyjs/wasm-gen": 1.11.6 - checksum: b2cf751bf4552b5b9999d27bbb7692d0aca75260140195cb58ea6374d7b9c2dc69b61e10b211a0e773f66209c3ddd612137ed66097e3684d7816f854997682e9 + "@webassemblyjs/wasm-gen": 1.12.1 + checksum: c19810cdd2c90ff574139b6d8c0dda254d42d168a9e5b3d353d1bc085f1d7164ccd1b3c05592a45a939c47f7e403dc8d03572bb686642f06a3d02932f6f0bc8f languageName: node linkType: hard @@ -20590,68 +20615,68 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-edit@npm:1.11.6" +"@webassemblyjs/wasm-edit@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 - "@webassemblyjs/helper-buffer": 1.11.6 + "@webassemblyjs/ast": 1.12.1 + "@webassemblyjs/helper-buffer": 1.12.1 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 - "@webassemblyjs/helper-wasm-section": 1.11.6 - "@webassemblyjs/wasm-gen": 1.11.6 - "@webassemblyjs/wasm-opt": 1.11.6 - "@webassemblyjs/wasm-parser": 1.11.6 - "@webassemblyjs/wast-printer": 1.11.6 - checksum: 29ce75870496d6fad864d815ebb072395a8a3a04dc9c3f4e1ffdc63fc5fa58b1f34304a1117296d8240054cfdbc38aca88e71fb51483cf29ffab0a61ef27b481 + "@webassemblyjs/helper-wasm-section": 1.12.1 + "@webassemblyjs/wasm-gen": 1.12.1 + "@webassemblyjs/wasm-opt": 1.12.1 + "@webassemblyjs/wasm-parser": 1.12.1 + "@webassemblyjs/wast-printer": 1.12.1 + checksum: ae23642303f030af888d30c4ef37b08dfec7eab6851a9575a616e65d1219f880d9223913a39056dd654e49049d76e97555b285d1f7e56935047abf578cce0692 languageName: node linkType: hard -"@webassemblyjs/wasm-gen@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" +"@webassemblyjs/wasm-gen@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/ast": 1.12.1 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 "@webassemblyjs/ieee754": 1.11.6 "@webassemblyjs/leb128": 1.11.6 "@webassemblyjs/utf8": 1.11.6 - checksum: a645a2eecbea24833c3260a249704a7f554ef4a94c6000984728e94bb2bc9140a68dfd6fd21d5e0bbb09f6dfc98e083a45760a83ae0417b41a0196ff6d45a23a + checksum: 5787626bb7f0b033044471ddd00ce0c9fe1ee4584e8b73e232051e3a4c99ba1a102700d75337151c8b6055bae77eefa4548960c610a5e4a504e356bd872138ff languageName: node linkType: hard -"@webassemblyjs/wasm-opt@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" +"@webassemblyjs/wasm-opt@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 - "@webassemblyjs/helper-buffer": 1.11.6 - "@webassemblyjs/wasm-gen": 1.11.6 - "@webassemblyjs/wasm-parser": 1.11.6 - checksum: b4557f195487f8e97336ddf79f7bef40d788239169aac707f6eaa2fa5fe243557c2d74e550a8e57f2788e70c7ae4e7d32f7be16101afe183d597b747a3bdd528 + "@webassemblyjs/ast": 1.12.1 + "@webassemblyjs/helper-buffer": 1.12.1 + "@webassemblyjs/wasm-gen": 1.12.1 + "@webassemblyjs/wasm-parser": 1.12.1 + checksum: 0e8fa8a0645304a1e18ff40d3db5a2e9233ebaa169b19fcc651d6fc9fe2cac0ce092ddee927318015ae735d9cd9c5d97c0cafb6a51dcd2932ac73587b62df991 languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": - version: 1.11.6 - resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" +"@webassemblyjs/wasm-parser@npm:1.12.1, @webassemblyjs/wasm-parser@npm:^1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/ast": 1.12.1 "@webassemblyjs/helper-api-error": 1.11.6 "@webassemblyjs/helper-wasm-bytecode": 1.11.6 "@webassemblyjs/ieee754": 1.11.6 "@webassemblyjs/leb128": 1.11.6 "@webassemblyjs/utf8": 1.11.6 - checksum: 8200a8d77c15621724a23fdabe58d5571415cda98a7058f542e670ea965dd75499f5e34a48675184947c66f3df23adf55df060312e6d72d57908e3f049620d8a + checksum: 176015de3551ac068cd4505d837414f258d9ade7442bd71efb1232fa26c9f6d7d4e11a5c816caeed389943f409af7ebff6899289a992d7a70343cb47009d21a8 languageName: node linkType: hard -"@webassemblyjs/wast-printer@npm:1.11.6": - version: 1.11.6 - resolution: "@webassemblyjs/wast-printer@npm:1.11.6" +"@webassemblyjs/wast-printer@npm:1.12.1": + version: 1.12.1 + resolution: "@webassemblyjs/wast-printer@npm:1.12.1" dependencies: - "@webassemblyjs/ast": 1.11.6 + "@webassemblyjs/ast": 1.12.1 "@xtuc/long": 4.2.2 - checksum: d2fa6a4c427325ec81463e9c809aa6572af6d47f619f3091bf4c4a6fc34f1da3df7caddaac50b8e7a457f8784c62cd58c6311b6cb69b0162ccd8d4c072f79cf8 + checksum: 2974b5dda8d769145ba0efd886ea94a601e61fb37114c14f9a9a7606afc23456799af652ac3052f284909bd42edc3665a76bc9b50f95f0794c053a8a1757b713 languageName: node linkType: hard @@ -22472,13 +22497,6 @@ __metadata: languageName: node linkType: hard -"brcast@npm:^3.0.1": - version: 3.0.2 - resolution: "brcast@npm:3.0.2" - checksum: 7abae42088c6ffad9ff9e0fc756607a1764e299d737b3007fa49a73a38b3fda1e51362713f645db7991878ca388de94942011188450324c3debd08509315fa94 - languageName: node - linkType: hard - "breakword@npm:^1.0.5": version: 1.0.5 resolution: "breakword@npm:1.0.5" @@ -23048,13 +23066,6 @@ __metadata: languageName: node linkType: hard -"change-emitter@npm:^0.1.2": - version: 0.1.6 - resolution: "change-emitter@npm:0.1.6" - checksum: 0ed494ba9901ca56ea6f942668fd294465c334a9a0981dca96da5aea5e387c0023a630d7c658c1b532d203db54c928ddca2564e434b4a8b7f6d39155d09db255 - languageName: node - linkType: hard - "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -23359,17 +23370,6 @@ __metadata: languageName: node linkType: hard -"clipboard@npm:^2.0.4": - version: 2.0.11 - resolution: "clipboard@npm:2.0.11" - dependencies: - good-listener: ^1.2.2 - select: ^1.1.2 - tiny-emitter: ^2.0.0 - checksum: 413055a6038e43898e0e895216b58ed54fbf386f091cb00188875ef35b186cefbd258acdf4cb4b0ac87cbc00de936f41b45dde9fe1fd1a57f7babb28363b8748 - languageName: node - linkType: hard - "cliui@npm:7.0.4, cliui@npm:^7.0.2": version: 7.0.4 resolution: "cliui@npm:7.0.4" @@ -23812,9 +23812,9 @@ __metadata: languageName: node linkType: hard -"commonmark@npm:^0.29.0": - version: 0.29.3 - resolution: "commonmark@npm:0.29.3" +"commonmark@npm:0.30.0": + version: 0.30.0 + resolution: "commonmark@npm:0.30.0" dependencies: entities: ~2.0 mdurl: ~1.0.1 @@ -23822,7 +23822,7 @@ __metadata: string.prototype.repeat: ^0.2.0 bin: commonmark: bin/commonmark - checksum: 892b603aeffea6a04770ce24979fc4024d2d92ca6d5146353398faab15be980af6802f3d7b35357400b870e752a9f80b766fed5c09ca063a2951c8c26013876c + checksum: 39f05d6d0f471aa345487d45199809233f29f857630780fde23661356f1f31970f67328934895d3062e01524674785981837c3b6f4303b63d8c9bff8fa99ec83 languageName: node linkType: hard @@ -24211,13 +24211,6 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^1.0.0": - version: 1.2.7 - resolution: "core-js@npm:1.2.7" - checksum: 0b76371bfa98708351cde580f9287e2360d2209920e738ae950ae74ad08639a2e063541020bf666c28778956fc356ed9fe56d962129c88a87a6a4a0612526c75 - languageName: node - linkType: hard - "core-js@npm:^2.4.0, core-js@npm:^2.5.0": version: 2.6.12 resolution: "core-js@npm:2.6.12" @@ -24670,15 +24663,6 @@ __metadata: languageName: node linkType: hard -"css-vendor@npm:^0.3.8": - version: 0.3.8 - resolution: "css-vendor@npm:0.3.8" - dependencies: - is-in-browser: ^1.0.2 - checksum: 0a2e0cd0d4adbfdb6236950e7f9697b8a9b294eb2ae7c95996a95d273d2a63316ce793cb4654ae048aa3c129327124d2a29aada9935a0c284f9cc341c2768c8a - languageName: node - linkType: hard - "css-vendor@npm:^2.0.8": version: 2.0.8 resolution: "css-vendor@npm:2.0.8" @@ -24813,14 +24797,14 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^2.0.0, csstype@npm:^2.5.2, csstype@npm:^2.6.7": +"csstype@npm:^2.5.2, csstype@npm:^2.6.7": version: 2.6.21 resolution: "csstype@npm:2.6.21" checksum: 2ce8bc832375146eccdf6115a1f8565a27015b74cce197c35103b4494955e9516b246140425ad24103864076aa3e1257ac9bab25a06c8d931dd87a6428c9dccf languageName: node linkType: hard -"csstype@npm:^3.0.2, csstype@npm:^3.1.2": +"csstype@npm:^3.0.2, csstype@npm:^3.1.1, csstype@npm:^3.1.2, csstype@npm:^3.1.3": version: 3.0.9 resolution: "csstype@npm:3.0.9" checksum: 199f9af7e673f9f188525c3102a329d637ff46c52f6385a4427ff5cb17adcb736189150170a7af7c5701d18d7704bdad130273f4aa7e44c6c4f9967e6115dc93 @@ -25181,7 +25165,7 @@ __metadata: languageName: node linkType: hard -"debounce@npm:^1.1.0, debounce@npm:^1.2.0": +"debounce@npm:^1.2.0": version: 1.2.1 resolution: "debounce@npm:1.2.1" checksum: 682a89506d9e54fb109526f4da255c5546102fbb8e3ae75eef3b04effaf5d4853756aee97475cd4650641869794e44f410eeb20ace2b18ea592287ab2038519e @@ -25357,13 +25341,6 @@ __metadata: languageName: node linkType: hard -"deepmerge@npm:^3.0.0": - version: 3.3.0 - resolution: "deepmerge@npm:3.3.0" - checksum: 4322195389e0170a0443c07b36add19b90249802c4b47b96265fdc5f5d8beddf491d5e50cbc5bfd65f85ccf76598173083863c202f5463b3b667aca8be75d5ac - languageName: node - linkType: hard - "deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1, deepmerge@npm:~4.3.0": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" @@ -25497,13 +25474,6 @@ __metadata: languageName: node linkType: hard -"delegate@npm:^3.1.2": - version: 3.2.0 - resolution: "delegate@npm:3.2.0" - checksum: d943058fe05897228b158cbd1bab05164df28c8f54127873231d6b03b0a5acc1b3ee1f98ac70ccc9b79cd84aa47118a7de111fee2923753491583905069da27d - languageName: node - linkType: hard - "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -25811,15 +25781,6 @@ __metadata: languageName: node linkType: hard -"dom-helpers@npm:^3.2.1, dom-helpers@npm:^3.4.0": - version: 3.4.0 - resolution: "dom-helpers@npm:3.4.0" - dependencies: - "@babel/runtime": ^7.1.2 - checksum: 58d9f1c4a96daf77eddc63ae1236b826e1cddd6db66bbf39b18d7e21896d99365b376593352d52a60969d67fa4a8dbef26adc1439fa2c1b355efa37cacbaf637 - languageName: node - linkType: hard - "dom-helpers@npm:^5.0.1": version: 5.1.4 resolution: "dom-helpers@npm:5.1.4" @@ -26248,7 +26209,7 @@ __metadata: languageName: node linkType: hard -"encoding@npm:^0.1.11, encoding@npm:^0.1.12, encoding@npm:^0.1.13": +"encoding@npm:^0.1.12, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" dependencies: @@ -26266,13 +26227,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.15.0": - version: 5.15.0 - resolution: "enhanced-resolve@npm:5.15.0" +"enhanced-resolve@npm:^5.16.0": + version: 5.16.0 + resolution: "enhanced-resolve@npm:5.16.0" dependencies: graceful-fs: ^4.2.4 tapable: ^2.2.0 - checksum: fbd8cdc9263be71cc737aa8a7d6c57b43d6aa38f6cc75dde6fcd3598a130cc465f979d2f4d01bb3bf475acb43817749c79f8eef9be048683602ca91ab52e4f11 + checksum: ccfd01850ecf2aa51e8554d539973319ff7d8a539ef1e0ba3460a0ccad6223c4ef6e19165ee64161b459cd8a48df10f52af4434c60023c65fde6afa32d475f7e languageName: node linkType: hard @@ -27035,13 +26996,13 @@ __metadata: linkType: hard "eslint-plugin-testing-library@npm:^6.0.0": - version: 6.2.0 - resolution: "eslint-plugin-testing-library@npm:6.2.0" + version: 6.2.1 + resolution: "eslint-plugin-testing-library@npm:6.2.1" dependencies: "@typescript-eslint/utils": ^5.58.0 peerDependencies: eslint: ^7.5.0 || ^8.0.0 - checksum: 7af7e0a1eee44c6ba65ce2ae99f8e46ce709a319f4cce778bb0af2dda5828d78f3a81e8989c7b691a8b9b9fef102b56136209aac700038b9e64794600b0d12db + checksum: 27c6aa32bfaba83f3d7c1477ebe75cd9ab414e8ecac8c10da18ed237fe6827df4ab1194addca7a259bfbdca1d929d88e93ed847c388f8882cbb83b84fb831e3f languageName: node linkType: hard @@ -28180,21 +28141,6 @@ __metadata: languageName: node linkType: hard -"fbjs@npm:^0.8.1": - version: 0.8.18 - resolution: "fbjs@npm:0.8.18" - dependencies: - core-js: ^1.0.0 - isomorphic-fetch: ^2.1.1 - loose-envify: ^1.0.0 - object-assign: ^4.1.0 - promise: ^7.1.1 - setimmediate: ^1.0.5 - ua-parser-js: ^0.7.30 - checksum: 668731b946a765908c9cbe51d5160f973abb78004b3d122587c3e930e3e1ddcc0ce2b17f2a8637dc9d733e149aa580f8d3035a35cc2d3bc78b78f1b19aab90e2 - languageName: node - linkType: hard - "fbjs@npm:^3.0.0, fbjs@npm:^3.0.1": version: 3.0.4 resolution: "fbjs@npm:3.0.4" @@ -29336,15 +29282,6 @@ __metadata: languageName: node linkType: hard -"good-listener@npm:^1.2.2": - version: 1.2.2 - resolution: "good-listener@npm:1.2.2" - dependencies: - delegate: ^3.1.2 - checksum: f39fb82c4e41524f56104cfd2d7aef1a88e72f3f75139115fbdf98cc7d844e0c1b39218b2e83438c6188727bf904ed78c7f0f2feff67b32833bc3af7f0202b33 - languageName: node - linkType: hard - "google-auth-library@npm:^9.0.0, google-auth-library@npm:^9.6.3": version: 9.7.0 resolution: "google-auth-library@npm:9.7.0" @@ -29565,23 +29502,22 @@ __metadata: linkType: hard "graphql-voyager@npm:^1.0.0-rc.31": - version: 1.0.0-rc.31 - resolution: "graphql-voyager@npm:1.0.0-rc.31" + version: 1.3.0 + resolution: "graphql-voyager@npm:1.3.0" dependencies: - "@f/animate": ^1.0.1 - "@material-ui/core": ^3.9.3 - classnames: ^2.2.6 - clipboard: ^2.0.4 - commonmark: ^0.29.0 - lodash: ^4.17.10 - prop-types: ^15.7.2 - svg-pan-zoom: ^3.6.0 - viz.js: 2.1.2 + "@emotion/react": 11.10.5 + "@emotion/styled": 11.10.5 + "@mui/icons-material": 5.11.0 + "@mui/lab": 5.0.0-alpha.114 + "@mui/material": 5.11.2 + commonmark: 0.30.0 + lodash: 4.17.21 + svg-pan-zoom: 3.6.1 peerDependencies: - graphql: ">=14.0.0" - react: ">=15.4.2" - react-dom: ">=15.4.2" - checksum: 80e826eeb42d286540447723ea35a463a3a155287914a45e24b69f31617d8ca74c0c43bdaa64123bf4ed046e68f7e31feca7661c06288551040f3e5b96c48fed + graphql: ">=16.5.0" + react: ">=18.0.0" + react-dom: ">=18.0.0" + checksum: 664d8adb932e3d72189e24dfb09fe364cc0e2852ab615eddeac250d9b4a0e6130a5ea363aec87c516b3c134c070c4c15999ccedec89f57afd554e600c6d4ca34 languageName: node linkType: hard @@ -29922,14 +29858,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^2.3.1": - version: 2.5.5 - resolution: "hoist-non-react-statics@npm:2.5.5" - checksum: ee2d05e5c7e1398ad84a15b0327f66bd78f38a8e0015e852f954b36434e32eb7e942d5357505020a3a1147f247b165bf1e69d72393e3accab67cafdafeb86230 - languageName: node - linkType: hard - -"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.2.1, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": +"hoist-non-react-statics@npm:^3.0.0, hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -30319,7 +30248,7 @@ __metadata: languageName: node linkType: hard -"hyphenate-style-name@npm:^1.0.2, hyphenate-style-name@npm:^1.0.3": +"hyphenate-style-name@npm:^1.0.3": version: 1.0.3 resolution: "hyphenate-style-name@npm:1.0.3" checksum: e333f610e7cb32210861ff8c55f99582cdc39e4737903936a7eb61702c98f8db45f33d54f9d230221fabc4d321b2ab8382b8af50d24a1eba21916f2edddd7b85 @@ -30513,15 +30442,6 @@ __metadata: languageName: node linkType: hard -"indefinite-observable@npm:^1.0.1": - version: 1.0.2 - resolution: "indefinite-observable@npm:1.0.2" - dependencies: - symbol-observable: 1.2.0 - checksum: 69a337967f48fca18989f9d68ad98c7220ed9b499bf00330ff72669a9583cb7f8e82f801da10720f720edf1313f427c77ce793350bdc413c952cec8ce112fc12 - languageName: node - linkType: hard - "indent-string@npm:^4.0.0": version: 4.0.0 resolution: "indent-string@npm:4.0.0" @@ -31338,7 +31258,7 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^1.0.1, is-stream@npm:^1.1.0": +"is-stream@npm:^1.1.0": version: 1.1.0 resolution: "is-stream@npm:1.1.0" checksum: 063c6bec9d5647aa6d42108d4c59723d2bd4ae42135a2d4db6eadbd49b7ea05b750fd69d279e5c7c45cf9da753ad2c00d8978be354d65aa9f6bb434969c6a2ae @@ -31558,16 +31478,6 @@ __metadata: languageName: node linkType: hard -"isomorphic-fetch@npm:^2.1.1": - version: 2.2.1 - resolution: "isomorphic-fetch@npm:2.2.1" - dependencies: - node-fetch: ^1.0.1 - whatwg-fetch: ">=0.10.0" - checksum: bb5daa7c3785d6742f4379a81e55b549a469503f7c9bf9411b48592e86632cf5e8fe8ea878dba185c0f33eb7c510c23abdeb55aebfdf5d3c70f031ced68c5424 - languageName: node - linkType: hard - "isomorphic-form-data@npm:^2.0.0": version: 2.0.0 resolution: "isomorphic-form-data@npm:2.0.0" @@ -31877,13 +31787,6 @@ __metadata: languageName: node linkType: hard -"jest-date-mock@npm:^1.0.8": - version: 1.0.8 - resolution: "jest-date-mock@npm:1.0.8" - checksum: 7b012870440b0df742d0b651435b91625dbbf02d916633a0c70c7deb5d5087f9aadc59847602312368826325909e70e95cd412896a0c9ee1d5ac382000bf5ef9 - languageName: node - linkType: hard - "jest-diff@npm:^29.2.0, jest-diff@npm:^29.7.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" @@ -32888,46 +32791,6 @@ __metadata: languageName: node linkType: hard -"jss-camel-case@npm:^6.0.0": - version: 6.1.0 - resolution: "jss-camel-case@npm:6.1.0" - dependencies: - hyphenate-style-name: ^1.0.2 - peerDependencies: - jss: ^9.7.0 - checksum: f20ad892cddc30241b5127d648233b513ce57b2a6ed2f710cd995510f8d06e1418cd8f6e8e50dc6bd60b2e8a35159bfdeb40143938e0f2cdc19fd39279953789 - languageName: node - linkType: hard - -"jss-default-unit@npm:^8.0.2": - version: 8.0.2 - resolution: "jss-default-unit@npm:8.0.2" - peerDependencies: - jss: ^9.4.0 - checksum: 5277c5ccc3d56f5c137d6d65f0fc36d15eb22ef08b5949e887ee75e9f4f25197e25f2a99706b70a2828cee010fd8ed7484fb1bb2086fe327f2b24b1bdcc22abb - languageName: node - linkType: hard - -"jss-global@npm:^3.0.0": - version: 3.0.0 - resolution: "jss-global@npm:3.0.0" - peerDependencies: - jss: ^9.0.0 - checksum: e3fa80d8251ba5f183d9b0b4416d64e8f5d285cfdb595cc600daf1a1366b67bca573939bb71d98b6596bf4d6f2c95711cf53a358af3d63cd7945dbb434c6547b - languageName: node - linkType: hard - -"jss-nested@npm:^6.0.1": - version: 6.0.1 - resolution: "jss-nested@npm:6.0.1" - dependencies: - warning: ^3.0.0 - peerDependencies: - jss: ^9.0.0 - checksum: 437bdacc559be0b4b5bc1faa2bc77b5c6cf14733fefbf73a34bb7335786b9f08e4e79b3d73cf83b386959adcd8fa9d725877912e96d9a0921ed38baa1a69b8d9 - languageName: node - linkType: hard - "jss-plugin-camel-case@npm:^10.10.0, jss-plugin-camel-case@npm:^10.5.1": version: 10.10.0 resolution: "jss-plugin-camel-case@npm:10.10.0" @@ -33002,26 +32865,6 @@ __metadata: languageName: node linkType: hard -"jss-props-sort@npm:^6.0.0": - version: 6.0.0 - resolution: "jss-props-sort@npm:6.0.0" - peerDependencies: - jss: ^9.0.0 - checksum: 82a04f625a2f2b3a71b2fcb00b1ed0478137c83dc47c323d24c7ea88d08c9ea295b061f99cf264db133fef3435366d6da594724f7713e0415dc50b4eff2aeb53 - languageName: node - linkType: hard - -"jss-vendor-prefixer@npm:^7.0.0": - version: 7.0.0 - resolution: "jss-vendor-prefixer@npm:7.0.0" - dependencies: - css-vendor: ^0.3.8 - peerDependencies: - jss: ^9.0.0 - checksum: 8ec3608711833e79da7ccfffd8177e752f16093589fa28d3a24ec19e6588fbc6a07cc5cacf59ef4c111ab1d0fb72e07b88cb3444eb8c0c6ed4cc8107da984633 - languageName: node - linkType: hard - "jss@npm:10.10.0, jss@npm:^10.10.0, jss@npm:^10.5.1, jss@npm:~10.10.0": version: 10.10.0 resolution: "jss@npm:10.10.0" @@ -33034,17 +32877,6 @@ __metadata: languageName: node linkType: hard -"jss@npm:^9.8.7": - version: 9.8.7 - resolution: "jss@npm:9.8.7" - dependencies: - is-in-browser: ^1.1.3 - symbol-observable: ^1.1.0 - warning: ^3.0.0 - checksum: ebb264cc893fb8c17a0277875947f8f72e01f5be991bf7f61a39abb861e1276e75ee402fdd2be9ce827af839a1a08bd81ad86a4e108d57fd7d7ebb2361837a53 - languageName: node - linkType: hard - "jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": version: 3.3.5 resolution: "jsx-ast-utils@npm:3.3.5" @@ -36048,16 +35880,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^1.0.1": - version: 1.7.3 - resolution: "node-fetch@npm:1.7.3" - dependencies: - encoding: ^0.1.11 - is-stream: ^1.0.1 - checksum: 3bb0528c05d541316ebe52770d71ee25a6dce334df4231fd55df41a644143e07f068637488c18a5b0c43f05041dbd3346752f9e19b50df50569a802484544d5b - languageName: node - linkType: hard - "node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.5, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" @@ -36338,13 +36160,6 @@ __metadata: languageName: node linkType: hard -"normalize-scroll-left@npm:^0.1.2": - version: 0.1.2 - resolution: "normalize-scroll-left@npm:0.1.2" - checksum: 817d5a659ba6f14f458cd03a5a0f98ec2a9d7e6c63c160f2db164827b87bb77c0237752890a18d11054fe628907493c5f4d8914907585ec9fbba3de26d2a6815 - languageName: node - linkType: hard - "normalize-url@npm:^4.1.0": version: 4.5.1 resolution: "normalize-url@npm:4.5.1" @@ -38093,27 +37908,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.43.0": - version: 1.43.0 - resolution: "playwright-core@npm:1.43.0" +"playwright-core@npm:1.43.1": + version: 1.43.1 + resolution: "playwright-core@npm:1.43.1" bin: playwright-core: cli.js - checksum: b35265d356d87eb4114997e6af985ee3cb5a473f15a465d6e02834288a55f0cae4233a18178658389ff443f92405e020815fc165274d2262c3ca1c2b5be948cf + checksum: 7c96b3a4a4bce2ee22c3cd680c9b0bb9e4bf07ee4b51d1e9a7f47a6489c7b0b960d4b550e530b8f41d1ffeadd26c7c6bb626ae8689dfd90dce1cb8e35ae78ff7 languageName: node linkType: hard -"playwright@npm:1.43.0": - version: 1.43.0 - resolution: "playwright@npm:1.43.0" +"playwright@npm:1.43.1": + version: 1.43.1 + resolution: "playwright@npm:1.43.1" dependencies: fsevents: 2.3.2 - playwright-core: 1.43.0 + playwright-core: 1.43.1 dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: edd4ef9644398c197d20e957cf2a5e5a95d7e0144f3c971923e709247f5b48551bf6168c74b34909f263ab96fac6e49466b65a9cd1349ad400dabd4b8b364acf + checksum: de9db021f93018a18275bbb5af09ebf1804aa0534f47578b35b440064abc774509740205802824afc94a99fc84dd55ffe9e215718ad3ecc691b251ab3882b096 languageName: node linkType: hard @@ -38150,13 +37965,6 @@ __metadata: languageName: node linkType: hard -"popper.js@npm:^1.14.1": - version: 1.16.1 - resolution: "popper.js@npm:1.16.1" - checksum: c56ae5001ec50a77ee297a8061a0221d99d25c7348d2e6bcd3e45a0d0f32a1fd81bca29d46cb0d4bdf13efb77685bd6a0ce93f9eb3c608311a461f945fffedbe - languageName: node - linkType: hard - "portfinder@npm:1.0.28": version: 1.0.28 resolution: "portfinder@npm:1.0.28" @@ -38923,7 +38731,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:15.x, prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -39156,11 +38964,11 @@ __metadata: linkType: hard "qs@npm:^6.10.1, qs@npm:^6.10.2, qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.9.1, qs@npm:^6.9.4, qs@npm:^6.9.6": - version: 6.12.0 - resolution: "qs@npm:6.12.0" + version: 6.12.1 + resolution: "qs@npm:6.12.1" dependencies: side-channel: ^1.0.6 - checksum: ba007fb2488880b9c6c3df356fe6888b9c1f4c5127552edac214486cfe83a332de09a5c40d490d79bb27bef977ba1085a8497512ff52eaac72e26564f77ce908 + checksum: aa761d99e65b6936ba2dd2187f2d9976afbcda38deb3ff1b3fe331d09b0c578ed79ca2abdde1271164b5be619c521ec7db9b34c23f49a074e5921372d16242d5 languageName: node linkType: hard @@ -39549,19 +39357,6 @@ __metadata: languageName: node linkType: hard -"react-event-listener@npm:^0.6.2": - version: 0.6.6 - resolution: "react-event-listener@npm:0.6.6" - dependencies: - "@babel/runtime": ^7.2.0 - prop-types: ^15.6.0 - warning: ^4.0.1 - peerDependencies: - react: ^16.3.0 - checksum: 0287e0ae8cbf0a4c03889ffc2b745f5494b3edea0f4667357d709f5646dd78c7289ce305b6f473ec6ac5789f8a937c86b7e543d0249f0bf787bccdb1eab7ecd0 - languageName: node - linkType: hard - "react-fast-compare@npm:^3.1.1": version: 3.2.0 resolution: "react-fast-compare@npm:3.2.0" @@ -39676,7 +39471,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.6.3, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.6": +"react-is@npm:^16.10.2, react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.0, react-is@npm:^16.8.6": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -39712,7 +39507,7 @@ __metadata: languageName: node linkType: hard -"react-lifecycles-compat@npm:^3.0.2, react-lifecycles-compat@npm:^3.0.4": +"react-lifecycles-compat@npm:^3.0.4": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f @@ -40031,21 +39826,6 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:^2.2.1": - version: 2.9.0 - resolution: "react-transition-group@npm:2.9.0" - dependencies: - dom-helpers: ^3.4.0 - loose-envify: ^1.4.0 - prop-types: ^15.6.2 - react-lifecycles-compat: ^3.0.4 - peerDependencies: - react: ">=15.0.0" - react-dom: ">=15.0.0" - checksum: d8c9e50aabdc2cfc324e5cdb0ad1c6eecb02e1c0cd007b26d5b30ccf49015e900683dd489348c71fba4055858308d9ba7019e0d37d0e8d37bd46ed098788f670 - languageName: node - linkType: hard - "react-transition-group@npm:^4.0.0, react-transition-group@npm:^4.4.0, react-transition-group@npm:^4.4.5": version: 4.4.5 resolution: "react-transition-group@npm:4.4.5" @@ -40071,7 +39851,7 @@ __metadata: languageName: node linkType: hard -"react-use@npm:^17.2.4, react-use@npm:^17.3.1, react-use@npm:^17.3.2, react-use@npm:^17.4.0": +"react-use@npm:^17.2.4, react-use@npm:^17.3.1, react-use@npm:^17.3.2, react-use@npm:^17.4.0, react-use@npm:^17.5.0": version: 17.5.0 resolution: "react-use@npm:17.5.0" dependencies: @@ -40348,22 +40128,6 @@ __metadata: languageName: node linkType: hard -"recompose@npm:0.28.0 - 0.30.0": - version: 0.30.0 - resolution: "recompose@npm:0.30.0" - dependencies: - "@babel/runtime": ^7.0.0 - change-emitter: ^0.1.2 - fbjs: ^0.8.1 - hoist-non-react-statics: ^2.3.1 - react-lifecycles-compat: ^3.0.2 - symbol-observable: ^1.0.4 - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0 - checksum: 18e58252336d0628b22db1e38407d32e836648e6d5c9453ba37c9f8030138b3429ee3952b053a13b60311f8b60893b207a761466bb293083542db0cf317b7a41 - languageName: node - linkType: hard - "recursive-readdir@npm:^2.2.2": version: 2.2.3 resolution: "recursive-readdir@npm:2.2.3" @@ -41157,24 +40921,24 @@ __metadata: linkType: hard "rollup@npm:^4.0.0": - version: 4.14.1 - resolution: "rollup@npm:4.14.1" + version: 4.14.2 + resolution: "rollup@npm:4.14.2" dependencies: - "@rollup/rollup-android-arm-eabi": 4.14.1 - "@rollup/rollup-android-arm64": 4.14.1 - "@rollup/rollup-darwin-arm64": 4.14.1 - "@rollup/rollup-darwin-x64": 4.14.1 - "@rollup/rollup-linux-arm-gnueabihf": 4.14.1 - "@rollup/rollup-linux-arm64-gnu": 4.14.1 - "@rollup/rollup-linux-arm64-musl": 4.14.1 - "@rollup/rollup-linux-powerpc64le-gnu": 4.14.1 - "@rollup/rollup-linux-riscv64-gnu": 4.14.1 - "@rollup/rollup-linux-s390x-gnu": 4.14.1 - "@rollup/rollup-linux-x64-gnu": 4.14.1 - "@rollup/rollup-linux-x64-musl": 4.14.1 - "@rollup/rollup-win32-arm64-msvc": 4.14.1 - "@rollup/rollup-win32-ia32-msvc": 4.14.1 - "@rollup/rollup-win32-x64-msvc": 4.14.1 + "@rollup/rollup-android-arm-eabi": 4.14.2 + "@rollup/rollup-android-arm64": 4.14.2 + "@rollup/rollup-darwin-arm64": 4.14.2 + "@rollup/rollup-darwin-x64": 4.14.2 + "@rollup/rollup-linux-arm-gnueabihf": 4.14.2 + "@rollup/rollup-linux-arm64-gnu": 4.14.2 + "@rollup/rollup-linux-arm64-musl": 4.14.2 + "@rollup/rollup-linux-powerpc64le-gnu": 4.14.2 + "@rollup/rollup-linux-riscv64-gnu": 4.14.2 + "@rollup/rollup-linux-s390x-gnu": 4.14.2 + "@rollup/rollup-linux-x64-gnu": 4.14.2 + "@rollup/rollup-linux-x64-musl": 4.14.2 + "@rollup/rollup-win32-arm64-msvc": 4.14.2 + "@rollup/rollup-win32-ia32-msvc": 4.14.2 + "@rollup/rollup-win32-x64-msvc": 4.14.2 "@types/estree": 1.0.5 fsevents: ~2.3.2 dependenciesMeta: @@ -41212,7 +40976,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: f065ba6ea0ab4271f61bb458afba7d5d36062511929f9d3ec160438c4946d843bf07b8d35d9a8986620fb2aa56990a70c8e968dda5a9baf81a3480023434bdd0 + checksum: ad2c4486e9d76dc8575f13f89cfb16dd4611b5e3527ba80454304fc96b7b9dcf91da98a3318efa7f97320324fdfa15596db2b55cfc39434ebb6e3200c8a75136 languageName: node linkType: hard @@ -41555,13 +41319,6 @@ __metadata: languageName: node linkType: hard -"select@npm:^1.1.2": - version: 1.1.2 - resolution: "select@npm:1.1.2" - checksum: 4346151e94f226ea6131e44e68e6d837f3fdee64831b756dd657cc0b02f4cb5107f867cb34a1d1216ab7737d0bf0645d44546afb030bbd8d64e891f5e4c4814e - languageName: node - linkType: hard - "selfsigned@npm:^2.0.0, selfsigned@npm:^2.4.1": version: 2.4.1 resolution: "selfsigned@npm:2.4.1" @@ -43260,7 +43017,7 @@ __metadata: languageName: node linkType: hard -"svg-pan-zoom@npm:^3.6.0": +"svg-pan-zoom@npm:3.6.1": version: 3.6.1 resolution: "svg-pan-zoom@npm:3.6.1" checksum: 500b99378d321315e1668067ef6a73af3f61f9adfab75a575b21d2d216cb446def61d60ad12265203463cca9bef56b090f00e48118fa9b7f0ffa1c691e4c1621 @@ -43383,7 +43140,7 @@ __metadata: languageName: node linkType: hard -"symbol-observable@npm:1.2.0, symbol-observable@npm:^1.0.4, symbol-observable@npm:^1.1.0": +"symbol-observable@npm:^1.0.4": version: 1.2.0 resolution: "symbol-observable@npm:1.2.0" checksum: 48ffbc22e3d75f9853b3ff2ae94a44d84f386415110aea5effc24d84c502e03a4a6b7a8f75ebaf7b585780bda34eb5d6da3121f826a6f93398429d30032971b6 @@ -43761,13 +43518,6 @@ __metadata: languageName: node linkType: hard -"tiny-emitter@npm:^2.0.0": - version: 2.1.0 - resolution: "tiny-emitter@npm:2.1.0" - checksum: fbcfb5145751a0e3b109507a828eb6d6d4501352ab7bb33eccef46e22e9d9ad3953158870a6966a59e57ab7c3f9cfac7cab8521db4de6a5e757012f4677df2dd - languageName: node - linkType: hard - "tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.3.1": version: 1.3.1 resolution: "tiny-invariant@npm:1.3.1" @@ -45504,13 +45254,6 @@ __metadata: languageName: node linkType: hard -"viz.js@npm:2.1.2": - version: 2.1.2 - resolution: "viz.js@npm:2.1.2" - checksum: 299e5a8519a472e0e98197c5f39c0ca6f3f6c4642b6ae007ed888fea998d82455c7ae0118d09cb888bfd2c46ebe3b03d7ce321a4eef1c822a5f5719d3a6ef3bb - languageName: node - linkType: hard - "vlq@npm:^0.2.1": version: 0.2.3 resolution: "vlq@npm:0.2.3" @@ -45589,31 +45332,13 @@ __metadata: languageName: node linkType: hard -"warning@npm:^3.0.0": - version: 3.0.0 - resolution: "warning@npm:3.0.0" - dependencies: - loose-envify: ^1.0.0 - checksum: c9f99a12803aab81b29858e7dc3415bf98b41baee3a4c3acdeb680d98c47b6e17490f1087dccc54432deed5711a5ce0ebcda2b27e9b5eb054c32ae50acb4419c - languageName: node - linkType: hard - -"warning@npm:^4.0.1": - version: 4.0.3 - resolution: "warning@npm:4.0.3" - dependencies: - loose-envify: ^1.0.0 - checksum: 4f2cb6a9575e4faf71ddad9ad1ae7a00d0a75d24521c193fa464f30e6b04027bd97aa5d9546b0e13d3a150ab402eda216d59c1d0f2d6ca60124d96cd40dfa35c - languageName: node - linkType: hard - -"watchpack@npm:^2.4.0": - version: 2.4.0 - resolution: "watchpack@npm:2.4.0" +"watchpack@npm:^2.4.1": + version: 2.4.1 + resolution: "watchpack@npm:2.4.1" dependencies: glob-to-regexp: ^0.4.1 graceful-fs: ^4.1.2 - checksum: 23d4bc58634dbe13b86093e01c6a68d8096028b664ab7139d58f0c37d962d549a940e98f2f201cecdabd6f9c340338dc73ef8bf094a2249ef582f35183d1a131 + checksum: 5b0179348655dcdf19cac7cb4ff923fdc024d630650c0bf6bec8899cf47c60e19d4f810a88dba692ed0e7f684cf0fcffea86efdbf6c35d81f031e328043b7fab languageName: node linkType: hard @@ -45788,24 +45513,24 @@ __metadata: linkType: hard "webpack@npm:^5, webpack@npm:^5.70.0": - version: 5.90.2 - resolution: "webpack@npm:5.90.2" + version: 5.91.0 + resolution: "webpack@npm:5.91.0" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^1.0.5 - "@webassemblyjs/ast": ^1.11.5 - "@webassemblyjs/wasm-edit": ^1.11.5 - "@webassemblyjs/wasm-parser": ^1.11.5 + "@webassemblyjs/ast": ^1.12.1 + "@webassemblyjs/wasm-edit": ^1.12.1 + "@webassemblyjs/wasm-parser": ^1.12.1 acorn: ^8.7.1 acorn-import-assertions: ^1.9.0 browserslist: ^4.21.10 chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.15.0 + enhanced-resolve: ^5.16.0 es-module-lexer: ^1.2.1 eslint-scope: 5.1.1 events: ^3.2.0 glob-to-regexp: ^0.4.1 - graceful-fs: ^4.2.9 + graceful-fs: ^4.2.11 json-parse-even-better-errors: ^2.3.1 loader-runner: ^4.2.0 mime-types: ^2.1.27 @@ -45813,14 +45538,14 @@ __metadata: schema-utils: ^3.2.0 tapable: ^2.1.1 terser-webpack-plugin: ^5.3.10 - watchpack: ^2.4.0 + watchpack: ^2.4.1 webpack-sources: ^3.2.3 peerDependenciesMeta: webpack-cli: optional: true bin: webpack: bin/webpack.js - checksum: 4af55f314c3610e0b17afd01960c4cf9d8d90c87a58ef7d7dc7a0a019f48e2bba4eff63b02d9da56fc61df5b04af0d66b3fa4ec82937a780d37a64db84c643fe + checksum: f1073715dbb1ed5c070affef293d800a867708bcbc5aba4d8baee87660e0cf53c55966a6f36fab078d1d6c9567cdcd0a9086bdfb607cab87ea68c6449791b9a3 languageName: node linkType: hard @@ -45860,13 +45585,6 @@ __metadata: languageName: node linkType: hard -"whatwg-fetch@npm:>=0.10.0": - version: 3.6.19 - resolution: "whatwg-fetch@npm:3.6.19" - checksum: 2896bc9ca867ea514392c73e2a272f65d5c4916248fe0837a9df5b1b92f247047bc76cf7c29c28a01ac6c5fb4314021d2718958c8a08292a96d56f72b2f56806 - languageName: node - linkType: hard - "whatwg-mimetype@npm:^2.3.0": version: 2.3.0 resolution: "whatwg-mimetype@npm:2.3.0"