Merge pull request #646 from spotify/rugvip/graphiql

[Plugin] Add GraphiQL Plugin
This commit is contained in:
Patrik Oldsberg
2020-04-26 14:18:52 +02:00
committed by GitHub
23 changed files with 1333 additions and 6 deletions
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
rules: {
// Prefer to use rendered.getBy*, which will throw an error
'jest/expect-expect': 0,
},
};
+64
View File
@@ -0,0 +1,64 @@
# @backstage/plugin-graphiql
This plugin integrates [GraphiQL](https://github.com/graphql/graphiql) as a tool to browse GraphiQL endpoints inside Backstage.
The purpose of the plugin is to provide a convenient way for developers to try out GraphQL queries in their own environment.
By exposing GraphiQL as a plugin instead of a standalone app, it's possible to provide a preconfigured environment for engineers, and also tie into authentication providers already inside Backstage.
## Getting Started
### Installing the plugin
Start out by installing the plugin in your Backstage app:
```bash
yarn add @backstage/plugin-graphiql
```
Then add an entry to your App's `plugins.ts` to import the plugin.
The plugin registers a `/graphiql` route, which you can link to from the Sidebar if desired.
### Adding GraphQL endpoints
For the plugin to function, you need to supply GraphQL endpoints through the GraphQLBrowse API, which is done by implementing the `GraphQLBrowseApi` exported by this plugin.
If all you need is a static list of endpoints, the plugin exports a `GraphQLEndpoints` class that implements the `GraphQLBrowseApi` for you. Here's and example of how you could expose two GraphQL endpoints in your App:
```tsx
import {
graphQlBrowseApiRef,
GraphQLEndpoints,
} from '@backstage/plugin-graphiql';
// Implement the Graph QL browse API using a static list of endpoints
const graphQlBrowseApi = GraphQLEndpoints.from([
// Use the .create function if all you need is a static URL and headers.
GraphQLEndpoints.create({
id: 'gitlab',
title: 'GitLab',
url: 'https://gitlab.com/api/graphql',
// Optional extra headers
headers: { Extra: 'Header' },
}),
{
id: 'hooli-search',
title: 'Hooli Search',
// Custom fetch function, this one is equivalent to using GraphQLEndpoints.create()
// with url set to https://internal.hooli.com/search
fetcher: async (params: any) => {
return fetch('https://internal.hooli.com/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
}).then(res => res.json());
},
},
]);
// ApiRegistry builder created somewhere in your App
const builder = ApiRegistry.builder();
// Add the instance to the API registry
builder.add(graphQlBrowseApiRef, graphQlBrowseApi);
```
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright 2020 Spotify AB
*
* 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 ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import HomeIcon from '@material-ui/icons/Home';
import { ThemeProvider, CssBaseline } from '@material-ui/core';
import {
createApp,
SidebarPage,
Sidebar,
SidebarItem,
SidebarSpacer,
ApiRegistry,
} from '@backstage/core';
import { lightTheme } from '@backstage/theme';
import { plugin, GraphQLEndpoints, graphQlBrowseApiRef } from '../src';
const graphQlBrowseApi = GraphQLEndpoints.from([
GraphQLEndpoints.create({
id: 'gitlab',
title: 'GitLab',
url: 'https://gitlab.com/api/graphql',
}),
GraphQLEndpoints.create({
id: 'countries',
title: 'Countries',
url: 'https://countries.trevorblades.com/',
}),
]);
const app = createApp();
app.registerApis(ApiRegistry.from([[graphQlBrowseApiRef, graphQlBrowseApi]]));
app.registerPlugin(plugin);
const AppComponent = app.build();
const App: FC<{}> = () => {
return (
<ThemeProvider theme={lightTheme}>
<CssBaseline>
<BrowserRouter>
<SidebarPage>
<Sidebar>
<SidebarSpacer />
<SidebarItem icon={HomeIcon} to="/graphiql" text="Home" />
</Sidebar>
<AppComponent />
</SidebarPage>
</BrowserRouter>
</CssBaseline>
</ThemeProvider>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
+55
View File
@@ -0,0 +1,55 @@
{
"name": "@backstage/plugin-graphiql",
"description": "Backstage plugin for browsing GraphQL APIs",
"version": "0.1.1-alpha.4",
"private": false,
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/spotify/backstage/tree/master/plugins/graphiql#readme",
"repository": {
"type": "git",
"url": "https://github.com/spotify/backstage",
"directory": "plugins/graphiql"
},
"keywords": [
"backstage"
],
"license": "Apache-2.0",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"clean": "backstage-cli clean"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.4",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/testing-library__jest-dom": "5.0.2",
"jest-fetch-mock": "^3.0.3",
"react-router-dom": "^5.1.2"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.4",
"@backstage/test-utils": "^0.1.1-alpha.4",
"@backstage/theme": "^0.1.1-alpha.4",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"graphiql": "^1.0.0-alpha.8",
"graphql": "15.0.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-use": "^13.0.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,57 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { render } from '@testing-library/react';
import { GraphiQLBrowser } from './GraphiQLBrowser';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
jest.mock('graphiql', () => () => '<GraphiQL />');
describe('GraphiQLBrowser', () => {
it('should render error text if there are no endpoints', () => {
const rendered = render(
<ThemeProvider theme={lightTheme}>
<GraphiQLBrowser endpoints={[]} />
</ThemeProvider>,
);
rendered.getByText('No endpoints available');
});
it('should render endpoint tabs', () => {
const rendered = render(
<ThemeProvider theme={lightTheme}>
<GraphiQLBrowser
endpoints={[
{
id: 'a',
title: 'Endpoint A',
async fetcher() {},
},
{
id: 'b',
title: 'Endpoint B',
async fetcher() {},
},
]}
/>
</ThemeProvider>,
);
rendered.getByText('Endpoint A');
rendered.getByText('Endpoint B');
});
});
@@ -0,0 +1,77 @@
/*
* Copyright 2020 Spotify AB
*
* 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, useState } from 'react';
import { Tabs, Tab, makeStyles, Typography, Divider } from '@material-ui/core';
import 'graphiql/graphiql.css';
import GraphiQL from 'graphiql';
import { StorageBucket } from 'lib/storage';
import { GraphQLEndpoint } from 'lib/api';
import { BackstageTheme } from '@backstage/theme';
const useStyles = makeStyles<BackstageTheme>(theme => ({
root: {
height: '100%',
display: 'flex',
flexFlow: 'column nowrap',
},
tabs: {
background: theme.palette.background.paper,
},
graphiQlWrapper: {
flex: 1,
'@global': {
'.graphiql-container': {
boxSizing: 'initial',
},
},
},
}));
type GraphiQLBrowserProps = {
endpoints: GraphQLEndpoint[];
};
export const GraphiQLBrowser: FC<GraphiQLBrowserProps> = ({ endpoints }) => {
const classes = useStyles();
const [tabIndex, setTabIndex] = useState(0);
if (!endpoints.length) {
return <Typography variant="h4">No endpoints available</Typography>;
}
const { id, fetcher } = endpoints[tabIndex];
const storage = StorageBucket.forLocalStorage(`plugin/graphiql/data/${id}`);
return (
<div className={classes.root}>
<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 />
<div className={classes.graphiQlWrapper}>
<GraphiQL key={tabIndex} fetcher={fetcher} storage={storage} />
</div>
</div>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 './GraphiQLBrowser';
@@ -0,0 +1,88 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { GraphiQLPage } from './GraphiQLPage';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { ApiProvider, ApiRegistry } from '@backstage/core';
import { renderWithEffects } from '@backstage/test-utils';
import { GraphQLBrowseApi, graphQlBrowseApiRef } from 'lib/api';
jest.mock('components/GraphiQLBrowser', () => ({
GraphiQLBrowser: () => '<GraphiQLBrowser />',
}));
describe('GraphiQLPage', () => {
it('should show progress', async () => {
const loadingApi: GraphQLBrowseApi = {
async getEndpoints() {
await new Promise(() => {});
return [];
},
};
const rendered = await renderWithEffects(
<ApiProvider apis={ApiRegistry.from([[graphQlBrowseApiRef, loadingApi]])}>
<ThemeProvider theme={lightTheme}>
<GraphiQLPage />
</ThemeProvider>
,
</ApiProvider>,
);
rendered.getByText('GraphiQL');
rendered.getByTestId('progress');
});
it('should show error', async () => {
const loadingApi: GraphQLBrowseApi = {
async getEndpoints() {
throw new Error('NOPE');
},
};
const rendered = await renderWithEffects(
<ApiProvider apis={ApiRegistry.from([[graphQlBrowseApiRef, loadingApi]])}>
<ThemeProvider theme={lightTheme}>
<GraphiQLPage />
</ThemeProvider>
</ApiProvider>,
);
rendered.getByText('GraphiQL');
rendered.getByText('Failed to load GraphQL endpoints, Error: NOPE');
});
it('should show GraphiQLBrowser', async () => {
const loadingApi: GraphQLBrowseApi = {
async getEndpoints() {
return [];
},
};
const rendered = await renderWithEffects(
<ApiProvider apis={ApiRegistry.from([[graphQlBrowseApiRef, loadingApi]])}>
<ThemeProvider theme={lightTheme}>
<GraphiQLPage />
</ThemeProvider>
</ApiProvider>,
);
rendered.getByText('GraphiQL');
rendered.getByText('<GraphiQLBrowser />');
});
});
@@ -0,0 +1,71 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
Content,
Header,
HeaderLabel,
Page,
Progress,
pageTheme,
useApi,
} from '@backstage/core';
import { useAsync } from 'react-use';
import 'graphiql/graphiql.css';
import { graphQlBrowseApiRef } from 'lib/api';
import { GraphiQLBrowser } from 'components';
import { Typography } from '@material-ui/core';
export const GraphiQLPage: FC<{}> = () => {
const graphQlBrowseApi = useApi(graphQlBrowseApiRef);
const endpoints = useAsync(() => graphQlBrowseApi.getEndpoints());
let content: JSX.Element;
if (endpoints.loading) {
content = (
<Content>
<Progress />
</Content>
);
} else if (endpoints.error) {
content = (
<Content>
<Typography variant="h4" color="error">
{/* TODO: provide a proper error component */}
Failed to load GraphQL endpoints, {String(endpoints.error)}
</Typography>
</Content>
);
} else {
content = (
<Content noPadding>
<GraphiQLBrowser endpoints={endpoints.value!} />
</Content>
);
}
return (
<Page theme={pageTheme.tool}>
<Header title="GraphiQL">
<HeaderLabel label="Owner" value="Spotify" />
<HeaderLabel label="Lifecycle" value="Alpha" />
</Header>
{content}
</Page>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 './GraphiQLPage';
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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 './GraphiQLPage';
export * from './GraphiQLBrowser';
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { plugin } from './plugin';
export * from './lib/api';
@@ -0,0 +1,63 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { GraphQLBrowseApi, GraphQLEndpoint } from './types';
// Helper for generic http endpoints
export type EndpointConfig = {
id: string;
title: string;
// Endpoint URL
url: string;
// only supports POST right now
method?: 'POST';
// Defaults to setting Content-Type to application/json
headers?: { [name in string]: string };
};
export class GraphQLEndpoints implements GraphQLBrowseApi {
// Create a support
static create(config: EndpointConfig): GraphQLEndpoint {
const { id, title, url, method = 'POST' } = config;
return {
id,
title,
fetcher: async (params: any) => {
const body = JSON.stringify(params);
const headers = {
'Content-Type': 'application/json',
...config.headers,
};
const res = await fetch(url, {
method,
headers,
body,
});
return res.json();
},
};
}
static from(endpoints: GraphQLEndpoint[]) {
return new GraphQLEndpoints(endpoints);
}
private constructor(private readonly endpoints: GraphQLEndpoint[]) {}
async getEndpoints() {
return this.endpoints;
}
}
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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 './GraphQLEndpoints';
export * from './types';
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ApiRef } from '@backstage/core';
export type GraphQLEndpoint = {
// Will be used as unique key for storing history and query data
id: string;
// Displayed to the user to identify the source.
title: string;
// Method used send a GraphQL query.
// The body parameter is equivalent to the POST body to be JSON-serialized, and the
// return value should be the equivalent of a parsed JSON response from that POST.
fetcher: (body: any) => Promise<any>;
};
export type GraphQLBrowseApi = {
getEndpoints(): Promise<GraphQLEndpoint[]>;
};
export const graphQlBrowseApiRef = new ApiRef<GraphQLBrowseApi>({
id: 'plugin.graphiql.browse',
description: 'Used to supply GraphQL endpoints for browsing',
});
@@ -0,0 +1,143 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { StorageBucket } from './StorageBucket';
describe('StorageBucket', () => {
it('should forbid access to unknown keys', () => {
const bucket = StorageBucket.forStorage(localStorage, 'hello');
expect(() => {
bucket['dunno-this-one'] = 'nope';
}).toThrow('Direct property access is not allowed for StorageBuckets');
expect(() => {
return bucket['dunno-this-one'];
}).toThrow('Direct property access is not allowed for StorageBuckets');
});
it('should not implement all methods', () => {
const bucket = StorageBucket.forLocalStorage('hello');
expect(() => bucket.length).toThrow('Method not implemented.');
expect(() => bucket.key()).toThrow('Method not implemented.');
});
describe('with mocked underlying storage', () => {
const mockStorage = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
};
const bucket = StorageBucket.forStorage(
(mockStorage as unknown) as Storage,
'my-bucket',
);
afterEach(() => {
jest.resetAllMocks();
});
it('should set a first item', () => {
bucket.setItem('x', 'a');
expect(mockStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(1);
expect(mockStorage.setItem).toHaveBeenLastCalledWith(
'my-bucket',
JSON.stringify({ x: 'a' }),
);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
});
it('should set a second item', () => {
mockStorage.getItem.mockReturnValueOnce(JSON.stringify({ y: 'b' }));
bucket.setItem('x', 'a');
expect(mockStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(1);
expect(mockStorage.setItem).toHaveBeenLastCalledWith(
'my-bucket',
JSON.stringify({ y: 'b', x: 'a' }),
);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
});
it('should clear the bucket', () => {
bucket.clear();
expect(mockStorage.getItem).toHaveBeenCalledTimes(0);
expect(mockStorage.setItem).toHaveBeenCalledTimes(0);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(1);
expect(mockStorage.removeItem).toHaveBeenLastCalledWith('my-bucket');
});
it('should get an item', () => {
mockStorage.getItem.mockReturnValueOnce(JSON.stringify({ x: 'X' }));
expect(bucket.getItem('x')).toBe('X');
expect(mockStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(0);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
});
it('should remove an item', () => {
mockStorage.getItem.mockReturnValueOnce(
JSON.stringify({ x: 'X', y: 'Y' }),
);
bucket.removeItem('x');
expect(mockStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(1);
expect(mockStorage.setItem).toHaveBeenLastCalledWith(
'my-bucket',
JSON.stringify({ y: 'Y' }),
);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
});
it('should not bother to write when deleting a missing key', () => {
mockStorage.getItem.mockReturnValueOnce(JSON.stringify({ y: 'Y' }));
bucket.removeItem('x');
expect(mockStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(0);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
});
it('should ignore bad data', () => {
mockStorage.getItem.mockReturnValue('derp');
expect(bucket.getItem('x')).toBe(null);
expect(mockStorage.getItem).toHaveBeenCalledTimes(1);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(0);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
bucket.removeItem('x');
expect(mockStorage.getItem).toHaveBeenCalledTimes(2);
expect(mockStorage.getItem).toHaveBeenLastCalledWith('my-bucket');
expect(mockStorage.setItem).toHaveBeenCalledTimes(0);
expect(mockStorage.removeItem).toHaveBeenCalledTimes(0);
});
});
});
@@ -0,0 +1,102 @@
/*
* Copyright 2020 Spotify AB
*
* 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.
*/
type BucketData = { [key in string]: string };
export class StorageBucket implements Storage {
private static noPropAccessProxyHandler: ProxyHandler<StorageBucket> = {
get(target, prop) {
if (prop in target) {
return target[prop as any];
}
throw new Error(
'Direct property access is not allowed for StorageBuckets',
);
},
set() {
throw new Error(
'Direct property access is not allowed for StorageBuckets',
);
},
};
static forStorage(storage: Storage, bucket: string) {
const storageBucket = new StorageBucket(storage, bucket);
return new Proxy(storageBucket, StorageBucket.noPropAccessProxyHandler);
}
static forLocalStorage(bucket: string): StorageBucket {
return StorageBucket.forStorage(localStorage, bucket);
}
private constructor(
private readonly storage: Storage,
private readonly bucket: string,
) {}
[name: string]: any;
get length(): number {
throw new Error('Method not implemented.');
}
clear(): void {
this.storage.removeItem(this.bucket);
}
getItem(key: string): string | null {
return this.read()?.[key] ?? null;
}
key(): never {
throw new Error('Method not implemented.');
}
removeItem(key: string): void {
const data = this.read();
if (!data) {
return;
}
if (key in data) {
delete data[key];
this.write(data);
}
}
setItem(key: string, value: string): void {
const data = this.read() ?? {};
data[key] = value;
this.write(data);
}
private read(): BucketData | undefined {
const bucketValue = this.storage.getItem(this.bucket);
if (!bucketValue) {
return undefined;
}
try {
return JSON.parse(bucketValue);
} catch {
return undefined;
}
}
private write(data: BucketData) {
this.storage.setItem(this.bucket, JSON.stringify(data));
}
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 './StorageBucket';
+23
View File
@@ -0,0 +1,23 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { plugin } from './plugin';
describe('graphiql', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
});
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright 2020 Spotify AB
*
* 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 } from '@backstage/core';
import { GraphiQLPage } from './components';
export const plugin = createPlugin({
id: 'graphiql',
register({ router }) {
router.registerRoute('/graphiql', GraphiQLPage);
},
});
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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/extend-expect';
require('jest-fetch-mock').enableMocks();
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "dev"],
"compilerOptions": {
"baseUrl": "src"
}
}
+320 -6
View File
@@ -1166,7 +1166,7 @@
"@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5"
"@emotion/core@^10.0.20":
"@emotion/core@^10.0.0", "@emotion/core@^10.0.20", "@emotion/core@^10.0.28":
version "10.0.28"
resolved "https://registry.npmjs.org/@emotion/core/-/core-10.0.28.tgz#bb65af7262a234593a9e952c041d0f1c9b9bef3d"
integrity sha512-pH8UueKYO5jgg0Iq+AmCLxBsvuGtvlmiDCOuv8fGNYn3cowFpLN98L8zO56U0H1PjDIyAlXymgL3Wu7u7v6hbA==
@@ -1192,14 +1192,14 @@
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/is-prop-valid@0.8.8":
"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.1":
version "0.8.8"
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
dependencies:
"@emotion/memoize" "0.7.4"
"@emotion/memoize@0.7.4":
"@emotion/memoize@0.7.4", "@emotion/memoize@^0.7.1":
version "0.7.4"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
@@ -1230,7 +1230,7 @@
"@emotion/serialize" "^0.11.15"
"@emotion/utils" "0.11.3"
"@emotion/styled@^10.0.17":
"@emotion/styled@^10.0.0", "@emotion/styled@^10.0.17":
version "10.0.27"
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-10.0.27.tgz#12cb67e91f7ad7431e1875b1d83a94b814133eaf"
integrity sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q==
@@ -2491,6 +2491,11 @@
prop-types "^15.7.2"
react-is "^16.8.0"
"@mdx-js/react@^1.0.0", "@mdx-js/react@^1.5.2":
version "1.5.9"
resolved "https://registry.npmjs.org/@mdx-js/react/-/react-1.5.9.tgz#31873ab097fbe58c61c7585fc0be64e83182b6df"
integrity sha512-rengdUSedIdIQbXPSeafItCacTYocARAjUA51b6R1KNHmz+59efz7UmyTKr73viJQZ98ouu7iRGmOTtjRrbbWA==
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -3401,6 +3406,105 @@
telejson "^3.2.0"
util-deprecate "^1.0.2"
"@styled-system/background@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz#75c63d06b497ab372b70186c0bf608d62847a2ba"
integrity sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/border@^5.1.5":
version "5.1.5"
resolved "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz#0493d4332d2b59b74bb0d57d08c73eb555761ba6"
integrity sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/color@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz#b8d6b4af481faabe4abca1a60f8daa4ccc2d9f43"
integrity sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/core@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz#b8b7b86455d5a0514f071c4fa8e434b987f6a772"
integrity sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==
dependencies:
object-assign "^4.1.1"
"@styled-system/css@^5.1.5":
version "5.1.5"
resolved "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz#0460d5f3ff962fa649ea128ef58d9584f403bbbc"
integrity sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==
"@styled-system/flexbox@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz#077090f43f61c3852df63da24e4108087a8beecf"
integrity sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/grid@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz#7165049877732900b99cd00759679fbe45c6c573"
integrity sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/layout@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz#12d73e79887e10062f4dbbbc2067462eace42339"
integrity sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/position@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz#56961266566836f57a24d8e8e33ce0c1adb59dd3"
integrity sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/shadow@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz#beddab28d7de03cd0177a87ac4ed3b3b6d9831fd"
integrity sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/should-forward-prop@^5.1.2":
version "5.1.5"
resolved "https://registry.npmjs.org/@styled-system/should-forward-prop/-/should-forward-prop-5.1.5.tgz#c392008c6ae14a6eb78bf1932733594f7f7e5c76"
integrity sha512-+rPRomgCGYnUIaFabDoOgpSDc4UUJ1KsmlnzcEp0tu5lFrBQKgZclSo18Z1URhaZm7a6agGtS5Xif7tuC2s52Q==
dependencies:
"@emotion/is-prop-valid" "^0.8.1"
"@emotion/memoize" "^0.7.1"
styled-system "^5.1.5"
"@styled-system/space@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz#38925d2fa29a41c0eb20e65b7c3efb6e8efce953"
integrity sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/typography@^5.1.2":
version "5.1.2"
resolved "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz#65fb791c67d50cd2900d234583eaacdca8c134f7"
integrity sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/variant@^5.1.5":
version "5.1.5"
resolved "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz#8446d8aad06af3a4c723d717841df2dbe4ddeafd"
integrity sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==
dependencies:
"@styled-system/core" "^5.1.2"
"@styled-system/css" "^5.1.5"
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
version "4.2.0"
resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
@@ -3566,6 +3670,61 @@
resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-7.2.1.tgz#2ad4e844175a3738cb9e7064be5ea070b8863a1c"
integrity sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==
"@theme-ui/color-modes@^0.3.1":
version "0.3.1"
resolved "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.3.1.tgz#8c2b18fb170e6c998287b7381240a8b9cca8b0d1"
integrity sha512-WuZGgFW7M5wOWSse1PVZCEfM0OZip15/D6U3bB3B9KmWax7qiSnAm1yAMLRQKC+QYhndrjq3xU+WAQm11KnhIw==
dependencies:
"@emotion/core" "^10.0.0"
"@theme-ui/core" "^0.3.1"
"@theme-ui/css" "^0.3.1"
deepmerge "^4.2.2"
"@theme-ui/components@^0.3.1":
version "0.3.1"
resolved "https://registry.npmjs.org/@theme-ui/components/-/components-0.3.1.tgz#fe023e156c1e1c076d5f2258466426e94adc2765"
integrity sha512-uG4dUM61s4tWv6N34uxs5VIh24bJyA/7TrYJ75WDiI+s72zbcNG7aGRpvX/hSZnAhxjdXpuskdEM3eEgOabdEg==
dependencies:
"@emotion/core" "^10.0.0"
"@emotion/styled" "^10.0.0"
"@styled-system/color" "^5.1.2"
"@styled-system/should-forward-prop" "^5.1.2"
"@styled-system/space" "^5.1.2"
"@theme-ui/css" "^0.3.1"
"@theme-ui/core@^0.3.1":
version "0.3.1"
resolved "https://registry.npmjs.org/@theme-ui/core/-/core-0.3.1.tgz#dbe9800b9d6e923e1a7417e6adebce21524f8c02"
integrity sha512-cK6EVSOx0Kyx1Xpi4qb0JTLIxywx0DRh+53Ln1foXMplF2qKaDsFi3vD6duHIlT331E3CNOa9dftHHNM7y4rbA==
dependencies:
"@emotion/core" "^10.0.0"
"@theme-ui/css" "^0.3.1"
deepmerge "^4.2.2"
"@theme-ui/css@^0.3.1":
version "0.3.1"
resolved "https://registry.npmjs.org/@theme-ui/css/-/css-0.3.1.tgz#b85c7e8fae948dc0de65aa30b853368993e25cb3"
integrity sha512-QB2/fZBpo4inaLHL3OrB8NOBgNfwnj8GtHzXWHb9iQSRjmtNX8zPXBe32jLT7qQP0+y8JxPT4YChZIkm5ZyIdg==
"@theme-ui/mdx@^0.3.0":
version "0.3.0"
resolved "https://registry.npmjs.org/@theme-ui/mdx/-/mdx-0.3.0.tgz#8bb1342204acfaa69914d6b6567c5c49d9a8c1e6"
integrity sha512-/GHBNKqmUptWwkmF+zIASVQtjYs81XMEwtqPCHnHuaaCzhZxcXrtCwvcAgmCXF8hpRttCXVVxw1X3Gt0mhzaTQ==
dependencies:
"@emotion/core" "^10.0.0"
"@emotion/styled" "^10.0.0"
"@mdx-js/react" "^1.0.0"
"@theme-ui/theme-provider@^0.3.1":
version "0.3.1"
resolved "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.3.1.tgz#910bc43454fd61b1047d7bb0dce05e36ffb6b44b"
integrity sha512-Sjj6lD0gPxBi+hcGCkawcGZECeESV/mW2YfmPqjNgmc296x5tulfNc+0/N5CJwLVOmnkn8zR5KNWZ8BjndfeTg==
dependencies:
"@emotion/core" "^10.0.0"
"@theme-ui/color-modes" "^0.3.1"
"@theme-ui/core" "^0.3.1"
"@theme-ui/mdx" "^0.3.0"
"@tootallnate/once@1":
version "1.0.0"
resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.0.0.tgz#9c13c2574c92d4503b005feca8f2e16cc1611506"
@@ -4483,6 +4642,11 @@
resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.4.tgz#a7dce20b7465bcad29cd6bbb557695e4ea7863cb"
integrity sha512-o12FCQt/X5n3pgKEWGpt0f/7Eg4mfv3uRwPUrctiOT8ZuxbH3cNLGWfH/8y6KxVJg4L2885ucuXQ6XECZzUiJA==
"@xobotyi/scrollbar-width@1.9.5":
version "1.9.5"
resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -6473,6 +6637,19 @@ code-point-at@^1.0.0:
resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codemirror-graphql@^0.12.0-alpha.7:
version "0.12.0-alpha.7"
resolved "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-0.12.0-alpha.7.tgz#fb8a8e633f5e416a9736df2fa697312a0955db20"
integrity sha512-LOsHnAc/pWOqqUEfQX5tNn10jStqIe8il6/vHq7m81uyGcp7tx5Np3O7zwGKQ/HdewDUMjeqXnva5tFGYaKzVg==
dependencies:
graphql-language-service-interface "^2.4.0-alpha.7"
graphql-language-service-parser "^1.6.0-alpha.3"
codemirror@^5.52.2:
version "5.53.2"
resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.53.2.tgz#9799121cf8c50809cca487304e9de3a74d33f428"
integrity sha512-wvSQKS4E+P8Fxn/AQ+tQtJnF1qH5UOlxtugFLpubEZ5jcdH2iXTVinb+Xc/4QjshuOxRm4fUsU2QPF1JJKiyXA==
collapse-white-space@^1.0.2:
version "1.0.6"
resolved "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287"
@@ -7618,6 +7795,11 @@ deep-object-diff@^1.1.0:
resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a"
integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw==
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
default-gateway@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@@ -8188,7 +8370,7 @@ entities@^1.1.1, entities@^1.1.2:
resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
entities@^2.0.0:
entities@^2.0.0, entities@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
@@ -9968,6 +10150,55 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graphiql@^1.0.0-alpha.8:
version "1.0.0-alpha.8"
resolved "https://registry.npmjs.org/graphiql/-/graphiql-1.0.0-alpha.8.tgz#a9cc594a8666e372c919a5c86a32835bc37499ff"
integrity sha512-6cR4NUtR79P2ShXt2P5d4OSIlsqiHzb7fK27LSm4JHIx0uBLYWrlJwHcjjF+Ucujr3fbVn4miyNF+ypvMJp53g==
dependencies:
"@emotion/core" "^10.0.28"
"@mdx-js/react" "^1.5.2"
codemirror "^5.52.2"
codemirror-graphql "^0.12.0-alpha.7"
copy-to-clipboard "^3.2.0"
entities "^2.0.0"
markdown-it "^10.0.0"
regenerator-runtime "^0.13.5"
theme-ui "^0.3.1"
graphql-language-service-interface@^2.4.0-alpha.7:
version "2.4.0-alpha.7"
resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.4.0-alpha.7.tgz#5458f1f17a79dde5b51dab0d21eda63dff48a918"
integrity sha512-X5jwTI4AlcKB/t9/hZBn9O69uG9WPbkFnZivhNfztzeudKV+cXHYR6behsR47bR2nzF3YEu7Nv+3HExXKQAlWg==
dependencies:
graphql-language-service-parser "^1.6.0-alpha.3"
graphql-language-service-types "^1.6.0-alpha.5"
graphql-language-service-utils "^2.4.0-alpha.6"
vscode-languageserver-types "^3.15.1"
graphql-language-service-parser@^1.6.0-alpha.3:
version "1.6.0-alpha.3"
resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.6.0-alpha.3.tgz#671643018fb4b24f409997a05b021030948562fb"
integrity sha512-gx2u6b2m9X9DjvlfEjmBQ67GtZxPPWAtVb63BQ21u2MPxqYjKsBJkKLq9PiC2u57bJhv+h4MoL4OLJ/vkmq6Ig==
dependencies:
graphql-language-service-types "^1.6.0-alpha.5"
graphql-language-service-types@^1.6.0-alpha.5:
version "1.6.0-alpha.5"
resolved "https://registry.npmjs.org/graphql-language-service-types/-/graphql-language-service-types-1.6.0-alpha.5.tgz#113a92c7544d98c8a8b8c3e04c9f982d3dbe1a00"
integrity sha512-Q/pTif29xM6OiFV9RsYLwX2uDeCMdSG36n4G0aDvfbWxbOBj48vSlLL5R8Gpt/DjocG9Kkv51CZay6pMPtmAfg==
graphql-language-service-utils@^2.4.0-alpha.6:
version "2.4.0-alpha.6"
resolved "https://registry.npmjs.org/graphql-language-service-utils/-/graphql-language-service-utils-2.4.0-alpha.6.tgz#5c3b30150e4905517b84b71f84956851c2c883e1"
integrity sha512-/+crYcPhOoeu1qpHhH2FZpI7w4i3q4Kq7jzbodXxOZBAqJFd3FSHTnVBYEAfgTgkTbIC7yIXVwnIwXtqDFTNDQ==
dependencies:
graphql-language-service-types "^1.6.0-alpha.5"
graphql@15.0.0:
version "15.0.0"
resolved "https://registry.npmjs.org/graphql/-/graphql-15.0.0.tgz#042a5eb5e2506a2e2111ce41eb446a8e570b8be9"
integrity sha512-ZyVO1xIF9F+4cxfkdhOJINM+51B06Friuv4M66W7HzUOeFd+vNzUn4vtswYINPi6sysjf1M2Ri/rwZALqgwbaQ==
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -12910,6 +13141,13 @@ lines-and-columns@^1.1.6:
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
linkify-it@^2.0.0:
version "2.2.0"
resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf"
integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==
dependencies:
uc.micro "^1.0.1"
lint-staged@^10.0.4:
version "10.0.8"
resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-10.0.8.tgz#0f7849cdc336061f25f5d4fcbcfa385701ff4739"
@@ -13464,6 +13702,17 @@ markdown-escapes@^1.0.0:
resolved "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
markdown-it@^10.0.0:
version "10.0.0"
resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==
dependencies:
argparse "^1.0.7"
entities "~2.0.0"
linkify-it "^2.0.0"
mdurl "^1.0.1"
uc.micro "^1.0.5"
markdown-to-jsx@^6.9.1, markdown-to-jsx@^6.9.3:
version "6.11.0"
resolved "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.0.tgz#a2e3f2bc781c3402d8bb0f8e0a12a186474623b0"
@@ -13515,6 +13764,11 @@ mdn-data@2.0.6:
resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978"
integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
meant@~1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/meant/-/meant-1.0.1.tgz#66044fea2f23230ec806fb515efea29c44d2115d"
@@ -16967,6 +17221,25 @@ react-transition-group@^4.3.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-use@^13.0.0:
version "13.27.1"
resolved "https://registry.npmjs.org/react-use/-/react-use-13.27.1.tgz#e2ae2b708dafc7893c4772628801589aab9de370"
integrity sha512-bAwdqDMXs5lovEanXnL1izledfrPEUUv1afoTVB59eUiYcDyKul+M/dT/2WcgHjoY/R6QlrTcZoW4R7ifwvBfw==
dependencies:
"@types/js-cookie" "2.2.5"
"@xobotyi/scrollbar-width" "1.9.5"
copy-to-clipboard "^3.2.0"
fast-deep-equal "^3.1.1"
fast-shallow-equal "^1.0.0"
js-cookie "^2.2.1"
nano-css "^5.2.1"
resize-observer-polyfill "^1.5.1"
screenfull "^5.0.0"
set-harmonic-interval "^1.0.1"
throttle-debounce "^2.1.0"
ts-easing "^0.2.0"
tslib "^1.10.0"
react-use@^13.24.0:
version "13.27.0"
resolved "https://registry.npmjs.org/react-use/-/react-use-13.27.0.tgz#53a619dc9213e2cbe65d6262e8b0e76641ade4aa"
@@ -17290,7 +17563,7 @@ regenerator-runtime@^0.11.0:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4:
regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.5:
version "0.13.5"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
@@ -19023,6 +19296,25 @@ style-loader@^1.0.0:
loader-utils "^1.2.3"
schema-utils "^2.6.4"
styled-system@^5.1.5:
version "5.1.5"
resolved "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz#e362d73e1dbb5641a2fd749a6eba1263dc85075e"
integrity sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==
dependencies:
"@styled-system/background" "^5.1.2"
"@styled-system/border" "^5.1.5"
"@styled-system/color" "^5.1.2"
"@styled-system/core" "^5.1.2"
"@styled-system/flexbox" "^5.1.2"
"@styled-system/grid" "^5.1.2"
"@styled-system/layout" "^5.1.2"
"@styled-system/position" "^5.1.2"
"@styled-system/shadow" "^5.1.2"
"@styled-system/space" "^5.1.2"
"@styled-system/typography" "^5.1.2"
"@styled-system/variant" "^5.1.5"
object-assign "^4.1.1"
stylehacks@^4.0.0:
version "4.0.3"
resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
@@ -19294,6 +19586,18 @@ text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0:
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
theme-ui@^0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/theme-ui/-/theme-ui-0.3.1.tgz#b00ee2c03eb3d820536af8b121c64d13b3777cf0"
integrity sha512-My/TSALqp7Dst5Ez7nJA+94Q8zJhc26Z0qGo8kEWyoqHHJ5TU8xdhjLPBltTdQck3T32cSq5USIeSKU3JtxYUQ==
dependencies:
"@theme-ui/color-modes" "^0.3.1"
"@theme-ui/components" "^0.3.1"
"@theme-ui/core" "^0.3.1"
"@theme-ui/css" "^0.3.1"
"@theme-ui/mdx" "^0.3.0"
"@theme-ui/theme-provider" "^0.3.1"
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
@@ -19722,6 +20026,11 @@ ua-parser-js@^0.7.18:
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.6"
resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==
uglify-js@3.4.x:
version "3.4.10"
resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
@@ -20176,6 +20485,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
vscode-languageserver-types@^3.15.1:
version "3.15.1"
resolved "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de"
integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==
w3c-hr-time@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"