feat(plugin): Add graphql-voyager as new backstage plugin

Signed-off-by: MitchWijt <mitchel@wijt.net>
This commit is contained in:
MitchWijt
2023-01-27 16:25:35 +01:00
parent f4e4f04152
commit 3b5990bca1
20 changed files with 908 additions and 0 deletions
@@ -0,0 +1,13 @@
---
title: GraphQL Voyager
author: PostNL
authorUrl: https://github.com/postnl
category: Debugging
description: Integrates the GraphQL Voyager tool inside Backstage.
documentation: https://github.com/backstage/backstage/tree/master/plugins/graphql-voyager
iconUrl: https://res.cloudinary.com/apideck/image/upload/v1612724234/icons/graphql-voyager.png
npmPackageName: '@backstage/plugin-graphql-voyager'
tags:
- graphql
- graphql-voyager
addedDate: '2023-01-27'
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+75
View File
@@ -0,0 +1,75 @@
# graphql-voyager
Welcome to the graphql-voyager plugin!
_This plugin was created through the Backstage CLI_
## Getting started
### Installing
To get started, first install the plugin with the following command:
```bash
# From your Backstage root directory
yarn add --cwd packages/app @backstage/plugin-graphql-voyager
```
### Adding the page
In order to be able to navigate to the graphQL voyager page, a new route needs to be added to the app. This can be done in the following way:
```tsx
// packages/app/App.tsx
import { GraphQLVoyagerPage } from "@backstage/plugin-graphql-voyager";
const routes = (
<FlatRoutes>
<Route path="/graphql-voyager" element={<GraphqlVoyagerPage/>}/>
```
### Configuration
In order for the plugin to function correctly. GraphQL endpoints need to be added / configured through the GraphQLVoyager API. This can be done by implementing the `graphQlVoyagerApiRef` exported by this plugin.
Add your own configuration to the `packages/app/src/apis.ts` file the following way:
```ts
import { identityApiRef } from './IdentityApi';
import { GraphQLVoyagerEndpoints } from './GraphQLVoyagerEndpoints';
export const apis: AnyApiFactory[] = [
createApiFactory({
api: graphQlVoyagerApiRef,
deps: { identityApi: identityApiRef },
factory: ({ identityApiRef }) => {
GraphQLVoyagerEndpoints.from([
{
id: `graphql-voyager-endpoint-id`,
title: 'endpoint-title',
introspectionErrorMessage:
'Unable to perform introspection, make sure you are on the correct environment.',
introspection: async (query: any) => {
const token = 'someSecretJWTComingFromIdentityApiRef';
const res = await fetch('graphQLEndpoint', {
method: 'POST',
body: JSON.stringify({ query }),
headers: {
'Content-Type': 'application/json',
Authorization: token,
},
});
return res.json();
},
voyagerProps: {
hideDocs: true,
},
},
]);
},
}),
];
```
+64
View File
@@ -0,0 +1,64 @@
## API Report File for "@backstage/plugin-graphql-voyager"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { RouteRef } from '@backstage/core-plugin-api';
import { VoyagerDisplayOptions } from 'graphql-voyager/typings/components/Voyager';
import { WorkerCallback } from 'graphql-voyager/typings/utils/types';
// @public (undocumented)
export type GraphQLVoyagerApi = {
getEndpoints(): Promise<GraphQLVoyagerEndpoint[]>;
};
// @public (undocumented)
export const graphQlVoyagerApiRef: ApiRef<GraphQLVoyagerApi>;
// @public (undocumented)
export type GraphQLVoyagerEndpoint = {
id: string;
title: string;
introspection: (body: any) => Promise<any>;
introspectionErrorMessage: string;
voyagerProps?: {
displayOptions?: VoyagerDisplayOptions;
hideDocs?: boolean;
hideSettings?: boolean;
workerURI?: string;
loadWorker?: WorkerCallback;
};
};
// @public (undocumented)
export class GraphQLVoyagerEndpoints implements GraphQLVoyagerApi {
// (undocumented)
static from(endpoints: GraphQLVoyagerEndpoint[]): GraphQLVoyagerEndpoints;
// (undocumented)
getEndpoints(): Promise<GraphQLVoyagerEndpoint[]>;
}
// @public (undocumented)
export const GraphqlVoyagerPage: () => JSX.Element;
// @public (undocumented)
export const graphqlVoyagerPlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{},
{}
>;
// @public (undocumented)
export const introspectionQuery: string;
// @public (undocumented)
export const Router: () => JSX.Element;
// (No @packageDocumentation comment for this package)
```
+53
View File
@@ -0,0 +1,53 @@
{
"name": "@backstage/plugin-graphql-voyager",
"description": "Backstage plugin for GraphQL Voyager",
"version": "0.1.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"
},
"backstage": {
"role": "frontend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-components": "workspace:^",
"@backstage/core-plugin-api": "workspace:^",
"@backstage/theme": "workspace:^",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"graphql-voyager": "^1.0.0-rc.31",
"react-use": "^17.2.4"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/dev-utils": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/node": "*",
"cross-fetch": "^3.1.5",
"msw": "^0.47.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,94 @@
/*
* 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 React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { GraphQLVoyagerBrowser } from './GraphQLVoyagerBrowser';
jest.mock('graphql-voyager', () => ({ Voyager: () => '<Voyager />' }));
describe('GraphQLVoyagerBrowser', () => {
it('should render error text if there are no endpoints', async () => {
const rendered = await renderInTestApp(
<GraphQLVoyagerBrowser endpoints={[]} />,
);
expect(rendered.getByText('No endpoints available')).toBeInTheDocument();
});
it('should render endpoint tabs', async () => {
const rendered = await renderInTestApp(
<GraphQLVoyagerBrowser
endpoints={[
{
id: 'a',
title: 'Endpoint A',
introspectionErrorMessage: 'Failed to introspect A',
async introspection() {
return {
data: {
_schema: {},
},
};
},
voyagerProps: {
hideDocs: true,
},
},
{
id: 'b',
title: 'Endpoint B',
introspectionErrorMessage: 'Failed to introspect B',
async introspection() {
return {
data: {
_schema: {},
},
};
},
voyagerProps: {
hideDocs: true,
},
},
]}
/>,
);
expect(rendered.getByText('Endpoint A')).toBeInTheDocument();
expect(rendered.getByText('Endpoint B')).toBeInTheDocument();
});
it('Should render the introspection error message when introspection fails', async () => {
const rendered = await renderInTestApp(
<GraphQLVoyagerBrowser
endpoints={[
{
id: 'a',
title: 'Endpoint A',
introspectionErrorMessage: 'Failed to introspect A',
async introspection() {
return {};
},
voyagerProps: {
hideDocs: true,
},
},
]}
/>,
);
expect(rendered.getByText('Failed to introspect A')).toBeInTheDocument();
});
});
@@ -0,0 +1,131 @@
/*
* 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 {
GraphQLVoyagerEndpoint,
introspectionQuery,
} from '../../lib/api/types';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { Divider, makeStyles, Tab, Tabs, Typography } from '@material-ui/core';
import React, { Suspense } from 'react';
import { BackstageTheme } from '@backstage/theme';
import { Content, Progress } from '@backstage/core-components';
import { Voyager } from 'graphql-voyager';
const useStyles = makeStyles<BackstageTheme>(theme => ({
root: {
height: '100%',
display: 'flex',
flexFlow: 'column nowrap',
},
tabs: {
background: theme.palette.background.paper,
},
}));
type GraphQLVoyagerBrowserProps = {
endpoints: GraphQLVoyagerEndpoint[];
};
type IntrospectionResult = {
data?: Object;
};
const NoEndpoints = () => {
return <Typography variant="h4">No endpoints available</Typography>;
};
const VoyagerBrowser = (props: GraphQLVoyagerBrowserProps) => {
const { endpoints } = props;
const [tabIndex, setTabIndex] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [introspectionResult, setIntrospectionResult]: [
IntrospectionResult,
Dispatch<SetStateAction<IntrospectionResult>>,
] = useState({});
const classes = useStyles();
const {
voyagerProps = {},
introspectionErrorMessage,
introspection,
} = endpoints[tabIndex];
useEffect(() => {
const fetchIntrospection = async () => {
setIsLoading(true);
const data = await introspection(introspectionQuery);
setIntrospectionResult(data);
setIsLoading(false);
};
fetchIntrospection();
}, [tabIndex, introspection]);
let voyagerContent: JSX.Element;
if (isLoading) {
voyagerContent = <Typography variant="body1">Loading...</Typography>;
} else if (!introspectionResult.data) {
voyagerContent = (
<Typography variant="h4" color="error">
{introspectionErrorMessage}
</Typography>
);
} else {
voyagerContent = (
<Voyager
key={tabIndex}
introspection={introspectionResult}
{...voyagerProps}
/>
);
}
return (
<div className={classes.root}>
<Suspense fallback={<Progress />}>
<Tabs
classes={{ root: classes.tabs }}
value={tabIndex}
onChange={(_, value) => setTabIndex(value)}
indicatorColor="primary"
>
{endpoints.map(({ title }, index) => (
<Tab key={index} label={title} value={index} />
))}
</Tabs>
<Divider />
<Content>{voyagerContent}</Content>
</Suspense>
</div>
);
};
/** @public */
export const GraphQLVoyagerBrowser = (props: GraphQLVoyagerBrowserProps) => {
const hasEndpoints = checkEndpoints(props);
if (!hasEndpoints) {
return <NoEndpoints />;
}
return <VoyagerBrowser {...props} />;
};
function checkEndpoints(props: GraphQLVoyagerBrowserProps) {
return props.endpoints.length;
}
@@ -0,0 +1,16 @@
/*
* 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.
*/
export * from './GraphQLVoyagerBrowser';
@@ -0,0 +1,64 @@
/*
* 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 React from 'react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { GraphQLVoyagerPage } from './GraphQLVoyagerPage';
import { GraphQLVoyagerApi, graphQlVoyagerApiRef } from '../../lib/api';
jest.mock('../GraphQLVoyagerBrowser', () => ({
GraphQLVoyagerBrowser: () => '<GraphQLVoyagerBrowser />',
}));
describe('GraphQLVoyagerPage', () => {
it('should show error when no endpoints are loaded', async () => {
const loadingApi: GraphQLVoyagerApi = {
async getEndpoints() {
throw new Error('No endpoints');
},
};
const rendered = await renderInTestApp(
<TestApiProvider apis={[[graphQlVoyagerApiRef, loadingApi]]}>
<GraphQLVoyagerPage />
</TestApiProvider>,
);
expect(rendered.getByText('Welcome to Voyager!')).toBeInTheDocument();
expect(
rendered.getByText(
'Failed to load GraphQL endpoints, Error: No endpoints',
),
).toBeInTheDocument();
});
it('should show GraphQLVoyagerBrowser', async () => {
const loadingApi: GraphQLVoyagerApi = {
async getEndpoints() {
return [];
},
};
const rendered = await renderInTestApp(
<TestApiProvider apis={[[graphQlVoyagerApiRef, loadingApi]]}>
<GraphQLVoyagerPage />
</TestApiProvider>,
);
expect(rendered.getByText('Welcome to Voyager!')).toBeInTheDocument();
expect(rendered.getByText('<GraphQLVoyagerBrowser />')).toBeInTheDocument();
});
});
@@ -0,0 +1,62 @@
/*
* 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 React from 'react';
import { Header, Page, Content, Progress } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import 'graphql-voyager/dist/voyager.css';
import { graphQlVoyagerApiRef } from '../../lib/api';
import useAsync from 'react-use/lib/useAsync';
import { Typography } from '@material-ui/core';
import { GraphQLVoyagerBrowser } from '../GraphQLVoyagerBrowser';
/** @public */
export const GraphQLVoyagerPage = () => {
const graphQLVoyagerApi = useApi(graphQlVoyagerApiRef);
const { value, loading, error } = useAsync(() =>
graphQLVoyagerApi.getEndpoints(),
);
let content: JSX.Element;
if (loading) {
content = (
<Content>
<Progress />
</Content>
);
} else if (error) {
content = (
<Content>
<Typography variant="h4" color="error">
Failed to load GraphQL endpoints, {String(error)}
</Typography>
</Content>
);
} else {
content = (
<Content noPadding>
<GraphQLVoyagerBrowser endpoints={value!} />
</Content>
);
}
return (
<Page themeId="tool">
<Header title="Welcome to Voyager!" />
{content}
</Page>
);
};
@@ -0,0 +1,16 @@
/*
* 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.
*/
export * from './GraphQLVoyagerPage';
@@ -0,0 +1,17 @@
/*
* 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.
*/
export * from './GraphQLVoyagerPage';
export * from './GraphQLVoyagerBrowser';
+18
View File
@@ -0,0 +1,18 @@
/*
* 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.
*/
export { graphqlVoyagerPlugin, GraphqlVoyagerPage } from './plugin';
export { GraphQLVoyagerPage as Router } from './components';
export * from './lib/api';
@@ -0,0 +1,29 @@
/*
* 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 { GraphQLVoyagerApi, GraphQLVoyagerEndpoint } from './types';
/** @public */
export class GraphQLVoyagerEndpoints implements GraphQLVoyagerApi {
private constructor(private readonly endpoints: GraphQLVoyagerEndpoint[]) {}
static from(endpoints: GraphQLVoyagerEndpoint[]) {
return new GraphQLVoyagerEndpoints(endpoints);
}
async getEndpoints() {
return this.endpoints;
}
}
@@ -0,0 +1,17 @@
/*
* 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.
*/
export * from './GraphQLVoyagerEndpoints';
export * from './types';
@@ -0,0 +1,141 @@
/*
* 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 { createApiRef } from '@backstage/core-plugin-api';
import { VoyagerDisplayOptions } from 'graphql-voyager/typings/components/Voyager';
import { WorkerCallback } from 'graphql-voyager/typings/utils/types';
/** @public */
export type GraphQLVoyagerEndpoint = {
id: string;
title: string;
introspection: (body: any) => Promise<any>;
introspectionErrorMessage: string;
voyagerProps?: {
displayOptions?: VoyagerDisplayOptions;
hideDocs?: boolean;
hideSettings?: boolean;
workerURI?: string;
loadWorker?: WorkerCallback;
};
};
/** @public */
export type GraphQLVoyagerApi = {
getEndpoints(): Promise<GraphQLVoyagerEndpoint[]>;
};
/** @public */
export const graphQlVoyagerApiRef = createApiRef<GraphQLVoyagerApi>({
id: 'plugin.graphqlvoyager.api',
});
/** @public */
export const introspectionQuery: string = `query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}`;
@@ -0,0 +1,22 @@
/*
* 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 { graphqlVoyagerPlugin } from './plugin';
describe('graphql-voyager', () => {
it('should export plugin', () => {
expect(graphqlVoyagerPlugin).toBeDefined();
});
});
+38
View File
@@ -0,0 +1,38 @@
/*
* 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 {
createPlugin,
createRoutableExtension,
} from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
/** @public */
export const graphqlVoyagerPlugin = createPlugin({
id: 'graphql-voyager',
routes: {
root: rootRouteRef,
},
});
/** @public */
export const GraphqlVoyagerPage = graphqlVoyagerPlugin.provide(
createRoutableExtension({
name: 'GraphqlVoyagerPage',
component: () => import('./components').then(m => m.GraphQLVoyagerPage),
mountPoint: rootRouteRef,
}),
);
+20
View File
@@ -0,0 +1,20 @@
/*
* 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 { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
id: 'graphql-voyager',
});
+17
View File
@@ -0,0 +1,17 @@
/*
* 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 '@testing-library/jest-dom';
import 'cross-fetch/polyfill';