diff --git a/.changeset/fresh-beans-call.md b/.changeset/fresh-beans-call.md new file mode 100644 index 0000000000..7327f9a3c9 --- /dev/null +++ b/.changeset/fresh-beans-call.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Add ability to customize `Read More` destination with `readMoreUrl` prop for `MissingAnnotationEmptyState` component. diff --git a/.changeset/nervous-bears-brush.md b/.changeset/nervous-bears-brush.md new file mode 100644 index 0000000000..f3b0c7db78 --- /dev/null +++ b/.changeset/nervous-bears-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +The Auth0 adapter no longer disables session refreshing. diff --git a/.changeset/pretty-hornets-listen.md b/.changeset/pretty-hornets-listen.md new file mode 100644 index 0000000000..6e0701e617 --- /dev/null +++ b/.changeset/pretty-hornets-listen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fix issue where `nextPageCursor` is defined on the last page of results diff --git a/.changeset/sweet-icons-sneeze.md b/.changeset/sweet-icons-sneeze.md new file mode 100644 index 0000000000..b12424c7a8 --- /dev/null +++ b/.changeset/sweet-icons-sneeze.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fix `EntityTypeFilter` so generating available types is case insensitive diff --git a/.changeset/tall-tables-smash.md b/.changeset/tall-tables-smash.md new file mode 100644 index 0000000000..2bf277879e --- /dev/null +++ b/.changeset/tall-tables-smash.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-graphiql': patch +'@backstage/plugin-api-docs': patch +--- + +Add the dependency `graphql-ws` to fix the latest release of `@graphiql/toolkit` that has a missing dependency diff --git a/.changeset/techdocs-khaki-cheetahs-clap.md b/.changeset/techdocs-khaki-cheetahs-clap.md new file mode 100644 index 0000000000..5aba436534 --- /dev/null +++ b/.changeset/techdocs-khaki-cheetahs-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-react': patch +--- + +Added overload signatures for `createTechDocsAddonExtension` to handle TechDocs addons without props. diff --git a/.changeset/techdocs-seal-deal.md b/.changeset/techdocs-seal-deal.md new file mode 100644 index 0000000000..865d1fc837 --- /dev/null +++ b/.changeset/techdocs-seal-deal.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Fixed a bug that could cause TechDocs index generation to hang and fail when an underlying TechDocs site's `search_index.json` was empty. diff --git a/.changeset/techdocs-stupid-radios-approve.md b/.changeset/techdocs-stupid-radios-approve.md new file mode 100644 index 0000000000..813e895611 --- /dev/null +++ b/.changeset/techdocs-stupid-radios-approve.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +Update `techdocs-cli serve`'s `proxyEndpoint` to match the base URL of the embedded techdocs app. diff --git a/.changeset/techdocs-weak-boats-work.md b/.changeset/techdocs-weak-boats-work.md new file mode 100644 index 0000000000..ec4f089592 --- /dev/null +++ b/.changeset/techdocs-weak-boats-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Introducing the Expandable Navigation addon, which lets you expand and collapse the TechDocs main navigation and store your preference in local storage. diff --git a/.changeset/tough-queens-hope.md b/.changeset/tough-queens-hope.md new file mode 100644 index 0000000000..6816893611 --- /dev/null +++ b/.changeset/tough-queens-hope.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added a chevron Indicator when the sidebar is collapsed and has a sub-menu diff --git a/.changeset/unlucky-bobcats-fold.md b/.changeset/unlucky-bobcats-fold.md new file mode 100644 index 0000000000..6dd820db8d --- /dev/null +++ b/.changeset/unlucky-bobcats-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Removed the explicit `disableRefresh` option from `OAuthAdapter`. Refresh can still be disabled for a provider by not implementing the `refresh` method. diff --git a/.github/workflows/verify_dco.yaml b/.github/workflows/verify_dco.yaml index 71bd322eca..2094327d23 100644 --- a/.github/workflows/verify_dco.yaml +++ b/.github/workflows/verify_dco.yaml @@ -54,7 +54,7 @@ jobs: console.log(`creating comment on PR #${pull.number}`); const body = ` Thanks for the contribution! - All commits need to be DCO signed before merging. Please refer to the the [DCO section in CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) or the [DCO](${checks.data.check_runs[0].html_url}) status for more info. + All commits need to be DCO signed before they are reviewed. Please refer to the the [DCO section in CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md#developer-certificate-of-origin) or the [DCO](${checks.data.check_runs[0].html_url}) status for more info. `; await github.rest.issues.createComment({ repo, diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md index 282088fef9..bd2bcd1694 100644 --- a/docs/auth/add-auth-provider.md +++ b/docs/auth/add-auth-provider.md @@ -153,7 +153,6 @@ export const createOktaProvider: AuthProviderFactory = ({ // Wrap the OAuthProviderHandlers with OAuthProvider, which implements AuthProviderRouteHandlers return OAuthProvider.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, tokenIssuer, }); diff --git a/docs/features/techdocs/addons.md b/docs/features/techdocs/addons.md index e8af56d3fc..5305e3bc68 100644 --- a/docs/features/techdocs/addons.md +++ b/docs/features/techdocs/addons.md @@ -111,10 +111,11 @@ page header, TechDocs Addons whose location is `Header` will not be rendered. Addons can, in principle, be provided by any plugin! To make it easier to discover available Addons, we've compiled a list of them here: -| Addon | Package/Plugin | Description | -| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. | -| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. | +| Addon | Package/Plugin | Description | +| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. | Got an Addon to contribute? Feel free to add a row above! diff --git a/microsite/data/on-demand/20220420-1.yaml b/microsite/data/on-demand/20220420-1.yaml index 6089ac1cc9..96560ec436 100644 --- a/microsite/data/on-demand/20220420-1.yaml +++ b/microsite/data/on-demand/20220420-1.yaml @@ -1,7 +1,7 @@ --- title: Adopters Community Sessions date: April 20, 2022 -category: Upcoming +category: Meetup description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc. youtubeUrl: https://youtu.be/mFi_X58igzk youtubeImgUrl: https://backstage.io/img/b-sessions.png diff --git a/microsite/data/on-demand/20220427-1.yaml b/microsite/data/on-demand/20220427-1.yaml index 451ee3bc67..80ec8eae00 100644 --- a/microsite/data/on-demand/20220427-1.yaml +++ b/microsite/data/on-demand/20220427-1.yaml @@ -1,7 +1,7 @@ --- title: Contributor Community Sessions date: April 27, 2022 -category: Upcoming +category: Meetup description: Join the maintainers and contributors for the Contributor Community Sessions youtubeUrl: https://youtu.be/evf_LV0KzIk youtubeImgUrl: https://backstage.io/img/b-sessions.png diff --git a/microsite/data/on-demand/20220518-01.yaml b/microsite/data/on-demand/20220518-01.yaml new file mode 100644 index 0000000000..e98c79005a --- /dev/null +++ b/microsite/data/on-demand/20220518-01.yaml @@ -0,0 +1,9 @@ +--- +title: Adopters Community Sessions +date: May 18, 2022 +category: Upcoming +description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc. +youtubeUrl: https://youtu.be/dEd1fl3wRv +youtubeImgUrl: https://backstage.io/img/b-sessions.png +rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com +eventUrl: https://github.com/backstage/community/issues/46 diff --git a/microsite/data/plugins/kpt-config-as-data.yaml b/microsite/data/plugins/kpt-config-as-data.yaml new file mode 100644 index 0000000000..03d5d66947 --- /dev/null +++ b/microsite/data/plugins/kpt-config-as-data.yaml @@ -0,0 +1,9 @@ +--- +title: Configuration as Data +author: kpt +authorUrl: https://kpt.dev +category: Configuration +description: Configuration GUI over GitOps using kpt, with WYSIWYG editing, review and approval, versioning and undo, and package cloning and upgrades. +documentation: https://github.com/GoogleContainerTools/kpt-backstage-plugins/tree/main/plugins/cad +iconUrl: https://github.com/GoogleContainerTools/kpt/blob/main/logo/KptLogoSmall.png?raw=true +npmPackageName: '@kpt/backstage-plugin-cad' diff --git a/microsite/pages/en/live.js b/microsite/pages/en/live.js index 374b690d89..9f8a0e24b0 100644 --- a/microsite/pages/en/live.js +++ b/microsite/pages/en/live.js @@ -34,7 +34,7 @@ const Background = props => { diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 089900fb77..35c61f2f16 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -71,6 +71,7 @@ import { } from '@backstage/plugin-techdocs'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { + ExpandableNavigation, ReportIssue, TextSize, } from '@backstage/plugin-techdocs-module-addons-contrib'; @@ -185,6 +186,7 @@ const routes = ( > {techDocsPage} + diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx index 61131bad9b..8d8416a241 100644 --- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx @@ -37,6 +37,7 @@ spec: type Props = { annotation: string; + readMoreUrl?: string; }; export type MissingAnnotationEmptyStateClassKey = 'code'; @@ -53,7 +54,10 @@ const useStyles = makeStyles( ); export function MissingAnnotationEmptyState(props: Props) { - const { annotation } = props; + const { annotation, readMoreUrl } = props; + const url = + readMoreUrl || + 'https://backstage.io/docs/features/software-catalog/well-known-annotations'; const classes = useStyles(); const description = ( <> @@ -81,11 +85,7 @@ export function MissingAnnotationEmptyState(props: Props) { customStyle={{ background: 'inherit', fontSize: '115%' }} /> - diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 4a0376946a..c004f6904c 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -369,6 +369,16 @@ const SidebarItemBase = forwardRef((props, ref) => { // Still waiting on a Q answered to fine tune the implementation const { isOpen } = useContext(SidebarContext); + const divStyle = + !isOpen && children ? { display: 'flex', marginLeft: '24px' } : {}; + + const displayItemIcon = ( +
+ + {!isOpen && children ? : <>} +
+ ); + const itemIcon = ( ((props, ref) => { invisible={!hasNotifications} className={classnames({ [classes.closedItemIcon]: !isOpen })} > - + {displayItemIcon} ); diff --git a/packages/techdocs-cli/src/lib/httpServer.ts b/packages/techdocs-cli/src/lib/httpServer.ts index 92fc5abc87..c4bd5b823a 100644 --- a/packages/techdocs-cli/src/lib/httpServer.ts +++ b/packages/techdocs-cli/src/lib/httpServer.ts @@ -32,7 +32,7 @@ export default class HTTPServer { mkdocsPort: number, verbose: boolean, ) { - this.proxyEndpoint = '/api/'; + this.proxyEndpoint = '/api/techdocs/'; this.backstageBundleDir = backstageBundleDir; this.backstagePort = backstagePort; this.mkdocsPort = mkdocsPort; @@ -46,10 +46,9 @@ export default class HTTPServer { }); return (request: http.IncomingMessage): [httpProxy, string] => { - // If the request goes to /api/ we want to remove /api/ from the prefix of the request URL. - // e.g. ['/', 'api', pathChunks] - const [, , ...pathChunks] = request.url?.split('/') ?? []; - const forwardPath = pathChunks.join('/'); + // If the request path is prefixed with this.proxyEndpoint, remove it. + const proxyEndpointPath = new RegExp(`^${this.proxyEndpoint}`, 'i'); + const forwardPath = request.url?.replace(proxyEndpointPath, '') || ''; return [proxy, forwardPath]; }; diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 1c00a4b01e..ddf837493b 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -44,6 +44,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "graphiql": "^1.8.8", + "graphql-ws": "^5.4.1", "graphql": "^16.0.0", "isomorphic-form-data": "^2.0.0", "react-router": "6.0.0-beta.0", diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index a38197668e..bb25ff6d57 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -586,11 +586,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { handlers: OAuthHandlers, options: Pick< Options, - | 'providerId' - | 'persistScopes' - | 'disableRefresh' - | 'tokenIssuer' - | 'callbackUrl' + 'providerId' | 'persistScopes' | 'tokenIssuer' | 'callbackUrl' >, ): OAuthAdapter; // (undocumented) diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index 0ce9dff66c..d6d346745a 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -60,7 +60,6 @@ describe('OAuthAdapter', () => { const oAuthProviderOptions = { providerId: 'test-provider', secure: false, - disableRefresh: true, appOrigin: 'http://localhost:3000', cookieDomain: 'example.com', cookiePath: '/auth/test-provider', @@ -110,7 +109,6 @@ describe('OAuthAdapter', () => { it('sets the refresh cookie if refresh is enabled', async () => { const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, - disableRefresh: false, isOriginAllowed: () => false, }); @@ -153,7 +151,6 @@ describe('OAuthAdapter', () => { }; const oauthProvider = new OAuthAdapter(handlers, { ...oAuthProviderOptions, - disableRefresh: false, persistScopes: true, }); @@ -238,36 +235,9 @@ describe('OAuthAdapter', () => { ); }); - it('does not set the refresh cookie if refresh is disabled', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - disableRefresh: true, - isOriginAllowed: () => false, - }); - - const mockRequest = { - cookies: { - 'test-provider-nonce': 'nonce', - }, - query: { - state: 'nonce', - }, - } as unknown as express.Request; - - const mockResponse = { - cookie: jest.fn().mockReturnThis(), - setHeader: jest.fn().mockReturnThis(), - end: jest.fn().mockReturnThis(), - } as unknown as express.Response; - - await oauthProvider.frameHandler(mockRequest, mockResponse); - expect(mockResponse.cookie).toHaveBeenCalledTimes(0); - }); - it('removes refresh cookie when logging out', async () => { const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, - disableRefresh: false, isOriginAllowed: () => false, }); @@ -292,10 +262,8 @@ describe('OAuthAdapter', () => { }); it('gets new access-token when refreshing', async () => { - oAuthProviderOptions.disableRefresh = false; const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, - disableRefresh: false, isOriginAllowed: () => false, }); @@ -327,26 +295,6 @@ describe('OAuthAdapter', () => { }); }); - it('handles refresh without capabilities', async () => { - const oauthProvider = new OAuthAdapter(providerInstance, { - ...oAuthProviderOptions, - disableRefresh: true, - isOriginAllowed: () => false, - }); - - const mockRequest = { - header: () => 'XMLHttpRequest', - } as unknown as express.Request; - - const mockResponse = {} as unknown as express.Response; - - await expect( - oauthProvider.refresh(mockRequest, mockResponse), - ).rejects.toThrow( - 'Refresh token is not supported for provider test-provider', - ); - }); - it('sets the correct cookie configuration using a callbackUrl', async () => { const config = { baseUrl: 'http://domain.org/auth', diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 637dc1cff2..5be4b7e3c0 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -48,7 +48,6 @@ export const TEN_MINUTES_MS = 600 * 1000; export type Options = { providerId: string; secure: boolean; - disableRefresh?: boolean; persistScopes?: boolean; cookieDomain: string; cookiePath: string; @@ -64,11 +63,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { handlers: OAuthHandlers, options: Pick< Options, - | 'providerId' - | 'persistScopes' - | 'disableRefresh' - | 'tokenIssuer' - | 'callbackUrl' + 'providerId' | 'persistScopes' | 'tokenIssuer' | 'callbackUrl' >, ): OAuthAdapter { const { origin: appOrigin } = new URL(config.appUrl); @@ -170,7 +165,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { response.providerInfo.scope = state.scope; } - if (refreshToken && !this.options.disableRefresh) { + if (refreshToken) { // set new refresh token this.setRefreshTokenCookie(res, refreshToken); } @@ -210,7 +205,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { throw new AuthenticationError('Invalid X-Requested-With header'); } - if (!this.handlers.refresh || this.options.disableRefresh) { + if (!this.handlers.refresh) { throw new InputError( `Refresh token is not supported for provider ${this.options.providerId}`, ); diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 3181314540..9ddd587508 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -243,7 +243,6 @@ export const auth0 = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: true, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/bitbucket/provider.ts b/plugins/auth-backend/src/providers/bitbucket/provider.ts index 905181333a..5b9919764b 100644 --- a/plugins/auth-backend/src/providers/bitbucket/provider.ts +++ b/plugins/auth-backend/src/providers/bitbucket/provider.ts @@ -261,7 +261,6 @@ export const bitbucket = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 23867f6990..868357bcda 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -248,7 +248,6 @@ export const gitlab = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 233e28f750..2d58bc2aa6 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -238,7 +238,6 @@ export const google = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 0a4b67d667..0c58049619 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -267,7 +267,6 @@ export const microsoft = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 8e0250cf2f..6b4787e985 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -43,6 +43,7 @@ import { SignInResolver, } from '../types'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; +import { InputError } from '@backstage/errors'; type PrivateInfo = { refreshToken: string; @@ -56,6 +57,7 @@ export type OAuth2AuthProviderOptions = OAuthProviderOptions & { scope?: string; resolverContext: AuthResolverContext; includeBasicAuth?: boolean; + disableRefresh?: boolean; }; export class OAuth2AuthProvider implements OAuthHandlers { @@ -63,11 +65,13 @@ export class OAuth2AuthProvider implements OAuthHandlers { private readonly signInResolver?: SignInResolver; private readonly authHandler: AuthHandler; private readonly resolverContext: AuthResolverContext; + private readonly disableRefresh: boolean; constructor(options: OAuth2AuthProviderOptions) { this.signInResolver = options.signInResolver; this.authHandler = options.authHandler; this.resolverContext = options.resolverContext; + this.disableRefresh = options.disableRefresh ?? false; this._strategy = new OAuth2Strategy( { @@ -132,6 +136,9 @@ export class OAuth2AuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest) { + if (this.disableRefresh) { + throw new InputError('Session refreshes have been disabled'); + } const refreshTokenResponse = await executeRefreshTokenStrategy( this._strategy, req.refreshToken, @@ -243,10 +250,10 @@ export const oauth2 = createAuthProviderIntegration({ scope, includeBasicAuth, resolverContext, + disableRefresh, }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index aba9048cc7..69a65d1cae 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -262,7 +262,6 @@ export const oidc = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 4e05ff4487..98cec6df3a 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -271,7 +271,6 @@ export const okta = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 7dcc6455ac..d81eb3d83c 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -238,7 +238,6 @@ export const onelogin = createAuthProviderIntegration({ }); return OAuthAdapter.fromConfig(globalConfig, provider, { - disableRefresh: false, providerId, callbackUrl, }); diff --git a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx index 8e8c1b24d7..464531a4d9 100644 --- a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx +++ b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx @@ -96,7 +96,13 @@ export function useEntityTypeFilter(): { } // Sort by facet count descending, so the most common types appear on top - const newTypes = sortBy(facets, f => -f.count).map(f => f.value); + const newTypes = [ + ...new Set( + sortBy(facets, f => -f.count).map(f => + f.value.toLocaleLowerCase('en-US'), + ), + ), + ]; setAvailableTypes(newTypes); // Update type filter to only valid values when the list of available types has changed diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index e19a3e40d1..99903cb63c 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -41,6 +41,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "graphiql": "^1.5.12", + "graphql-ws": "^5.4.1", "graphql": "^16.0.0", "react-use": "^17.2.4" }, diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts index 134de5a9f6..24ceec2875 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts @@ -438,6 +438,50 @@ describe('ElasticSearchSearchEngine', () => { }); }); + it('should perform search query with less results than one page', async () => { + mock.clear({ + method: 'POST', + path: '/*__search/_search', + }); + mock.add( + { + method: 'POST', + path: '/*__search/_search', + }, + () => { + return { + hits: { + total: { value: 20, relation: 'eq' }, + hits: Array(20) + .fill(null) + .map((_, i) => ({ + _index: 'mytype-index__', + _source: { + value: `${i}`, + }, + })), + }, + }; + }, + ); + + const mockedSearchResult = await testSearchEngine.query({ + term: 'testTerm', + filters: {}, + }); + + expect(mockedSearchResult).toMatchObject({ + results: expect.arrayContaining( + Array(20) + .fill(null) + .map((_, i) => ({ + type: 'mytype', + document: { value: `${i}` }, + })), + ), + }); + }); + it('should perform search query with more results than one page', async () => { mock.clear({ method: 'POST', diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index 5ec33a95e3..00c25905c3 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -268,7 +268,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { body: elasticSearchQuery, }); const { page } = decodePageCursor(query.pageCursor); - const hasNextPage = result.body.hits.total.value > page * pageSize; + const hasNextPage = result.body.hits.total.value > (page + 1) * pageSize; const hasPreviousPage = page > 0; const nextPageCursor = hasNextPage ? encodePageCursor({ page: page + 1 }) diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts index 915b027854..d458eeaba0 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts @@ -179,7 +179,18 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { }, }, ); - const searchIndex = await searchIndexResponse.json(); + + // todo(@backstage/techdocs-core): remove Promise.race() when node-fetch is 3.x+ + // workaround for fetch().json() hanging in node-fetch@2.x.x, fixed in 3.x.x + // https://github.com/node-fetch/node-fetch/issues/665 + const searchIndex = await Promise.race([ + searchIndexResponse.json(), + new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Could not parse JSON in 5 seconds.'); + }, 5000); + }), + ]); return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ title: unescape(doc.title), diff --git a/plugins/techdocs-module-addons-contrib/api-report.md b/plugins/techdocs-module-addons-contrib/api-report.md index 19d2f70a2b..efe88e2d5c 100644 --- a/plugins/techdocs-module-addons-contrib/api-report.md +++ b/plugins/techdocs-module-addons-contrib/api-report.md @@ -7,6 +7,9 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; +// @public +export const ExpandableNavigation: () => JSX.Element | null; + // @public export const ReportIssue: (props: ReportIssueProps) => JSX.Element | null; @@ -33,5 +36,5 @@ export type ReportIssueTemplateBuilder = ({ export const techdocsModuleAddonsContribPlugin: BackstagePlugin<{}, {}>; // @public -export const TextSize: (props: unknown) => JSX.Element | null; +export const TextSize: () => JSX.Element | null; ``` diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 8a1801f0ba..074d12a505 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -43,6 +43,7 @@ "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "@react-hookz/web": "^13.0.0", "git-url-parse": "^11.6.0", "react-use": "^17.2.4" }, diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx new file mode 100644 index 0000000000..3efc923145 --- /dev/null +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.test.tsx @@ -0,0 +1,146 @@ +/* + * 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 { TechDocsAddonTester } from '@backstage/plugin-techdocs-addons-test-utils'; + +import React from 'react'; +import { fireEvent, waitFor } from '@testing-library/react'; + +import { ExpandableNavigation } from '../plugin'; + +const mockNavWithSublevels = ( +
+ +
+); + +const mockNavWithoutSublevels = ( + +); + +describe('ExpandableNavigation', () => { + it('renders without exploding', async () => { + const { getByRole } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom(mockNavWithSublevels) + .renderWithEffects(); + + expect(getByRole('button', { name: 'expand-nav' })).toBeInTheDocument(); + }); + + it('expands and collapses navigation', async () => { + const { getByRole, shadowRoot } = + await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom(mockNavWithSublevels) + .renderWithEffects(); + + const toggles = + shadowRoot!.querySelectorAll('.md-toggle'); + + expect(toggles).toHaveLength(2); + toggles.forEach(item => { + expect(item).not.toBeChecked(); + }); + + const expandButton = getByRole('button', { name: 'expand-nav' }); + + fireEvent.click(expandButton); + + await waitFor(() => { + expect(getByRole('button', { name: 'collapse-nav' })).toBeInTheDocument(); + toggles.forEach(item => { + expect(item).toBeChecked(); + }); + }); + + const collapseButton = getByRole('button', { name: 'collapse-nav' }); + + fireEvent.click(collapseButton); + + await waitFor(() => { + toggles.forEach(item => { + expect(item).not.toBeChecked(); + }); + }); + }); + + it('does not render when navigation has no sublevels', async () => { + const { queryByRole } = await TechDocsAddonTester.buildAddonsInTechDocs([ + , + ]) + .withDom(mockNavWithoutSublevels) + .renderWithEffects(); + + expect( + queryByRole('button', { name: 'expand-nav' }), + ).not.toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx new file mode 100644 index 0000000000..5ef76187e3 --- /dev/null +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/ExpandableNavigation.tsx @@ -0,0 +1,126 @@ +/* + * 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 React, { useEffect, useCallback, useState } from 'react'; +import { useLocalStorageValue } from '@react-hookz/web'; +import { Button, withStyles } from '@material-ui/core'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + +import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; + +const NESTED_LIST_TOGGLE = '.md-nav__item--nested .md-toggle'; + +const EXPANDABLE_NAVIGATION_LOCAL_STORAGE = + '@backstage/techdocs-addons/nav-expanded'; + +const StyledButton = withStyles({ + root: { + position: 'absolute', + left: '220px', + top: '19px', + padding: 0, + minWidth: 0, + }, +})(Button); + +const CollapsedIcon = withStyles({ + root: { + height: '20px', + width: '20px', + }, +})(ChevronRightIcon); + +const ExpandedIcon = withStyles({ + root: { + height: '20px', + width: '20px', + }, +})(ExpandMoreIcon); + +type expandableNavigationLocalStorage = { + expandAllNestedNavs: boolean; +}; + +/** + * Show expand/collapse navigation button next to site name in main + * navigation menu if documentation site has nested navigation. + */ +export const ExpandableNavigationAddon = () => { + const defaultValue = { expandAllNestedNavs: false }; + const [expanded, setExpanded] = + useLocalStorageValue( + EXPANDABLE_NAVIGATION_LOCAL_STORAGE, + defaultValue, + ); + const [hasNavSubLevels, setHasNavSubLevels] = useState(false); + + const [...checkboxToggles] = useShadowRootElements([ + NESTED_LIST_TOGGLE, + ]); + + const shouldToggle = useCallback( + (item: HTMLInputElement) => { + const isExpanded = item.checked; + const shouldExpand = expanded?.expandAllNestedNavs; + + // Is collapsed but should expand + if (shouldExpand && !isExpanded) { + return true; + } + + // Is expanded but should collapse + if (!shouldExpand && isExpanded) { + return true; + } + + return false; + }, + [expanded], + ); + + useEffect(() => { + // There is no nested navs + if (!checkboxToggles?.length) return; + + setHasNavSubLevels(true); + checkboxToggles.forEach(item => { + if (shouldToggle(item)) item.click(); + }); + }, [expanded, shouldToggle, checkboxToggles]); + + const handleState = () => { + setExpanded(prevState => ({ + expandAllNestedNavs: !prevState?.expandAllNestedNavs, + })); + }; + + return ( + <> + {hasNavSubLevels ? ( + + {expanded?.expandAllNestedNavs ? : } + + ) : null} + + ); +}; diff --git a/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/index.ts b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/index.ts new file mode 100644 index 0000000000..8f1c131d08 --- /dev/null +++ b/plugins/techdocs-module-addons-contrib/src/ExpandableNavigation/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export * from './ExpandableNavigation'; diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx index 68bde5c762..459fa1b65e 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; -import { ReportIssue } from '..'; +import { ReportIssue } from '../plugin'; const byUrl = jest.fn(); diff --git a/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx b/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx index 282d54a0b6..6b4a055c2a 100644 --- a/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx +++ b/plugins/techdocs-module-addons-contrib/src/TextSize/TextSize.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; -import { TextSize } from '..'; +import { TextSize } from '../plugin'; describe('TextSize', () => { it('renders without exploding', async () => { diff --git a/plugins/techdocs-module-addons-contrib/src/index.ts b/plugins/techdocs-module-addons-contrib/src/index.ts index e2e57b15d1..528afe5c0d 100644 --- a/plugins/techdocs-module-addons-contrib/src/index.ts +++ b/plugins/techdocs-module-addons-contrib/src/index.ts @@ -22,6 +22,7 @@ export { techdocsModuleAddonsContribPlugin, + ExpandableNavigation, ReportIssue, TextSize, } from './plugin'; diff --git a/plugins/techdocs-module-addons-contrib/src/plugin.ts b/plugins/techdocs-module-addons-contrib/src/plugin.ts index 0d7743da9e..06fa7db07d 100644 --- a/plugins/techdocs-module-addons-contrib/src/plugin.ts +++ b/plugins/techdocs-module-addons-contrib/src/plugin.ts @@ -19,6 +19,7 @@ import { createTechDocsAddonExtension, TechDocsAddonLocations, } from '@backstage/plugin-techdocs-react'; +import { ExpandableNavigationAddon } from './ExpandableNavigation'; import { ReportIssueAddon, ReportIssueProps } from './ReportIssue'; import { TextSizeAddon } from './TextSize'; @@ -32,6 +33,52 @@ export const techdocsModuleAddonsContribPlugin = createPlugin({ id: 'techdocsModuleAddonsContrib', }); +/** + * TechDocs addon that lets you expand/collapse the TechDocs main navigation + * and keep the preferred state in local storage. The addon will render as + * a button next to the site name if the documentation has nested navigation. + * + * @example + * Here's a simple example: + * ``` + * import { + * DefaultTechDocsHome, + * TechDocsIndexPage, + * TechDocsReaderPage, + * } from '@backstage/plugin-techdocs'; + * import { TechDocsAddons } from '@backstage/plugin-techdocs-react/alpha'; + * import { ExpandableNavigation } from '@backstage/plugin-techdocs-module-addons-contrib'; + * + * + * const AppRoutes = () => { + * + * // other plugin routes + * }> + * + * + * } + * > + * + * + * + * + * ; + * }; + * ``` + * + * @public + */ + +export const ExpandableNavigation = techdocsModuleAddonsContribPlugin.provide( + createTechDocsAddonExtension({ + name: 'ExpandableNavigation', + location: TechDocsAddonLocations.PrimarySidebar, + component: ExpandableNavigationAddon, + }), +); + /** * TechDocs addon that lets you select text and open GitHub/Gitlab issues * diff --git a/plugins/techdocs-react/api-report.md b/plugins/techdocs-react/api-report.md index e96cbebd1c..b3ee9168a6 100644 --- a/plugins/techdocs-react/api-report.md +++ b/plugins/techdocs-react/api-report.md @@ -14,6 +14,11 @@ import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { SetStateAction } from 'react'; +// @public +export function createTechDocsAddonExtension( + options: TechDocsAddonOptions, +): Extension<() => JSX.Element | null>; + // @public export function createTechDocsAddonExtension( options: TechDocsAddonOptions, diff --git a/plugins/techdocs-react/src/addons.tsx b/plugins/techdocs-react/src/addons.tsx index 49aa5b1bcc..ce5b3eb5cd 100644 --- a/plugins/techdocs-react/src/addons.tsx +++ b/plugins/techdocs-react/src/addons.tsx @@ -48,7 +48,23 @@ const getDataKeyByName = (name: string) => { }; /** - * Create a TechDocs addon. + * Create a TechDocs addon overload signature without props. + * @public + */ +export function createTechDocsAddonExtension( + options: TechDocsAddonOptions, +): Extension<() => JSX.Element | null>; + +/** + * Create a TechDocs addon overload signature with props. + * @public + */ +export function createTechDocsAddonExtension( + options: TechDocsAddonOptions, +): Extension<(props: TComponentProps) => JSX.Element | null>; + +/** + * Create a TechDocs addon implementation. * @public */ export function createTechDocsAddonExtension( diff --git a/yarn.lock b/yarn.lock index 03a992be5e..2d54d645f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2246,9 +2246,9 @@ xdg-basedir "^4.0.0" "@graphiql/toolkit@^0.4.4": - version "0.4.4" - resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.4.4.tgz#c5f2cd0d7fc47707f68406655836aedc5e6ca2b1" - integrity sha512-HrppQ5u0uJPYpImLqQUJWNrS9E97Cmz8DO3Dn0dXeCg1iBoKjRnZoFf2rjFF5ge9Uc9RihL181Uph/Iw9YYMFg== + version "0.4.5" + resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.4.5.tgz#9fb5020444712562eae9581724695c235f610836" + integrity sha512-QXuuMSSK/0GfBS7tltrGZdyhIvm6oe9TK4VW9pfa8dALYttpzyJ64Q4Sx9I1Ng++yOMJWziM/ksa043zkNHsjQ== dependencies: "@n1ru4l/push-pull-async-iterable-iterator" "^3.1.0" meros "^1.1.4" @@ -6284,9 +6284,9 @@ integrity sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A== "@types/node@^16.0.0", "@types/node@^16.11.26", "@types/node@^16.9.2": - version "16.11.34" - resolved "https://registry.npmjs.org/@types/node/-/node-16.11.34.tgz#520224e4be4448c279ecad09639ab460cc441a50" - integrity sha512-UrWGDyLAlQ2Z8bNOGWTsqbP9ZcBeTYBVuTRNxXTztBy5KhWUFI3BaeDWoCP/CzV/EVGgO1NTYzv9ZytBI9GAEw== + version "16.11.35" + resolved "https://registry.npmjs.org/@types/node/-/node-16.11.35.tgz#6642bdce5b5cee23314b91a7c069981c6bd68791" + integrity sha512-QXu45LyepgnhUfnIAj/FyT4uM87ug5KpIrgXfQtUPNAlx8w5hmd8z8emqCLNvG11QkpRSCG9Qg2buMxvqfjfsQ== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -7914,9 +7914,9 @@ aws-sdk-mock@^5.2.1: traverse "^0.6.6" aws-sdk@^2.1122.0, aws-sdk@^2.840.0, aws-sdk@^2.948.0: - version "2.1132.0" - resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1132.0.tgz#0cb615e97db5a5133914ba0f2bdc8ea10eef4069" - integrity sha512-NPDesfTrNx8UMQ5VuosQNlFFFhswJ8cGVcVltZBXKVl1kW0BCp52XQBySSruIznaRX7vG6Ir2+nox0NdL05qBQ== + version "2.1133.0" + resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1133.0.tgz#db530faaa8dbe98503fd600b4e95efdd1c3996c7" + integrity sha512-TK7lcDxkrAc15Lmx030u4Wn3P798oxBVs79DBsD8+cZcW5Y8Gvat+evIZpkDoqh8YsfegvlI2jFg4ECQxKYPVA== dependencies: buffer "4.9.2" events "1.1.1" @@ -11108,9 +11108,9 @@ dompurify@=2.3.3: integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg== dompurify@^2.2.7, dompurify@^2.2.9, dompurify@^2.3.6: - version "2.3.6" - resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875" - integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg== + version "2.3.8" + resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f" + integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw== domutils@^2.5.2, domutils@^2.6.0: version "2.8.0" @@ -14111,9 +14111,9 @@ immer@9.0.7: integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== immer@^9.0.1, immer@^9.0.7: - version "9.0.13" - resolved "https://registry.npmjs.org/immer/-/immer-9.0.13.tgz#cee90eb7d46513d58daab0d7c2e6ef69aa4c1786" - integrity sha512-LufFIoBO2q3CZoUiObiV6ICK7QsrxOIo8ReQHggmYHA7aSbQdsAiZ+mkR3cjRD5ZxC/0P6EMdx2kk6oj1qyFLw== + version "9.0.14" + resolved "https://registry.npmjs.org/immer/-/immer-9.0.14.tgz#e05b83b63999d26382bb71676c9d827831248a48" + integrity sha512-ubBeqQutOSLIFCUBN03jGeOS6a3DoYlSYwYJTa+gSKEZKU5redJIqkIdZ3JVv/4RZpfcXdAWH5zCNLWPRv2WDw== immutable@^3.x.x: version "3.8.2" @@ -25857,9 +25857,9 @@ zip-stream@^4.1.0: readable-stream "^3.6.0" zod@^3.11.6, zod@^3.9.5: - version "3.15.1" - resolved "https://registry.npmjs.org/zod/-/zod-3.15.1.tgz#9e404cd8002ccffb03baa94cff2e1638ed49d82f" - integrity sha512-WAdjcoOxa4S9oc/u7fTbC3CC7uVqptLLU0LKqS8RDBOrCXp2t5avM8BUfgNVZJymGWAx6SEUYxWPPoYuQ5rgwQ== + version "3.16.0" + resolved "https://registry.npmjs.org/zod/-/zod-3.16.0.tgz#edfdbf77fcc9a5af13a2630a44bdda5b90e759b7" + integrity sha512-szrIkryADbTM+xBt2a1KoS2CJQXec4f9xG78bj5MJeEH/XqmmHpnO+fG3IE115AKBJak+2HrbxLZkc9mhdbDKA== zustand@3.6.9: version "3.6.9"