Skip to content

Commit

Permalink
feat: fetch API specifications for stand-alone API Explorer (#789)
Browse files Browse the repository at this point in the history
Changes the RunIt forms to make some of the features of RunIt available without requiring configuration and OAuth authentication to a Looker server:
- ConfigForm
- LoginForm
- RequestForm

This also adds recognition of `headless: true|false` to a `versions.json` payload to make it easier/more dynamic to configure the display mode for the stand-alone API Explorer. This can be used for the API Explorer instance that runs on https://developers.looker.com


Co-authored-by: Bryn Ryans <bryn.ryans@looker.com>
  • Loading branch information
jkaster and bryans99 authored Aug 10, 2021
1 parent b9ba328 commit f7be1fb
Show file tree
Hide file tree
Showing 39 changed files with 66,607 additions and 483 deletions.
31,555 changes: 31,555 additions & 0 deletions packages/api-explorer/public/Looker.3.1.json

Large diffs are not rendered by default.

34,253 changes: 34,253 additions & 0 deletions packages/api-explorer/public/Looker.4.0.json

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions packages/api-explorer/public/versions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"looker_release_version": "21.12",
"current_version": {
"version": "3.1",
"full_version": "3.1.0",
"status": "current",
"swagger_url": "Looker.3.1.json"
},
"supported_versions": [
{
"version": "3.1",
"full_version": "3.1.0",
"status": "current",
"swagger_url": "/Looker.3.1.json"
},
{
"version": "4.0",
"full_version": "4.0.21.12",
"status": "beta",
"swagger_url": "/Looker.4.0.json"
}
],
"api_server_url": "https://localhost:8080",
"headless": true
}
39 changes: 29 additions & 10 deletions packages/api-explorer/src/ApiExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import styled, { createGlobalStyle } from 'styled-components'
import { Aside, ComponentsProvider, Layout, Page } from '@looker/components'
import { Looker40SDK, Looker31SDK } from '@looker/sdk'
import { SpecList } from '@looker/sdk-codegen'
import { loadSpecApi } from '@looker/run-it'
import {
SearchContext,
LodeContext,
Expand All @@ -43,6 +44,7 @@ import {
initDefaultSpecState,
searchReducer,
defaultSearchState,
updateSpecApi,
} from './reducers'
import { AppRouter } from './routes'
import { apixFilesHost } from './utils/lodeUtils'
Expand All @@ -69,10 +71,11 @@ const ApiExplorer: FC<ApiExplorerProps> = ({
const location = useLocation()
const { setSdkLanguageAction } = useActions()

const [spec, specDispatch] = useReducer(
const [specState, specDispatch] = useReducer(
specReducer,
initDefaultSpecState(specs, location)
)
const { spec } = specState
const [searchSettings, setSearchSettings] = useReducer(
searchReducer,
defaultSearchState
Expand All @@ -89,6 +92,7 @@ const ApiExplorer: FC<ApiExplorerProps> = ({
setHasNavigation((currentHasNavigation) => !currentHasNavigation)
}
}, [])

useEffect(() => {
if (headless) {
window.addEventListener('message', hasNavigationToggle)
Expand All @@ -100,20 +104,33 @@ const ApiExplorer: FC<ApiExplorerProps> = ({
}
}, [])

useEffect(() => {
const loadSpec = async () => {
if (!spec.api) {
const newSpec = { ...spec }
const api = await loadSpecApi(newSpec)
if (api) {
specDispatch(updateSpecApi(spec.key, api))
}
}
}
loadSpec()
}, [spec])

useEffect(() => {
getLoded(exampleLodeUrl, declarationsLodeUrl).then((resp) => setLode(resp))
}, [exampleLodeUrl, declarationsLodeUrl])

useEffect(() => {
const getSettings = async () => {
const initSdkLanguage = async () => {
const resp = await envAdaptor.localStorageGetItem(
EnvAdaptorConstants.LOCALSTORAGE_SDK_LANGUAGE_KEY
)
if (resp) {
setSdkLanguageAction(resp)
}
}
getSettings()
initSdkLanguage()
}, [envAdaptor, setSdkLanguageAction])

const { loadGoogleFonts, themeCustomizations } = envAdaptor.themeOverrides()
Expand Down Expand Up @@ -141,7 +158,7 @@ const ApiExplorer: FC<ApiExplorerProps> = ({
)}
<Layout hasAside height="100%">
{hasNavigation && (
<AsideBorder width="20rem">
<AsideBorder pt="large" width="20rem">
<SideNav
headless={headless}
specs={specs}
Expand All @@ -150,12 +167,14 @@ const ApiExplorer: FC<ApiExplorerProps> = ({
/>
</AsideBorder>
)}
<AppRouter
api={spec.api}
specKey={spec.key}
specs={specs}
toggleNavigation={toggleNavigation}
/>
{spec.api && (
<AppRouter
api={spec.api}
specKey={spec.key}
specs={specs}
toggleNavigation={toggleNavigation}
/>
)}
</Layout>
</Page>
</SearchContext.Provider>
Expand Down
58 changes: 37 additions & 21 deletions packages/api-explorer/src/StandaloneApiExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
*/

import React, { FC } from 'react'
import { useRouteMatch } from 'react-router-dom'
import React, { FC, useEffect, useState } from 'react'
import {
RunItProvider,
defaultConfigurator,
initRunItSdk,
loadSpecsFromVersions,
} from '@looker/run-it'
import { IAPIMethods } from '@looker/sdk-rtl'
import { SpecList } from '@looker/sdk-codegen'
Expand All @@ -38,29 +38,39 @@ import { Provider } from 'react-redux'
import ApiExplorer from './ApiExplorer'
import { configureStore } from './state'
import { StandaloneEnvAdaptor } from './utils'
import { Loader } from './components'

export interface StandloneApiExplorerProps {
specs: SpecList
export interface StandaloneApiExplorerProps {
headless?: boolean
versionsUrl: string
}

const standaloneEnvAdaptor = new StandaloneEnvAdaptor()
const store = configureStore()

export const StandaloneApiExplorer: FC<StandloneApiExplorerProps> = ({
specs,
export const StandaloneApiExplorer: FC<StandaloneApiExplorerProps> = ({
headless = false,
versionsUrl = '',
}) => {
const match = useRouteMatch<{ specKey: string }>(`/:specKey`)
const specKey = match?.params.specKey || ''
// TODO we may not need this restriction any more?
// Check explicitly for specs 3.0 and 3.1 as run it is not supported.
// This is done as the return from OAUTH does not provide a spec key
// but an SDK is needed.
const chosenSdk: IAPIMethods | undefined =
specKey === '3.0' || specKey === '3.1'
? undefined
: initRunItSdk(defaultConfigurator)
const [specs, setSpecs] = useState<SpecList | undefined>()
const [embedded, setEmbedded] = useState<boolean>(headless)

useEffect(() => {
if (versionsUrl) {
// Load specifications from the versions url
loadSpecsFromVersions(versionsUrl).then((response) => {
setSpecs(response.specs)
if ('headless' in response) {
// headless will default to false if it's not explicitly set
setEmbedded(response.headless)
}
})
} else {
setSpecs(undefined)
}
}, [versionsUrl])

const chosenSdk: IAPIMethods = initRunItSdk(defaultConfigurator)

return (
<Provider store={store}>
Expand All @@ -69,11 +79,17 @@ export const StandaloneApiExplorer: FC<StandloneApiExplorerProps> = ({
configurator={defaultConfigurator}
basePath="/api/4.0"
>
<ApiExplorer
specs={specs}
envAdaptor={standaloneEnvAdaptor}
headless={headless}
/>
<>
{specs ? (
<ApiExplorer
specs={specs}
envAdaptor={standaloneEnvAdaptor}
headless={embedded}
/>
) : (
<Loader themeOverrides={standaloneEnvAdaptor.themeOverrides()} />
)}
</>
</RunItProvider>
</Provider>
)
Expand Down
14 changes: 7 additions & 7 deletions packages/api-explorer/src/components/Header/Header.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,15 @@ describe('Header', () => {
withReduxProvider(
<Header
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
toggleNavigation={toggleNavigation}
/>
)
)
expect(screen.getByText('API Explorer').closest('a')).toHaveAttribute(
'href',
`/${specState.key}`
`/${specState.spec.key}`
)
})

Expand All @@ -64,14 +64,14 @@ describe('Header', () => {
withReduxProvider(
<Header
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
toggleNavigation={toggleNavigation}
/>
)
)
const selector = screen.getByLabelText('spec selector')
expect(selector).toHaveValue(`${specState.key}`)
expect(selector).toHaveValue(`${specState.spec.key}`)
await act(async () => {
await userEvent.click(selector)
await waitFor(() => {
Expand All @@ -87,7 +87,7 @@ describe('Header', () => {
withReduxProvider(
<Header
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
toggleNavigation={toggleNavigation}
/>
Expand All @@ -110,7 +110,7 @@ describe('Header', () => {
withReduxProvider(
<Header
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
toggleNavigation={toggleNavigation}
/>
Expand All @@ -122,6 +122,6 @@ describe('Header', () => {
name: 'Compare Specifications',
})
.closest('a')
).toHaveAttribute('href', `/diff/${specState.key}/`)
).toHaveAttribute('href', `/diff/${specState.spec.key}/`)
})
})
6 changes: 3 additions & 3 deletions packages/api-explorer/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ import {
} from '@looker/components'
import { LookerLogo } from '@looker/icons'
import { Menu } from '@styled-icons/material/Menu'
import { SpecList } from '@looker/sdk-codegen'
import { SpecList, SpecItem } from '@looker/sdk-codegen'

import { Link } from '../Link'
import { SpecState, SpecAction } from '../../reducers'
import { SpecAction } from '../../reducers'
import { SelectorContainer } from '../SelectorContainer'

interface HeaderProps {
/** Specs to choose from */
specs: SpecList
/** Current selected spec */
spec: SpecState
spec: SpecItem
/** Spec state setter */
specDispatch: Dispatch<SpecAction>
/** Nav state setter */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import { act, screen, waitFor } from '@testing-library/react'
import { renderWithTheme } from '@looker/components-test-utils'
import userEvent from '@testing-library/user-event'

import { specs, specState } from '../../test-data'
import { specs, getLoadedSpecState } from '../../test-data'
import { renderWithRouter } from '../../test-utils'
import { ApiSpecSelector } from './ApiSpecSelector'

const specState = getLoadedSpecState()

describe('ApiSpecSelector', () => {
const specDispatch = jest.fn()
Element.prototype.scrollIntoView = jest.fn()
Expand All @@ -40,20 +42,20 @@ describe('ApiSpecSelector', () => {
renderWithTheme(
<ApiSpecSelector
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
/>
)

const selector = screen.getByRole('textbox')
expect(selector).toHaveValue(`${specState.key}`)
expect(selector).toHaveValue(`${specState.spec.key}`)
})

test('it lists all available specs', async () => {
renderWithTheme(
<ApiSpecSelector
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
/>
)
Expand All @@ -69,7 +71,7 @@ describe('ApiSpecSelector', () => {
renderWithRouter(
<ApiSpecSelector
specs={specs}
spec={specState}
spec={specState.spec}
specDispatch={specDispatch}
/>
)
Expand All @@ -81,8 +83,7 @@ describe('ApiSpecSelector', () => {
expect(specDispatch).toHaveBeenCalledTimes(1)
expect(specDispatch).toHaveBeenCalledWith({
type: 'SELECT_SPEC',
key: '3.1',
payload: specs,
payload: '3.1',
})
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ import React, { FC, Dispatch } from 'react'
import { Select } from '@looker/components'
import { useHistory } from 'react-router-dom'

import { SpecList } from '@looker/sdk-codegen'
import { SpecAction, SpecState, selectSpec } from '../../reducers'
import { SpecList, SpecItem } from '@looker/sdk-codegen'
import { SpecAction, selectSpec } from '../../reducers'

interface ApiSpecSelectorProps {
specs: SpecList
spec: SpecState
spec: SpecItem
specDispatch: Dispatch<SpecAction>
}

Expand All @@ -50,7 +50,7 @@ export const ApiSpecSelector: FC<ApiSpecSelectorProps> = ({
}))

const handleChange = (specKey: string) => {
specDispatch(selectSpec(specs, specKey))
specDispatch(selectSpec(specKey))
history.push(`/${specKey}`)
}

Expand Down
Loading

0 comments on commit f7be1fb

Please sign in to comment.