diff --git a/.changeset/warm-pugs-pretend.md b/.changeset/warm-pugs-pretend.md new file mode 100644 index 0000000000..f7adea6cc0 --- /dev/null +++ b/.changeset/warm-pugs-pretend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-apollo-explorer': minor +--- + +feat(apollo-explorer): allow callbacks using apiholder diff --git a/plugins/apollo-explorer/README.md b/plugins/apollo-explorer/README.md index 23ea71e934..bc06612cab 100644 --- a/plugins/apollo-explorer/README.md +++ b/plugins/apollo-explorer/README.md @@ -69,3 +69,36 @@ That's it! You should now see an `Apollo Explorer` item in your sidebar, and if Once you authenticate, your graph is ready to use 🚀 ![Logged In](./docs/img/logged-in.png) + +### Authentication Tokens for Apollo Studio + +If you need to utilize an ApiRef to supply a token to Apollo, you may do so using an ApiHolder. + +In `packages/app/src/App.tsx` perform the following modifications from above. The import `ssoAuthApiRef` is used as an example and **does not exist**. + +```typescript +import { ApolloExplorerPage, EndpointProps } from '@backstage/plugin-apollo-explorer'; +import { ssoAuthApiRef } from '@companyxyz/devkit'; +import { ApiHolder } from '@backstage/core-plugin-api'; + +async function authCallback(options: { apiHolder: ApiHolder }): Promise<{token: string}> { + const sso = options.apiHolder.get(ssoAuthApiRef) + return await sso.getToken() +} + +const routes = ( + + {/* other routes... */} + + } + /> +``` diff --git a/plugins/apollo-explorer/api-report.md b/plugins/apollo-explorer/api-report.md index 7d06876e89..5371bc7219 100644 --- a/plugins/apollo-explorer/api-report.md +++ b/plugins/apollo-explorer/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { ApiHolder } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { JSONObject } from '@apollo/explorer/src/helpers/types'; import { JSX as JSX_2 } from 'react'; @@ -14,23 +15,7 @@ import { RouteRef } from '@backstage/core-plugin-api'; export const ApolloExplorerPage: (props: { title?: string | undefined; subtitle?: string | undefined; - endpoints: { - title: string; - graphRef: string; - persistExplorerState?: boolean | undefined; - initialState?: - | { - document?: string | undefined; - variables?: JSONObject | undefined; - headers?: Record | undefined; - displayOptions: { - docsPanelState?: 'closed' | 'open' | undefined; - showHeadersAndEnvVars?: boolean | undefined; - theme?: 'dark' | 'light' | undefined; - }; - } - | undefined; - }[]; + endpoints: EndpointProps[]; }) => JSX_2.Element; // @public @@ -40,4 +25,27 @@ export const apolloExplorerPlugin: BackstagePlugin< }, {} >; + +// @public +export type AuthCallback = (options: { apiHolder: ApiHolder }) => Promise<{ + token: string; +}>; + +// @public +export type EndpointProps = { + title: string; + graphRef: string; + authCallback?: AuthCallback; + persistExplorerState?: boolean; + initialState?: { + document?: string; + variables?: JSONObject; + headers?: Record; + displayOptions: { + docsPanelState?: 'open' | 'closed'; + showHeadersAndEnvVars?: boolean; + theme?: 'dark' | 'light'; + }; + }; +}; ``` diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 4516ab6404..e843dd4cba 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -36,7 +36,9 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", + "@material-ui/lab": "^4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-use": "^17.5.0", "use-deep-compare-effect": "^1.8.1" }, "devDependencies": { diff --git a/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx b/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx index cf7ef16339..d276774003 100644 --- a/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx +++ b/plugins/apollo-explorer/src/components/ApolloExplorerBrowser/ApolloExplorerBrowser.tsx @@ -19,9 +19,11 @@ import Divider from '@material-ui/core/Divider'; import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; -import { JSONObject } from '@apollo/explorer/src/helpers/types'; import { ApolloExplorer } from '@apollo/explorer/react'; import { Content } from '@backstage/core-components'; +import { HandleRequest } from '@apollo/explorer/src/helpers/postMessageRelayHelpers'; +import { EndpointProps } from '../ApolloExplorerPage'; +import { useApiHolder } from '@backstage/core-plugin-api'; const useStyles = makeStyles(theme => ({ tabs: { @@ -38,30 +40,41 @@ const useStyles = makeStyles(theme => ({ }, })); -export type ApolloEndpointProps = { - title: string; - graphRef: string; - persistExplorerState?: boolean; - initialState?: { - document?: string; - variables?: JSONObject; - headers?: Record; - displayOptions: { - docsPanelState?: 'open' | 'closed'; - showHeadersAndEnvVars?: boolean; - theme?: 'dark' | 'light'; - }; - }; +type Props = { + endpoints: EndpointProps[]; + authCallback?: () => Promise<{ token: string }>; }; -type Props = { - endpoints: ApolloEndpointProps[]; +export const handleAuthRequest = ({ + authCallback, +}: { + authCallback: Props['authCallback']; +}): HandleRequest => { + const handleRequest: HandleRequest = async (endpointUrl, options) => + fetch(endpointUrl, { + ...options, + headers: { + ...options.headers, + ...(authCallback && { + Authorization: `Bearer ${(await authCallback()).token}`, + }), + }, + }); + return handleRequest; }; export const ApolloExplorerBrowser = ({ endpoints }: Props) => { const classes = useStyles(); const [tabIndex, setTabIndex] = useState(0); + const apiHolder = useApiHolder(); + + const getAuthCallback = (index: number) => { + const authCallback = endpoints[index].authCallback; + if (authCallback === undefined) return undefined; + return () => authCallback({ apiHolder }); + }; + return (
{ diff --git a/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx b/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx index f90af9bc17..53b6a54194 100644 --- a/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx +++ b/plugins/apollo-explorer/src/components/ApolloExplorerPage/ApolloExplorerPage.tsx @@ -18,10 +18,17 @@ import React from 'react'; import { Content, Header, Page } from '@backstage/core-components'; import { ApolloExplorerBrowser } from '../ApolloExplorerBrowser'; import { JSONObject } from '@apollo/explorer/src/helpers/types'; +import { ApiHolder } from '@backstage/core-plugin-api'; -type EndpointProps = { +/** + * Export types to be used with {@link @backstage/apollo-explorer#ApolloExplorerPage}. + * + * @public + */ +export type EndpointProps = { title: string; graphRef: string; + authCallback?: AuthCallback; persistExplorerState?: boolean; initialState?: { document?: string; @@ -35,6 +42,15 @@ type EndpointProps = { }; }; +/** + * Export types to be used with {@link @backstage/apollo-explorer#ApolloExplorerPage}. + * + * @public + */ +export type AuthCallback = (options: { + apiHolder: ApiHolder; +}) => Promise<{ token: string }>; + type Props = { title?: string | undefined; subtitle?: string | undefined; @@ -43,6 +59,7 @@ type Props = { export const ApolloExplorerPage = (props: Props) => { const { title, subtitle, endpoints } = props; + return (
diff --git a/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts b/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts index 8f62872fe6..3ca6d12fd0 100644 --- a/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts +++ b/plugins/apollo-explorer/src/components/ApolloExplorerPage/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { ApolloExplorerPage } from './ApolloExplorerPage'; +export type { EndpointProps, AuthCallback } from './ApolloExplorerPage'; diff --git a/plugins/apollo-explorer/src/index.ts b/plugins/apollo-explorer/src/index.ts index 565557e79f..471ec9e710 100644 --- a/plugins/apollo-explorer/src/index.ts +++ b/plugins/apollo-explorer/src/index.ts @@ -20,3 +20,7 @@ * @packageDocumentation */ export { apolloExplorerPlugin, ApolloExplorerPage } from './plugin'; +export type { + EndpointProps, + AuthCallback, +} from './components/ApolloExplorerPage'; diff --git a/yarn.lock b/yarn.lock index e298638f16..78574d3934 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4681,10 +4681,12 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@material-ui/core": ^4.12.2 + "@material-ui/lab": ^4.0.0-alpha.61 "@testing-library/dom": ^9.0.0 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^15.0.0 "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + react-use: ^17.5.0 use-deep-compare-effect: ^1.8.1 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -39887,7 +39889,7 @@ __metadata: languageName: node linkType: hard -"react-use@npm:^17.2.4, react-use@npm:^17.3.1, react-use@npm:^17.3.2, react-use@npm:^17.4.0": +"react-use@npm:^17.2.4, react-use@npm:^17.3.1, react-use@npm:^17.3.2, react-use@npm:^17.4.0, react-use@npm:^17.5.0": version: 17.5.0 resolution: "react-use@npm:17.5.0" dependencies: