@backstage/plugin-permission-react (#8215)
* Add @backstage/plugin-permission-react Signed-off-by: Joon Park <joonp@spotify.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-permission-react': minor
|
||||
---
|
||||
|
||||
Add @backstage/plugin-permission-react
|
||||
|
||||
@backstage/plugin-permission-react is a library containing utils for implementing permissions in your frontend Backstage plugins. See [the authorization PRFC](https://github.com/backstage/backstage/pull/7761) for more details.
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
# permission
|
||||
|
||||
**NOTE: THIS PACKAGE IS EXPERIMENTAL!**
|
||||
|
||||
Components and hooks to help implement permissions in Backstage frontend plugins. For more information, see the [authorization PRFC](https://github.com/backstage/backstage/pull/7761).
|
||||
@@ -0,0 +1,69 @@
|
||||
## API Report File for "@backstage/plugin-permission-react"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { ApiRef } from '@backstage/core-plugin-api';
|
||||
import { AuthorizeRequest } from '@backstage/plugin-permission-common';
|
||||
import { AuthorizeResponse } from '@backstage/plugin-permission-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
import { default as React_2 } from 'react';
|
||||
import { RouteProps } from 'react-router';
|
||||
|
||||
// @public (undocumented)
|
||||
export type AsyncPermissionResult = {
|
||||
loading: boolean;
|
||||
allowed: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class IdentityPermissionApi implements PermissionApi {
|
||||
// (undocumented)
|
||||
authorize(request: AuthorizeRequest): Promise<AuthorizeResponse>;
|
||||
// (undocumented)
|
||||
static create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}): IdentityPermissionApi;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type PermissionApi = {
|
||||
authorize(request: AuthorizeRequest): Promise<AuthorizeResponse>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const permissionApiRef: ApiRef<PermissionApi>;
|
||||
|
||||
// @public
|
||||
export const PermissionedRoute: ({
|
||||
permission,
|
||||
resourceRef,
|
||||
errorComponent,
|
||||
...props
|
||||
}: RouteProps & {
|
||||
permission: Permission;
|
||||
resourceRef?: string | undefined;
|
||||
errorComponent?:
|
||||
| React_2.ReactElement<any, string | React_2.JSXElementConstructor<any>>
|
||||
| null
|
||||
| undefined;
|
||||
}) => JSX.Element;
|
||||
|
||||
// @public
|
||||
export const usePermission: (
|
||||
permission: Permission,
|
||||
resourceRef?: string | undefined,
|
||||
) => AsyncPermissionResult;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@backstage/plugin-permission-react",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/permission-react"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-plugin-api": "^0.2.0",
|
||||
"@backstage/plugin-permission-common": "^0.2.0",
|
||||
"@types/react": "*",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"react": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.9.0",
|
||||
"@backstage/test-utils": "^0.1.22",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@types/jest": "^26.0.7"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2021 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 { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { PermissionApi } from './PermissionApi';
|
||||
import {
|
||||
AuthorizeRequest,
|
||||
AuthorizeResponse,
|
||||
PermissionClient,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { Config } from '@backstage/config';
|
||||
|
||||
/**
|
||||
* The default implementation of the PermissionApi, which simply calls the authorize method of the given
|
||||
* {@link @backstage/plugin-permission-common#PermissionClient}.
|
||||
* @public
|
||||
*/
|
||||
export class IdentityPermissionApi implements PermissionApi {
|
||||
private constructor(
|
||||
private readonly permissionClient: PermissionClient,
|
||||
private readonly identityApi: IdentityApi,
|
||||
) {}
|
||||
|
||||
static create({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
}) {
|
||||
const permissionClient = new PermissionClient({ discoveryApi, configApi });
|
||||
return new IdentityPermissionApi(permissionClient, identityApi);
|
||||
}
|
||||
|
||||
async authorize(request: AuthorizeRequest): Promise<AuthorizeResponse> {
|
||||
const response = await this.permissionClient.authorize([request], {
|
||||
token: await this.identityApi.getIdToken(),
|
||||
});
|
||||
return response[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
AuthorizeRequest,
|
||||
AuthorizeResponse,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
import { ApiRef, createApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* This API is used by various frontend utilities that allow developers to implement authorization wihtin their frontend
|
||||
* plugins. A plugin developer will likely not have to interact with this API or its implementations directly, but
|
||||
* rather with the aforementioned utility components/hooks.
|
||||
* @public
|
||||
*/
|
||||
export type PermissionApi = {
|
||||
authorize(request: AuthorizeRequest): Promise<AuthorizeResponse>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A Backstage ApiRef for the Permission API. See https://backstage.io/docs/api/utility-apis for more information on
|
||||
* Backstage ApiRefs.
|
||||
* @public
|
||||
*/
|
||||
export const permissionApiRef: ApiRef<PermissionApi> = createApiRef({
|
||||
id: 'plugin.permission.api',
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 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 { permissionApiRef } from './PermissionApi';
|
||||
export type { PermissionApi } from './PermissionApi';
|
||||
export { IdentityPermissionApi } from './IdentityPermissionApi';
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2021 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 { PermissionedRoute } from '.';
|
||||
import { usePermission } from '../hooks';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
jest.mock('../hooks', () => ({
|
||||
usePermission: jest.fn(),
|
||||
}));
|
||||
const mockUsePermission = usePermission as jest.MockedFunction<
|
||||
typeof usePermission
|
||||
>;
|
||||
|
||||
const permission = {
|
||||
name: 'access.something',
|
||||
attributes: { action: 'read' as const },
|
||||
};
|
||||
|
||||
describe('PermissionedRoute', () => {
|
||||
it('Does not render when loading', async () => {
|
||||
mockUsePermission.mockReturnValue({ loading: true, allowed: false });
|
||||
|
||||
const { queryByText } = await renderInTestApp(
|
||||
<PermissionedRoute
|
||||
permission={permission}
|
||||
element={<div>content</div>}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(queryByText('content')).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('Renders given element if authorized', async () => {
|
||||
mockUsePermission.mockReturnValue({ loading: false, allowed: true });
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<PermissionedRoute
|
||||
permission={permission}
|
||||
element={<div>content</div>}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('content')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Renders not found page if not authorized', async () => {
|
||||
mockUsePermission.mockReturnValue({ loading: false, allowed: false });
|
||||
|
||||
await expect(
|
||||
renderInTestApp(
|
||||
<PermissionedRoute
|
||||
permission={permission}
|
||||
element={<div>content</div>}
|
||||
/>,
|
||||
),
|
||||
).rejects.toThrowError('Reached NotFound Page');
|
||||
});
|
||||
|
||||
it('Renders custom error page if not authorized', async () => {
|
||||
mockUsePermission.mockReturnValue({ loading: false, allowed: false });
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<PermissionedRoute
|
||||
permission={permission}
|
||||
element={<div>content</div>}
|
||||
errorComponent={<h1>Custom Error</h1>}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByText('Custom Error')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2021 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, { ComponentProps, ReactElement } from 'react';
|
||||
import { Route } from 'react-router';
|
||||
import { useApp } from '@backstage/core-plugin-api';
|
||||
import { usePermission } from '../hooks';
|
||||
import { Permission } from '@backstage/plugin-permission-common';
|
||||
|
||||
/**
|
||||
* Returns a React Router Route which only renders the element when authorized. If unathorized, the Route will render a
|
||||
* NotFoundErrorPage (see {@link @backstage/core-app-api#AppComponents}).
|
||||
* @public
|
||||
*/
|
||||
export const PermissionedRoute = ({
|
||||
permission,
|
||||
resourceRef,
|
||||
errorComponent,
|
||||
...props
|
||||
}: ComponentProps<typeof Route> & {
|
||||
permission: Permission;
|
||||
resourceRef?: string;
|
||||
errorComponent?: ReactElement | null;
|
||||
}) => {
|
||||
const permissionResult = usePermission(permission, resourceRef);
|
||||
const app = useApp();
|
||||
const { NotFoundErrorPage } = app.getComponents();
|
||||
|
||||
let shownElement: ReactElement | null | undefined =
|
||||
errorComponent === undefined ? <NotFoundErrorPage /> : errorComponent;
|
||||
|
||||
if (permissionResult.loading) {
|
||||
shownElement = null;
|
||||
} else if (permissionResult.allowed) {
|
||||
shownElement = props.element;
|
||||
}
|
||||
|
||||
return <Route {...props} element={shownElement} />;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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 { PermissionedRoute } from './PermissionedRoute';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 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 { usePermission } from './usePermission';
|
||||
export type { AsyncPermissionResult } from './usePermission';
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2021 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, { FC } from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { usePermission } from './usePermission';
|
||||
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { permissionApiRef } from '../apis';
|
||||
|
||||
const mockAuthorize = jest.fn();
|
||||
|
||||
const permission = {
|
||||
name: 'access.something',
|
||||
attributes: { action: 'read' as const },
|
||||
};
|
||||
|
||||
const TestComponent: FC = () => {
|
||||
const { loading, allowed, error } = usePermission(permission);
|
||||
return (
|
||||
<div>
|
||||
{loading && 'loading'}
|
||||
{error && 'error'}
|
||||
{allowed ? 'content' : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe('usePermission', () => {
|
||||
it('Returns loading when permissionApi has not yet responded.', () => {
|
||||
mockAuthorize.mockReturnValueOnce(new Promise(() => {}));
|
||||
|
||||
const { getByText } = render(
|
||||
<TestApiProvider
|
||||
apis={[[permissionApiRef, { authorize: mockAuthorize }]]}
|
||||
>
|
||||
<TestComponent />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(mockAuthorize).toHaveBeenCalledWith({ permission });
|
||||
expect(getByText('loading')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Returns allowed when permissionApi allows authorization.', async () => {
|
||||
mockAuthorize.mockResolvedValueOnce({ result: AuthorizeResult.ALLOW });
|
||||
|
||||
const { findByText } = render(
|
||||
<TestApiProvider
|
||||
apis={[[permissionApiRef, { authorize: mockAuthorize }]]}
|
||||
>
|
||||
<TestComponent />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(mockAuthorize).toHaveBeenCalledWith({ permission });
|
||||
expect(await findByText('content')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Returns not allowed when permissionApi denies authorization.', async () => {
|
||||
mockAuthorize.mockResolvedValueOnce({ result: AuthorizeResult.DENY });
|
||||
|
||||
const { findByText } = render(
|
||||
<TestApiProvider
|
||||
apis={[[permissionApiRef, { authorize: mockAuthorize }]]}
|
||||
>
|
||||
<TestComponent />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(mockAuthorize).toHaveBeenCalledWith({ permission });
|
||||
await expect(findByText('content')).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2021 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 { useAsync } from 'react-use';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { permissionApiRef } from '../apis';
|
||||
import {
|
||||
AuthorizeResult,
|
||||
Permission,
|
||||
} from '@backstage/plugin-permission-common';
|
||||
|
||||
/** @public */
|
||||
export type AsyncPermissionResult = {
|
||||
loading: boolean;
|
||||
allowed: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
/**
|
||||
* React hook utlity for authorization. Given a {@link @backstage/plugin-permission-common#Permission} and an optional
|
||||
* resourceRef, it will return whether or not access is allowed (for the given resource, if resourceRef is provided). See
|
||||
* {@link @backstage/plugin-permission-common/PermissionClient#authorize} for more details.
|
||||
* @public
|
||||
*/
|
||||
export const usePermission = (
|
||||
permission: Permission,
|
||||
resourceRef?: string,
|
||||
): AsyncPermissionResult => {
|
||||
const permissionApi = useApi(permissionApiRef);
|
||||
|
||||
const { loading, error, value } = useAsync(async () => {
|
||||
const { result } = await permissionApi.authorize({
|
||||
permission,
|
||||
resourceRef,
|
||||
});
|
||||
|
||||
return result;
|
||||
}, [permissionApi, permission, resourceRef]);
|
||||
|
||||
if (loading) {
|
||||
return { loading: true, allowed: false };
|
||||
}
|
||||
if (error) {
|
||||
return { error, loading: false, allowed: false };
|
||||
}
|
||||
return { loading: false, allowed: value === AuthorizeResult.ALLOW };
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 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 './components';
|
||||
export * from './hooks';
|
||||
export * from './apis';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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 '@testing-library/jest-dom';
|
||||
Reference in New Issue
Block a user