From 0b4e5783d08babad6bc311bd95b48f2b95d47f5b Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 22 Jun 2022 23:52:09 +0000 Subject: [PATCH 01/30] WIP search sharing in URL --- packages/api-explorer/src/components/SideNav/SideNav.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 6df6b5aed..7f62bd5a0 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -70,6 +70,7 @@ interface SideNavProps { export const SideNav: FC = ({ headless = false, spec }) => { const history = useHistory() const location = useLocation() + const searchParams = new URLSearchParams(location.search) const specKey = spec.key const tabNames = ['methods', 'types'] const pathParts = location.pathname.split('/') @@ -91,6 +92,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { history.push(parts.join('/')) } } + history.replace({ search: searchParams.toString() }) } const tabs = useTabs({ defaultIndex, onChange: onTabChange }) const searchCriteria = useSelector(selectSearchCriteria) @@ -123,6 +125,12 @@ export const SideNav: FC = ({ headless = false, spec }) => { newTags = results.tags newTypes = results.types newTypeTags = tagTypes(api, results.types) + if (!pattern && searchParams.get('s')) { + searchParams.delete('s') + } else { + searchParams.set('s', debouncedPattern) + } + history.replace({ search: searchParams.toString() }) } else { newTags = api.tags || {} newTypes = api.types || {} From 95436a44bb9e45f152b77343356c7f6e96cb9cb5 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Thu, 23 Jun 2022 20:19:32 +0000 Subject: [PATCH 02/30] URL search param will update search box in sidenav --- packages/api-explorer/src/components/SideNav/SideNav.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 7f62bd5a0..5217bbda0 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -98,7 +98,9 @@ export const SideNav: FC = ({ headless = false, spec }) => { const searchCriteria = useSelector(selectSearchCriteria) const { setSearchPatternAction } = useSettingActions() - const [pattern, setSearchPattern] = useState('') + const [pattern, setSearchPattern] = useState( + searchParams.get('s') ? searchParams.get('s') : '' + ) const debouncedPattern = useDebounce(pattern, 250) const [sideNavState, setSideNavState] = useState(() => ({ tags: spec?.api?.tags || {}, From dcb882a8c83298b25c935b817abf61e1a30e722a Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Fri, 24 Jun 2022 17:38:47 +0000 Subject: [PATCH 03/30] Avoid string | null errors --- packages/api-explorer/src/components/SideNav/SideNav.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 5217bbda0..9cb33e31a 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -123,7 +123,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { const api = spec.api || ({} as ApiModel) if (debouncedPattern && api.search) { - results = api.search(pattern, criteriaToSet(searchCriteria)) + results = api.search(pattern!, criteriaToSet(searchCriteria)) newTags = results.tags newTypes = results.types newTypeTags = tagTypes(api, results.types) @@ -146,7 +146,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { methodCount: countMethods(newTags), searchResults: results, }) - setSearchPatternAction({ searchPattern: debouncedPattern }) + setSearchPatternAction({ searchPattern: debouncedPattern! }) }, [ debouncedPattern, specKey, From 850b64b87a19ba06dc3724e08ca26ebf3ea4be07 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Fri, 24 Jun 2022 21:16:58 +0000 Subject: [PATCH 04/30] WIP search query back button and slow input box --- .../src/components/SideNav/SideNav.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 9cb33e31a..0ed21e32d 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -92,7 +92,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { history.push(parts.join('/')) } } - history.replace({ search: searchParams.toString() }) + history.push({ search: searchParams.toString() }) } const tabs = useTabs({ defaultIndex, onChange: onTabChange }) const searchCriteria = useSelector(selectSearchCriteria) @@ -132,7 +132,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { } else { searchParams.set('s', debouncedPattern) } - history.replace({ search: searchParams.toString() }) + history.push({ search: searchParams.toString() }) } else { newTags = api.tags || {} newTypes = api.types || {} @@ -163,6 +163,19 @@ export const SideNav: FC = ({ headless = false, spec }) => { } }, [defaultIndex, tabs]) + useEffect(() => { + return () => { + if (history.action === 'POP') { + const searchQuery = new URLSearchParams(history.location.search).get( + 's' + ) + if (searchQuery) { + setSearchPattern(searchQuery) + } + } + } + }, [location]) + const size = useWindowSize() const headlessOffset = headless ? 200 : 120 const menuH = size.height - 16 * HEADER_REM - headlessOffset From 40caab12ca2088d03edf9351ad6aaeca1043af33 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 29 Jun 2022 00:03:00 +0000 Subject: [PATCH 05/30] Functional query routing with accurate browser history, WIP testing --- packages/api-explorer/src/ApiExplorer.tsx | 8 ++- .../src/components/SideNav/SideNav.tsx | 71 ++++++++----------- 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/packages/api-explorer/src/ApiExplorer.tsx b/packages/api-explorer/src/ApiExplorer.tsx index 769296455..e97ae6c92 100644 --- a/packages/api-explorer/src/ApiExplorer.tsx +++ b/packages/api-explorer/src/ApiExplorer.tsx @@ -91,7 +91,7 @@ export const ApiExplorer: FC = ({ const specs = useSelector(selectSpecs) const spec = useSelector(selectCurrentSpec) const { initLodesAction } = useLodeActions() - const { initSettingsAction } = useSettingActions() + const { initSettingsAction, setSearchPatternAction } = useSettingActions() const { initSpecsAction, setCurrentSpecAction } = useSpecActions() const location = useLocation() @@ -123,6 +123,12 @@ export const ApiExplorer: FC = ({ } }, [location.pathname, spec]) + useEffect(() => { + const searchParams = new URLSearchParams(location.search) + const searchPattern = searchParams.get('s') ? searchParams.get('s') : '' + setSearchPatternAction({ searchPattern: searchPattern! }) + }, [location.search]) + useEffect(() => { if (headless) { window.addEventListener('message', hasNavigationToggle) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 0ed21e32d..70171fccc 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -44,10 +44,13 @@ import type { } from '@looker/sdk-codegen' import { criteriaToSet, tagTypes } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' - import { useWindowSize } from '../../utils' import { HEADER_REM } from '../Header' -import { selectSearchCriteria, useSettingActions } from '../../state' +import { + selectSearchCriteria, + selectSearchPattern, + // useSettingActions, +} from '../../state' import { SideNavMethodTags } from './SideNavMethodTags' import { SideNavTypeTags } from './SideNavTypeTags' import { useDebounce, countMethods, countTypes } from './searchUtils' @@ -60,7 +63,6 @@ interface SideNavState { typeCount: number searchResults?: ISearchResult } - interface SideNavProps { headless?: boolean /** Current selected spec */ @@ -84,23 +86,25 @@ export const SideNav: FC = ({ headless = false, spec }) => { if (parts[1] === 'diff') { if (parts[3] !== tabNames[index]) { parts[3] = tabNames[index] - history.push(parts.join('/')) + history.push({ + pathname: parts.join('/'), + search: searchParams.toString(), + }) } } else { if (parts[2] !== tabNames[index]) { parts[2] = tabNames[index] - history.push(parts.join('/')) + history.push({ + pathname: parts.join('/'), + search: searchParams.toString(), + }) } } - history.push({ search: searchParams.toString() }) } const tabs = useTabs({ defaultIndex, onChange: onTabChange }) const searchCriteria = useSelector(selectSearchCriteria) - const { setSearchPatternAction } = useSettingActions() - - const [pattern, setSearchPattern] = useState( - searchParams.get('s') ? searchParams.get('s') : '' - ) + const searchPattern = useSelector(selectSearchPattern) + const [pattern, setSearchPattern] = useState(searchPattern) const debouncedPattern = useDebounce(pattern, 250) const [sideNavState, setSideNavState] = useState(() => ({ tags: spec?.api?.tags || {}, @@ -115,30 +119,34 @@ export const SideNav: FC = ({ headless = false, spec }) => { setSearchPattern(value) } + useEffect(() => { + if (debouncedPattern && debouncedPattern !== searchParams.get('s')) { + searchParams.set('s', debouncedPattern) + history.push({ search: searchParams.toString() }) + } else if (!debouncedPattern && searchParams.get('s')) { + searchParams.delete('s') + history.push({ search: searchParams.toString() }) + } + }, [debouncedPattern]) + useEffect(() => { let results let newTags let newTypes let newTypeTags const api = spec.api || ({} as ApiModel) + setSearchPattern(searchPattern) - if (debouncedPattern && api.search) { - results = api.search(pattern!, criteriaToSet(searchCriteria)) + if (searchPattern && api.search) { + results = api.search(searchPattern, criteriaToSet(searchCriteria)) newTags = results.tags newTypes = results.types newTypeTags = tagTypes(api, results.types) - if (!pattern && searchParams.get('s')) { - searchParams.delete('s') - } else { - searchParams.set('s', debouncedPattern) - } - history.push({ search: searchParams.toString() }) } else { newTags = api.tags || {} newTypes = api.types || {} newTypeTags = api.typeTags || {} } - setSideNavState({ tags: newTags, typeTags: newTypeTags, @@ -146,15 +154,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { methodCount: countMethods(newTags), searchResults: results, }) - setSearchPatternAction({ searchPattern: debouncedPattern! }) - }, [ - debouncedPattern, - specKey, - spec, - setSearchPatternAction, - pattern, - searchCriteria, - ]) + }, [searchPattern, specKey, spec, searchCriteria]) useEffect(() => { const { selectedIndex, onSelectTab } = tabs @@ -163,19 +163,6 @@ export const SideNav: FC = ({ headless = false, spec }) => { } }, [defaultIndex, tabs]) - useEffect(() => { - return () => { - if (history.action === 'POP') { - const searchQuery = new URLSearchParams(history.location.search).get( - 's' - ) - if (searchQuery) { - setSearchPattern(searchQuery) - } - } - } - }, [location]) - const size = useWindowSize() const headlessOffset = headless ? 200 : 120 const menuH = size.height - 16 * HEADER_REM - headlessOffset From 754c84f66e41800b7161f00b6529dacb862a52e8 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 29 Jun 2022 16:22:36 +0000 Subject: [PATCH 06/30] Removed comment --- packages/api-explorer/src/components/SideNav/SideNav.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 70171fccc..5accbce88 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -46,11 +46,7 @@ import { criteriaToSet, tagTypes } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { useWindowSize } from '../../utils' import { HEADER_REM } from '../Header' -import { - selectSearchCriteria, - selectSearchPattern, - // useSettingActions, -} from '../../state' +import { selectSearchCriteria, selectSearchPattern } from '../../state' import { SideNavMethodTags } from './SideNavMethodTags' import { SideNavTypeTags } from './SideNavTypeTags' import { useDebounce, countMethods, countTypes } from './searchUtils' From de28036ec8bd92e40c1c79f2ea2de7ce32bf07c8 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Thu, 30 Jun 2022 23:04:53 +0000 Subject: [PATCH 07/30] Added navigation utility file, helper function for pushing routing without losing parameters --- .../SelectorContainer/ApiSpecSelector.tsx | 4 +- .../src/components/SideNav/SideNav.spec.tsx | 30 ++++++++++++ .../src/components/SideNav/SideNav.tsx | 26 ++++++---- .../src/components/SideNav/SideNavMethods.tsx | 17 ++++--- .../src/components/SideNav/SideNavTypes.tsx | 21 +++++--- .../src/scenes/MethodScene/MethodScene.tsx | 7 ++- .../src/scenes/TypeTagScene/TypeTagScene.tsx | 4 +- packages/api-explorer/src/utils/navUtils.ts | 49 +++++++++++++++++++ 8 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 packages/api-explorer/src/utils/navUtils.ts diff --git a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx index 428e2feaf..7817973d2 100644 --- a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx +++ b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx @@ -30,6 +30,7 @@ import { Select } from '@looker/components' import { useHistory, useLocation } from 'react-router-dom' import type { SpecItem } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' +import { navigate } from '../../utils/navUtils' import { selectSpecs } from '../../state' @@ -49,7 +50,8 @@ export const ApiSpecSelector: FC = ({ spec }) => { const handleChange = (specKey: string) => { const matchPath = location.pathname.replace(`/${spec.key}`, `/${specKey}`) - history.push(matchPath) + navigate(matchPath, {}, history) + // history.push(matchPath) } return ( diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 7c59a9cbf..0cdfe1370 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -88,6 +88,12 @@ describe('SideNav', () => { }) }) +// TODO: tests for search query nav +// 1) inputting text in search updates URL +// 2) mock state, when state updates, search triggers +// 2) searching with param in URL returns searched page +// 3) back / forward navigation maintains previous state + describe('Search', () => { test('it filters methods and types on input', async () => { renderWithRouterAndReduxProvider() @@ -111,4 +117,28 @@ describe('Search', () => { expect(screen.getByText('EmbedSso')).toBeInTheDocument() }) }) + // test('Entering URL with search parameter drives search state', async () => { + // renderWithRouterAndReduxProvider(, [ + // '/3.1/methods?s=test', + // ]) + // const input = screen.getByLabelText('Search') + // jest.spyOn(spec.api!, 'search') + // expect(input).toContain('test') + // /** Pasting to avoid triggering search multiple times */ + // await userEvent.paste(input, searchPattern) + // await waitFor(() => { + // expect(spec.api!.search).toHaveBeenCalledWith( + // searchPattern, + // criteriaToSet(defaultSettingsState.searchCriteria) + // ) + // const methods = screen.getByRole('tab', { name: 'Methods (1)' }) + // userEvent.click(methods) + // expect( + // screen.getByText(spec.api!.tags.Auth.create_sso_embed_url.summary) + // ).toBeInTheDocument() + // const types = screen.getByRole('tab', { name: 'Types (1)' }) + // userEvent.click(types) + // expect(screen.getByText('EmbedSso')).toBeInTheDocument() + // }) + // }) }) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 5accbce88..163bc916f 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -45,6 +45,7 @@ import type { import { criteriaToSet, tagTypes } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { useWindowSize } from '../../utils' +import { navigate } from '../../utils/navUtils' import { HEADER_REM } from '../Header' import { selectSearchCriteria, selectSearchPattern } from '../../state' import { SideNavMethodTags } from './SideNavMethodTags' @@ -82,18 +83,20 @@ export const SideNav: FC = ({ headless = false, spec }) => { if (parts[1] === 'diff') { if (parts[3] !== tabNames[index]) { parts[3] = tabNames[index] - history.push({ - pathname: parts.join('/'), - search: searchParams.toString(), - }) + navigate(parts.join('/'), {}, history) + // history.push({ + // pathname: parts.join('/'), + // search: searchParams.toString(), + // }) } } else { if (parts[2] !== tabNames[index]) { parts[2] = tabNames[index] - history.push({ - pathname: parts.join('/'), - search: searchParams.toString(), - }) + navigate(parts.join('/'), {}, history) + // history.push({ + // pathname: parts.join('/'), + // search: searchParams.toString(), + // }) } } } @@ -118,10 +121,13 @@ export const SideNav: FC = ({ headless = false, spec }) => { useEffect(() => { if (debouncedPattern && debouncedPattern !== searchParams.get('s')) { searchParams.set('s', debouncedPattern) - history.push({ search: searchParams.toString() }) + navigate(null, { search: searchParams.toString() }, history) + + // history.push({ search: searchParams.toString() }) } else if (!debouncedPattern && searchParams.get('s')) { searchParams.delete('s') - history.push({ search: searchParams.toString() }) + navigate(null, { search: searchParams.toString() }, history) + // history.push({ search: searchParams.toString() }) } }, [debouncedPattern]) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index 9006342c8..e99bc1597 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -30,7 +30,7 @@ import { Accordion2, Heading } from '@looker/components' import type { MethodList } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { useHistory, useRouteMatch } from 'react-router-dom' - +import { navigate } from '../../utils/navUtils' import { Link } from '../Link' import { buildMethodPath, highlightHTML } from '../../utils' import { selectSearchPattern } from '../../state' @@ -45,20 +45,20 @@ interface MethodsProps { export const SideNavMethods = styled( ({ className, methods, tag, specKey, defaultOpen = false }: MethodsProps) => { + const history = useHistory() + const searchParams = new URLSearchParams(history.location.search) const searchPattern = useSelector(selectSearchPattern) const match = useRouteMatch<{ methodTag: string }>( `/:specKey/methods/:methodTag/:methodName?` ) const [isOpen, setIsOpen] = useState(defaultOpen) - const history = useHistory() - const handleOpen = () => { const _isOpen = !isOpen setIsOpen(_isOpen) if (_isOpen) { - history.push(`/${specKey}/methods/${tag}`) + navigate(`/${specKey}/methods/${tag}`, {}, history) } else { - history.push(`/${specKey}/methods`) + navigate(`/${specKey}/methods`, {}, history) } } @@ -84,7 +84,12 @@ export const SideNavMethods = styled(
    {Object.values(methods).map((method) => (
  • - + {highlightHTML(searchPattern, method.summary)}
  • diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx index 4d51e89bc..86fd11b31 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx @@ -30,7 +30,7 @@ import { Accordion2, Heading } from '@looker/components' import type { TypeList } from '@looker/sdk-codegen' import { useHistory, useRouteMatch } from 'react-router-dom' import { useSelector } from 'react-redux' - +import { navigate } from '../../utils/navUtils' import { Link } from '../Link' import { buildTypePath, highlightHTML } from '../../utils' import { selectSearchPattern } from '../../state' @@ -45,20 +45,24 @@ interface TypesProps { export const SideNavTypes = styled( ({ className, types, tag, specKey, defaultOpen = false }: TypesProps) => { + const history = useHistory() + const searchParams = new URLSearchParams(history.location.search) const searchPattern = useSelector(selectSearchPattern) const match = useRouteMatch<{ typeTag: string }>( `/:specKey/types/:typeTag/:typeName?` ) const [isOpen, setIsOpen] = useState(defaultOpen) - const history = useHistory() - const handleOpen = () => { const _isOpen = !isOpen setIsOpen(_isOpen) if (_isOpen) { - history.push(`/${specKey}/types/${tag}`) + navigate(`/${specKey}/types/${tag}`, {}, history) + // history.push({ + // pathname: `/${specKey}/types/${tag}`, + // search: new URLSearchParams(location.search).toString(), + // }) } else { - history.push(`/${specKey}/types`) + navigate(`/${specKey}/types`, {}, history) } } @@ -84,7 +88,12 @@ export const SideNavTypes = styled(
      {Object.values(types).map((type) => (
    • - + {highlightHTML(searchPattern, type.name)}
    • diff --git a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx index e1282e0df..aadb6078a 100644 --- a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx +++ b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx @@ -41,6 +41,7 @@ import type { ApiModel } from '@looker/sdk-codegen' import { typeRefs } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import type { IEnvironmentAdaptor } from '@looker/extension-utils' +import { navigate } from '../../utils/navUtils' import { getApixAdaptor } from '../../utils' import { @@ -94,9 +95,11 @@ export const MethodScene: FC = ({ api }) => { // Invalid method if (api.tags[methodTag]) { // Found tag though - history.push(`/${specKey}/methods/${methodTag}`) + navigate(`/${specKey}/methods/${methodTag}`, {}, history) + // history.push(`/${specKey}/methods/${methodTag}`) } else { - history.push(`/${specKey}/methods`) + navigate(`/${specKey}/methods`, {}, history) + // history.push(`/${specKey}/methods`) } } }, [api, history, methodName, methodTag, specKey]) diff --git a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx index abfb22a89..808ceb4fc 100644 --- a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx +++ b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx @@ -28,6 +28,7 @@ import React, { useEffect, useState } from 'react' import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' import { useParams, useHistory } from 'react-router-dom' +import { navigate } from '../../utils/navUtils' import { ApixSection, DocTitle, DocTypeSummary, Link } from '../../components' import { buildTypePath } from '../../utils' import { getMetaTypes } from './utils' @@ -54,7 +55,8 @@ export const TypeTagScene: FC = ({ api }) => { const types = api.typeTags[typeTag] useEffect(() => { if (!types) { - history.push(`/${specKey}/types`) + navigate(`/${specKey}/types`, {}, history) + // history.push(`/${specKey}/types`) } }, [history, types]) diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/navUtils.ts new file mode 100644 index 000000000..1aada3eb6 --- /dev/null +++ b/packages/api-explorer/src/utils/navUtils.ts @@ -0,0 +1,49 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ +import type { History } from 'history' + +export const navigate = ( + route: string | null, + newParams: { search?: string }, + history: History +): void => { + const curParams = new URLSearchParams(history.location.search) + if (!route) { + // we are pushing something to current + history.push({ search: newParams.search }) + return + } + if (!newParams) { + // if it is null, empty params + history.push({ pathname: route }) + } else if (Object.keys(newParams).length === 0) { + // if params is empty, leave the path be + history.push({ pathname: route, search: curParams.toString() }) + } else { + // push the given parameters + history.push({ pathname: route, search: newParams.search }) + } +} From d26203e17d274a1269d3948e5ee078da4321bed5 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Fri, 1 Jul 2022 16:01:17 +0000 Subject: [PATCH 08/30] Functional with spec and search and method/types chosen --- packages/api-explorer/src/components/SideNav/SideNav.tsx | 2 ++ .../api-explorer/src/components/SideNav/SideNavMethods.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 163bc916f..f5828c9f6 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -60,6 +60,7 @@ interface SideNavState { typeCount: number searchResults?: ISearchResult } + interface SideNavProps { headless?: boolean /** Current selected spec */ @@ -149,6 +150,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { newTypes = api.types || {} newTypeTags = api.typeTags || {} } + setSideNavState({ tags: newTags, typeTags: newTypeTags, diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index e99bc1597..0c642b8b0 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -29,7 +29,7 @@ import styled from 'styled-components' import { Accordion2, Heading } from '@looker/components' import type { MethodList } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' -import { useHistory, useRouteMatch } from 'react-router-dom' +import { useHistory, useLocation, useRouteMatch } from 'react-router-dom' import { navigate } from '../../utils/navUtils' import { Link } from '../Link' import { buildMethodPath, highlightHTML } from '../../utils' @@ -46,7 +46,8 @@ interface MethodsProps { export const SideNavMethods = styled( ({ className, methods, tag, specKey, defaultOpen = false }: MethodsProps) => { const history = useHistory() - const searchParams = new URLSearchParams(history.location.search) + const location = useLocation() + const searchParams = new URLSearchParams(location.search) const searchPattern = useSelector(selectSearchPattern) const match = useRouteMatch<{ methodTag: string }>( `/:specKey/methods/:methodTag/:methodName?` From 1a013f15cd4c6184919bca2e00d5373079e83624 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Tue, 12 Jul 2022 20:15:48 +0000 Subject: [PATCH 09/30] WIP: mocking ApiExplorer.tsx useEffect on location search --- .../src/components/SideNav/SideNav.spec.tsx | 17 +++++++++++++++++ .../src/components/SideNav/SideNav.tsx | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 0cdfe1370..0fa574803 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -35,6 +35,16 @@ import { SideNav } from './SideNav' import { countMethods, countTypes } from './searchUtils' const spec = getLoadedSpecs()['4.0'] +const mockHistoryPush = jest.fn() +jest.mock('react-router-dom', () => { + const ReactRouterDOM = jest.requireActual('react-router-dom') + return { + ...ReactRouterDOM, + useHistory: () => ({ + push: mockHistoryPush, + }), + } +}) describe('SideNav', () => { const allTagsPattern = /^(Auth|ApiAuth)$/ @@ -94,6 +104,12 @@ describe('SideNav', () => { // 2) searching with param in URL returns searched page // 3) back / forward navigation maintains previous state +/* + TODO: what is going on here is that the route gets pushed, however the URL + cannot drive state unless the APIExplorer useEffect for location.search + is executed, so we need to mock that. + */ + describe('Search', () => { test('it filters methods and types on input', async () => { renderWithRouterAndReduxProvider() @@ -102,6 +118,7 @@ describe('Search', () => { jest.spyOn(spec.api!, 'search') /** Pasting to avoid triggering search multiple times */ await userEvent.paste(input, searchPattern) + expect(mockHistoryPush).toHaveBeenCalledWith(`/3.1`) await waitFor(() => { expect(spec.api!.search).toHaveBeenCalledWith( searchPattern, diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index f5828c9f6..9d836a32e 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -158,7 +158,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { methodCount: countMethods(newTags), searchResults: results, }) - }, [searchPattern, specKey, spec, searchCriteria]) + }, [searchPattern, specKey, spec, searchCriteria, history.location]) useEffect(() => { const { selectedIndex, onSelectTab } = tabs From 02afef8fba11777da1848f98ee24006cc29f7692 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Tue, 12 Jul 2022 21:40:55 +0000 Subject: [PATCH 10/30] Quick addition --- packages/api-explorer/src/components/SideNav/SideNav.spec.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 0fa574803..cc4732064 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -42,6 +42,7 @@ jest.mock('react-router-dom', () => { ...ReactRouterDOM, useHistory: () => ({ push: mockHistoryPush, + location, }), } }) @@ -118,6 +119,7 @@ describe('Search', () => { jest.spyOn(spec.api!, 'search') /** Pasting to avoid triggering search multiple times */ await userEvent.paste(input, searchPattern) + expect(mockHistoryPush).toHaveBeenCalledWith(`/3.1`) await waitFor(() => { expect(spec.api!.search).toHaveBeenCalledWith( From 1c41c0316e890f74c55c802a0156914d22d99074 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 13 Jul 2022 16:07:55 +0000 Subject: [PATCH 11/30] WIP: getting testing to work. --- .../src/components/SideNav/SideNav.spec.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index cc4732064..777891fc4 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -29,8 +29,12 @@ import userEvent from '@testing-library/user-event' import { screen, waitFor } from '@testing-library/react' import { getLoadedSpecs } from '../../test-data' -import { renderWithRouterAndReduxProvider } from '../../test-utils' -import { defaultSettingsState } from '../../state' +import { + createTestStore, + renderWithRouterAndReduxProvider, +} from '../../test-utils' +import { defaultSettingsState, useSettingActions } from '../../state' +import { sleep } from '../../../../../examples/typescript/utils' import { SideNav } from './SideNav' import { countMethods, countTypes } from './searchUtils' @@ -110,17 +114,15 @@ describe('SideNav', () => { cannot drive state unless the APIExplorer useEffect for location.search is executed, so we need to mock that. */ - +const store = createTestStore() describe('Search', () => { test('it filters methods and types on input', async () => { - renderWithRouterAndReduxProvider() + renderWithRouterAndReduxProvider(, undefined, store) const searchPattern = 'embedsso' const input = screen.getByLabelText('Search') jest.spyOn(spec.api!, 'search') /** Pasting to avoid triggering search multiple times */ await userEvent.paste(input, searchPattern) - - expect(mockHistoryPush).toHaveBeenCalledWith(`/3.1`) await waitFor(() => { expect(spec.api!.search).toHaveBeenCalledWith( searchPattern, From a8a04518cf7b0cb4b6b280962320547414abb10b Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 13 Jul 2022 19:33:42 +0000 Subject: [PATCH 12/30] WIP comments --- .../src/components/SideNav/SideNav.spec.tsx | 56 ++++++++++++------- .../src/components/SideNav/SideNav.tsx | 4 +- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 777891fc4..54ed662d8 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -34,22 +34,10 @@ import { renderWithRouterAndReduxProvider, } from '../../test-utils' import { defaultSettingsState, useSettingActions } from '../../state' -import { sleep } from '../../../../../examples/typescript/utils' import { SideNav } from './SideNav' import { countMethods, countTypes } from './searchUtils' const spec = getLoadedSpecs()['4.0'] -const mockHistoryPush = jest.fn() -jest.mock('react-router-dom', () => { - const ReactRouterDOM = jest.requireActual('react-router-dom') - return { - ...ReactRouterDOM, - useHistory: () => ({ - push: mockHistoryPush, - location, - }), - } -}) describe('SideNav', () => { const allTagsPattern = /^(Auth|ApiAuth)$/ @@ -104,26 +92,52 @@ describe('SideNav', () => { }) // TODO: tests for search query nav -// 1) inputting text in search updates URL -// 2) mock state, when state updates, search triggers -// 2) searching with param in URL returns searched page -// 3) back / forward navigation maintains previous state +// 1) inputting text in search updates search box and URL +// 2) updated URL results in search triggered +// 3) searching with param in URL returns searched page +// 4) back / forward navigation maintains previous state /* TODO: what is going on here is that the route gets pushed, however the URL cannot drive state unless the APIExplorer useEffect for location.search is executed, so we need to mock that. */ -const store = createTestStore() + +const mockHistoryPush = jest.fn() +jest.mock('react-router-dom', () => { + const ReactRouterDOM = jest.requireActual('react-router-dom') + return { + ...ReactRouterDOM, + useHistory: () => ({ + push: mockHistoryPush, + location, + }), + } +}) + describe('Search', () => { - test('it filters methods and types on input', async () => { - renderWithRouterAndReduxProvider(, undefined, store) + test('inputting text in search box updates URL', async () => { + renderWithRouterAndReduxProvider(, undefined) const searchPattern = 'embedsso' const input = screen.getByLabelText('Search') - jest.spyOn(spec.api!, 'search') - /** Pasting to avoid triggering search multiple times */ await userEvent.paste(input, searchPattern) await waitFor(() => { + expect(mockHistoryPush).toHaveBeenCalledWith({ + search: `s=${searchPattern}`, + }) + }) + }) + + test('URL with search parameters drives state and filters methods and types', async () => { + const searchPattern = 'embedsso' + const store = createTestStore({ + settings: { searchPattern: searchPattern }, + }) + jest.spyOn(spec.api!, 'search') + renderWithRouterAndReduxProvider(, undefined, store) + await waitFor(() => { + const input = screen.getByLabelText('Search') + // expect(input).toHaveTextContent(searchPattern) expect(spec.api!.search).toHaveBeenCalledWith( searchPattern, criteriaToSet(defaultSettingsState.searchCriteria) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 9d836a32e..0c79a2cdf 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -138,7 +138,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { let newTypes let newTypeTags const api = spec.api || ({} as ApiModel) - setSearchPattern(searchPattern) + // setSearchPattern(searchPattern) if (searchPattern && api.search) { results = api.search(searchPattern, criteriaToSet(searchCriteria)) @@ -158,7 +158,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { methodCount: countMethods(newTags), searchResults: results, }) - }, [searchPattern, specKey, spec, searchCriteria, history.location]) + }, [searchPattern, specKey, spec, searchCriteria]) useEffect(() => { const { selectedIndex, onSelectTab } = tabs From 671d3dcb70ab5f6a11f443bce128351a62c331b1 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 13 Jul 2022 20:09:45 +0000 Subject: [PATCH 13/30] WIP: working SideNav tests --- .../api-explorer/src/APIExplorer.spec.tsx | 25 ++++++++++++ .../src/components/SideNav/SideNav.spec.tsx | 38 ++----------------- 2 files changed, 28 insertions(+), 35 deletions(-) create mode 100644 packages/api-explorer/src/APIExplorer.spec.tsx diff --git a/packages/api-explorer/src/APIExplorer.spec.tsx b/packages/api-explorer/src/APIExplorer.spec.tsx new file mode 100644 index 000000000..2497c86ea --- /dev/null +++ b/packages/api-explorer/src/APIExplorer.spec.tsx @@ -0,0 +1,25 @@ +/* + + MIT License + + Copyright (c) 2022 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 54ed662d8..a1a7c0977 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -33,7 +33,7 @@ import { createTestStore, renderWithRouterAndReduxProvider, } from '../../test-utils' -import { defaultSettingsState, useSettingActions } from '../../state' +import { defaultSettingsState } from '../../state' import { SideNav } from './SideNav' import { countMethods, countTypes } from './searchUtils' @@ -92,17 +92,9 @@ describe('SideNav', () => { }) // TODO: tests for search query nav -// 1) inputting text in search updates search box and URL -// 2) updated URL results in search triggered // 3) searching with param in URL returns searched page // 4) back / forward navigation maintains previous state -/* - TODO: what is going on here is that the route gets pushed, however the URL - cannot drive state unless the APIExplorer useEffect for location.search - is executed, so we need to mock that. - */ - const mockHistoryPush = jest.fn() jest.mock('react-router-dom', () => { const ReactRouterDOM = jest.requireActual('react-router-dom') @@ -135,9 +127,9 @@ describe('Search', () => { }) jest.spyOn(spec.api!, 'search') renderWithRouterAndReduxProvider(, undefined, store) + const input = screen.getByLabelText('Search') + expect(input).toHaveValue(searchPattern) await waitFor(() => { - const input = screen.getByLabelText('Search') - // expect(input).toHaveTextContent(searchPattern) expect(spec.api!.search).toHaveBeenCalledWith( searchPattern, criteriaToSet(defaultSettingsState.searchCriteria) @@ -152,28 +144,4 @@ describe('Search', () => { expect(screen.getByText('EmbedSso')).toBeInTheDocument() }) }) - // test('Entering URL with search parameter drives search state', async () => { - // renderWithRouterAndReduxProvider(, [ - // '/3.1/methods?s=test', - // ]) - // const input = screen.getByLabelText('Search') - // jest.spyOn(spec.api!, 'search') - // expect(input).toContain('test') - // /** Pasting to avoid triggering search multiple times */ - // await userEvent.paste(input, searchPattern) - // await waitFor(() => { - // expect(spec.api!.search).toHaveBeenCalledWith( - // searchPattern, - // criteriaToSet(defaultSettingsState.searchCriteria) - // ) - // const methods = screen.getByRole('tab', { name: 'Methods (1)' }) - // userEvent.click(methods) - // expect( - // screen.getByText(spec.api!.tags.Auth.create_sso_embed_url.summary) - // ).toBeInTheDocument() - // const types = screen.getByRole('tab', { name: 'Types (1)' }) - // userEvent.click(types) - // expect(screen.getByText('EmbedSso')).toBeInTheDocument() - // }) - // }) }) From 281eb27cf3888a7eeb8b2c500921847d41be5df2 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 13 Jul 2022 20:37:58 +0000 Subject: [PATCH 14/30] Tests working, need to now add final few --- .../src/components/SideNav/SideNavMethods.spec.tsx | 11 +++++++++-- .../src/components/SideNav/SideNavTypes.spec.tsx | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.spec.tsx index 8a4b0bb82..10a222375 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.spec.tsx @@ -42,6 +42,7 @@ jest.mock('react-router-dom', () => { ...ReactRouterDOM, useHistory: () => ({ push: mockHistoryPush, + location, }), } }) @@ -71,7 +72,10 @@ describe('SideNavMethods', () => { const firstMethod = Object.values(methods)[0].schema.summary expect(screen.queryByText(firstMethod)).not.toBeInTheDocument() userEvent.click(screen.getByText(tag)) - expect(mockHistoryPush).toHaveBeenCalledWith(`/${specKey}/methods/${tag}`) + expect(mockHistoryPush).toHaveBeenCalledWith({ + pathname: `/${specKey}/methods/${tag}`, + search: '', + }) expect(screen.getByRole('link', { name: firstMethod })).toBeInTheDocument() expect(screen.getAllByRole('link')).toHaveLength( Object.values(methods).length @@ -93,7 +97,10 @@ describe('SideNavMethods', () => { Object.values(methods).length ) userEvent.click(screen.getByText(tag)) - expect(mockHistoryPush).toHaveBeenCalledWith(`/${specKey}/methods`) + expect(mockHistoryPush).toHaveBeenCalledWith({ + pathname: `/${specKey}/methods`, + search: '', + }) expect(screen.queryByText(firstMethod)).not.toBeInTheDocument() expect(screen.queryByRole('link')).not.toBeInTheDocument() }) diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.spec.tsx index eee796174..f3cc00b0b 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.spec.tsx @@ -41,6 +41,7 @@ jest.mock('react-router-dom', () => { ...ReactRouterDOM, useHistory: () => ({ push: mockHistoryPush, + location, }), } }) @@ -63,7 +64,10 @@ describe('SideNavTypes', () => { ) expect(screen.queryByText(typeTags[0])).not.toBeInTheDocument() userEvent.click(screen.getByText(tag)) - expect(mockHistoryPush).toHaveBeenCalledWith(`/${specKey}/types/${tag}`) + expect(mockHistoryPush).toHaveBeenCalledWith({ + pathname: `/${specKey}/types/${tag}`, + search: '', + }) expect(screen.getByRole('link', { name: typeTags[0] })).toBeInTheDocument() }) @@ -78,7 +82,10 @@ describe('SideNavTypes', () => { ) expect(screen.getByRole('link', { name: typeTags[0] })).toBeInTheDocument() userEvent.click(screen.getAllByText(tag)[0]) - expect(mockHistoryPush).toHaveBeenCalledWith(`/${specKey}/types`) + expect(mockHistoryPush).toHaveBeenCalledWith({ + pathname: `/${specKey}/types`, + search: '', + }) expect( screen.queryByRole('link', { name: typeTags[0] }) ).not.toBeInTheDocument() From 14679703bca588fd923402e986a690a4a0346166 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Wed, 13 Jul 2022 22:06:54 +0000 Subject: [PATCH 15/30] New properly working navigate function with unit tests, working existing tests, WIP: testing URL sharing --- ...Explorer.spec.tsx => ApiExplorer.spec.tsx} | 0 .../SelectorContainer/ApiSpecSelector.tsx | 2 +- .../src/components/SideNav/SideNav.spec.tsx | 11 ++- .../src/components/SideNav/SideNav.tsx | 8 +- .../src/components/SideNav/SideNavMethods.tsx | 4 +- .../src/components/SideNav/SideNavTypes.tsx | 4 +- .../src/scenes/MethodScene/MethodScene.tsx | 4 +- .../scenes/MethodTagScene/MethodTagScene.tsx | 4 +- .../src/scenes/TypeScene/TypeScene.tsx | 10 ++- .../src/scenes/TypeTagScene/TypeTagScene.tsx | 2 +- .../api-explorer/src/utils/navUtils.spec.ts | 77 +++++++++++++++++++ packages/api-explorer/src/utils/navUtils.ts | 35 +++++---- 12 files changed, 126 insertions(+), 35 deletions(-) rename packages/api-explorer/src/{APIExplorer.spec.tsx => ApiExplorer.spec.tsx} (100%) create mode 100644 packages/api-explorer/src/utils/navUtils.spec.ts diff --git a/packages/api-explorer/src/APIExplorer.spec.tsx b/packages/api-explorer/src/ApiExplorer.spec.tsx similarity index 100% rename from packages/api-explorer/src/APIExplorer.spec.tsx rename to packages/api-explorer/src/ApiExplorer.spec.tsx diff --git a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx index 7817973d2..d7ce4ded7 100644 --- a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx +++ b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx @@ -50,7 +50,7 @@ export const ApiSpecSelector: FC = ({ spec }) => { const handleChange = (specKey: string) => { const matchPath = location.pathname.replace(`/${spec.key}`, `/${specKey}`) - navigate(matchPath, {}, history) + navigate(matchPath, history) // history.push(matchPath) } diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index a1a7c0977..650c6a7e7 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -109,24 +109,29 @@ jest.mock('react-router-dom', () => { describe('Search', () => { test('inputting text in search box updates URL', async () => { - renderWithRouterAndReduxProvider(, undefined) + renderWithRouterAndReduxProvider(, ['/3.1/methods']) const searchPattern = 'embedsso' const input = screen.getByLabelText('Search') await userEvent.paste(input, searchPattern) await waitFor(() => { expect(mockHistoryPush).toHaveBeenCalledWith({ + pathname: '/3.1/methods', search: `s=${searchPattern}`, }) }) }) - test('URL with search parameters drives state and filters methods and types', async () => { + test('State variable tied to URL parameters drives filtering of methods and types', async () => { const searchPattern = 'embedsso' const store = createTestStore({ settings: { searchPattern: searchPattern }, }) jest.spyOn(spec.api!, 'search') - renderWithRouterAndReduxProvider(, undefined, store) + renderWithRouterAndReduxProvider( + , + ['/3.1/methods?s=embedsso'], + store + ) const input = screen.getByLabelText('Search') expect(input).toHaveValue(searchPattern) await waitFor(() => { diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 0c79a2cdf..ab23efcd6 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -84,7 +84,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { if (parts[1] === 'diff') { if (parts[3] !== tabNames[index]) { parts[3] = tabNames[index] - navigate(parts.join('/'), {}, history) + navigate(parts.join('/'), history) // history.push({ // pathname: parts.join('/'), // search: searchParams.toString(), @@ -93,7 +93,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { } else { if (parts[2] !== tabNames[index]) { parts[2] = tabNames[index] - navigate(parts.join('/'), {}, history) + navigate(parts.join('/'), history) // history.push({ // pathname: parts.join('/'), // search: searchParams.toString(), @@ -122,12 +122,12 @@ export const SideNav: FC = ({ headless = false, spec }) => { useEffect(() => { if (debouncedPattern && debouncedPattern !== searchParams.get('s')) { searchParams.set('s', debouncedPattern) - navigate(null, { search: searchParams.toString() }, history) + navigate(location.pathname, history, { search: searchParams.toString() }) // history.push({ search: searchParams.toString() }) } else if (!debouncedPattern && searchParams.get('s')) { searchParams.delete('s') - navigate(null, { search: searchParams.toString() }, history) + navigate(location.pathname, history, { search: searchParams.toString() }) // history.push({ search: searchParams.toString() }) } }, [debouncedPattern]) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index 0c642b8b0..0278779e3 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -57,9 +57,9 @@ export const SideNavMethods = styled( const _isOpen = !isOpen setIsOpen(_isOpen) if (_isOpen) { - navigate(`/${specKey}/methods/${tag}`, {}, history) + navigate(`/${specKey}/methods/${tag}`, history) } else { - navigate(`/${specKey}/methods`, {}, history) + navigate(`/${specKey}/methods`, history) } } diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx index 86fd11b31..d8afc587d 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx @@ -56,13 +56,13 @@ export const SideNavTypes = styled( const _isOpen = !isOpen setIsOpen(_isOpen) if (_isOpen) { - navigate(`/${specKey}/types/${tag}`, {}, history) + navigate(`/${specKey}/types/${tag}`, history) // history.push({ // pathname: `/${specKey}/types/${tag}`, // search: new URLSearchParams(location.search).toString(), // }) } else { - navigate(`/${specKey}/types`, {}, history) + navigate(`/${specKey}/types`, history) } } diff --git a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx index aadb6078a..19aba81c8 100644 --- a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx +++ b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx @@ -95,10 +95,10 @@ export const MethodScene: FC = ({ api }) => { // Invalid method if (api.tags[methodTag]) { // Found tag though - navigate(`/${specKey}/methods/${methodTag}`, {}, history) + navigate(`/${specKey}/methods/${methodTag}`, history) // history.push(`/${specKey}/methods/${methodTag}`) } else { - navigate(`/${specKey}/methods`, {}, history) + navigate(`/${specKey}/methods`, history) // history.push(`/${specKey}/methods`) } } diff --git a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx index ed54e28f0..6db68dcd2 100644 --- a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx +++ b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx @@ -30,6 +30,7 @@ import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' import { ApixSection, DocTitle, DocMethodSummary, Link } from '../../components' import { buildMethodPath } from '../../utils' +import { navigate } from '../../utils/navUtils' import { getOperations } from './utils' interface MethodTagSceneProps { @@ -54,7 +55,8 @@ export const MethodTagScene: FC = ({ api }) => { const methods = api.tags[methodTag] useEffect(() => { if (!methods) { - history.push(`/${specKey}/methods`) + navigate(`/${specKey}/methods`, history) + // history.push(`/${specKey}/methods`) } }, [history, methods]) if (!methods) { diff --git a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx index ed3ec1703..d3a02eab0 100644 --- a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx +++ b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx @@ -40,6 +40,7 @@ import { ExploreType, DocSchema, } from '../../components' +import { navigate } from '../../utils/navUtils' interface DocTypeProps { api: ApiModel @@ -47,11 +48,12 @@ interface DocTypeProps { interface DocTypeParams { specKey: string + typeTag: string typeName: string } export const TypeScene: FC = ({ api }) => { - const { specKey, typeName } = useParams() + const { specKey, typeTag, typeName } = useParams() const type = api.types[typeName] const history = useHistory() const typesUsed = typeRefs(api, type?.customTypes) @@ -59,7 +61,11 @@ export const TypeScene: FC = ({ api }) => { const typesUsedBy = typeRefs(api, type?.parentTypes) useEffect(() => { if (!type) { - history.push(`/${specKey}/types`) + const route = api.typeTags[typeTag] + ? `/${specKey}/types/${typeTag}` + : `/${specKey}/types` + navigate(route, history) + // history.push(`/${specKey}/types`) } }, [history, specKey, type]) diff --git a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx index 808ceb4fc..5d40e056c 100644 --- a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx +++ b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx @@ -55,7 +55,7 @@ export const TypeTagScene: FC = ({ api }) => { const types = api.typeTags[typeTag] useEffect(() => { if (!types) { - navigate(`/${specKey}/types`, {}, history) + navigate(`/${specKey}/types`, history) // history.push(`/${specKey}/types`) } }, [history, types]) diff --git a/packages/api-explorer/src/utils/navUtils.spec.ts b/packages/api-explorer/src/utils/navUtils.spec.ts new file mode 100644 index 000000000..6c552d97c --- /dev/null +++ b/packages/api-explorer/src/utils/navUtils.spec.ts @@ -0,0 +1,77 @@ +/* + + MIT License + + Copyright (c) 2022 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ +import { useHistory } from 'react-router-dom' +import { navigate } from './navUtils' + +const mockHistoryPush = jest.fn() +jest.mock('react-router-dom', () => { + const ReactRouterDOM = jest.requireActual('react-router-dom') + return { + ...ReactRouterDOM, + useHistory: () => ({ + push: mockHistoryPush, + location: { pathname: '/3.1/methods', search: 's=test' }, + }), + } +}) + +describe('Navigate', () => { + const history = useHistory() + const curParams = new URLSearchParams(history.location.search) // 's=test' + const route = `/3.1` + + test('if query parameters is undefined, will maintain existing parameters in URL', () => { + navigate(route, history, undefined) + expect(curParams.get('s')).toBe('test') + expect(mockHistoryPush).lastCalledWith({ + pathname: route, + search: curParams.toString(), + }) + }) + + test('if query parameters is null, remove all parameters from URL', () => { + navigate(route, history, null) + expect(mockHistoryPush).lastCalledWith({ + pathname: route, + }) + }) + + test('if query parameters is an empty object, remove all parameters from URL', () => { + navigate(route, history, {}) + expect(mockHistoryPush).lastCalledWith({ + pathname: route, + }) + }) + + test('if query parameters are passed in, push to the URL', () => { + const newParams = 's=embedsso' + navigate(route, history, { search: newParams }) + expect(mockHistoryPush).lastCalledWith({ + pathname: route, + search: newParams, + }) + }) +}) diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/navUtils.ts index 1aada3eb6..4e932b0e7 100644 --- a/packages/api-explorer/src/utils/navUtils.ts +++ b/packages/api-explorer/src/utils/navUtils.ts @@ -2,7 +2,7 @@ MIT License - Copyright (c) 2021 Looker Data Sciences, Inc. + Copyright (c) 2022 Looker Data Sciences, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25,25 +25,26 @@ */ import type { History } from 'history' +/** + * + * @param route the pathname to which you want to push to history + * @param history caller's history object to be modified + * @param queryParams parameters to add to the URL + */ export const navigate = ( - route: string | null, - newParams: { search?: string }, - history: History -): void => { + route: string, + history: History, + queryParams?: { search?: string } | null +) => { const curParams = new URLSearchParams(history.location.search) - if (!route) { - // we are pushing something to current - history.push({ search: newParams.search }) - return - } - if (!newParams) { - // if it is null, empty params - history.push({ pathname: route }) - } else if (Object.keys(newParams).length === 0) { - // if params is empty, leave the path be + if (queryParams === undefined) { + // if params passed in is undefined, maintain existing params in URL history.push({ pathname: route, search: curParams.toString() }) + } else if (queryParams === null || Object.keys(queryParams).length === 0) { + // if params passed in is null or empty, remove all params from URL + history.push({ pathname: route }) } else { - // push the given parameters - history.push({ pathname: route, search: newParams.search }) + // if we have new parameters passed in, push to URL + history.push({ pathname: route, search: queryParams.search }) } } From 1f5ac100b9528cb68d961f313a5a1bb64311208a Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Thu, 14 Jul 2022 00:34:31 +0000 Subject: [PATCH 16/30] Refactoring some comments out, fixed to pass tests, removed APIExplorer.spec.tsx --- .../api-explorer/src/ApiExplorer.spec.tsx | 25 ------------------- .../ApiSpecSelector.spec.tsx | 7 ++++-- .../SelectorContainer/ApiSpecSelector.tsx | 1 - .../src/components/SideNav/SideNav.spec.tsx | 6 +---- .../src/components/SideNav/SideNav.tsx | 12 --------- .../src/components/SideNav/SideNavTypes.tsx | 4 --- .../src/scenes/MethodScene/MethodScene.tsx | 2 -- .../scenes/MethodTagScene/MethodTagScene.tsx | 1 - .../src/scenes/TypeScene/TypeScene.tsx | 1 - .../src/scenes/TypeTagScene/TypeTagScene.tsx | 1 - 10 files changed, 6 insertions(+), 54 deletions(-) delete mode 100644 packages/api-explorer/src/ApiExplorer.spec.tsx diff --git a/packages/api-explorer/src/ApiExplorer.spec.tsx b/packages/api-explorer/src/ApiExplorer.spec.tsx deleted file mode 100644 index 2497c86ea..000000000 --- a/packages/api-explorer/src/ApiExplorer.spec.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - - MIT License - - Copyright (c) 2022 Looker Data Sciences, Inc. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */ diff --git a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.spec.tsx b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.spec.tsx index 5fe012753..ddbb0e8a3 100644 --- a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.spec.tsx +++ b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.spec.tsx @@ -41,7 +41,7 @@ jest.mock('react-router-dom', () => { useLocation: () => ({ pathname: '/4.0/methods/Dashboard/dashboard', }), - useHistory: jest.fn().mockReturnValue({ push: jest.fn() }), + useHistory: jest.fn().mockReturnValue({ push: jest.fn(), location }), } }) @@ -83,6 +83,9 @@ describe('ApiSpecSelector', () => { }) const button = screen.getByText('3.1') userEvent.click(button) - expect(push).toHaveBeenCalledWith('/3.1/methods/Dashboard/dashboard') + expect(push).toHaveBeenCalledWith({ + pathname: '/3.1/methods/Dashboard/dashboard', + search: '', + }) }) }) diff --git a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx index d7ce4ded7..b0a3f60e5 100644 --- a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx +++ b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx @@ -51,7 +51,6 @@ export const ApiSpecSelector: FC = ({ spec }) => { const handleChange = (specKey: string) => { const matchPath = location.pathname.replace(`/${spec.key}`, `/${specKey}`) navigate(matchPath, history) - // history.push(matchPath) } return ( diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 650c6a7e7..88483936e 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -91,10 +91,6 @@ describe('SideNav', () => { }) }) -// TODO: tests for search query nav -// 3) searching with param in URL returns searched page -// 4) back / forward navigation maintains previous state - const mockHistoryPush = jest.fn() jest.mock('react-router-dom', () => { const ReactRouterDOM = jest.requireActual('react-router-dom') @@ -121,7 +117,7 @@ describe('Search', () => { }) }) - test('State variable tied to URL parameters drives filtering of methods and types', async () => { + test('Store variable for search pattern tied to URL parameters drives filtering of methods and types', async () => { const searchPattern = 'embedsso' const store = createTestStore({ settings: { searchPattern: searchPattern }, diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index ab23efcd6..2297dbc18 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -85,19 +85,11 @@ export const SideNav: FC = ({ headless = false, spec }) => { if (parts[3] !== tabNames[index]) { parts[3] = tabNames[index] navigate(parts.join('/'), history) - // history.push({ - // pathname: parts.join('/'), - // search: searchParams.toString(), - // }) } } else { if (parts[2] !== tabNames[index]) { parts[2] = tabNames[index] navigate(parts.join('/'), history) - // history.push({ - // pathname: parts.join('/'), - // search: searchParams.toString(), - // }) } } } @@ -123,12 +115,9 @@ export const SideNav: FC = ({ headless = false, spec }) => { if (debouncedPattern && debouncedPattern !== searchParams.get('s')) { searchParams.set('s', debouncedPattern) navigate(location.pathname, history, { search: searchParams.toString() }) - - // history.push({ search: searchParams.toString() }) } else if (!debouncedPattern && searchParams.get('s')) { searchParams.delete('s') navigate(location.pathname, history, { search: searchParams.toString() }) - // history.push({ search: searchParams.toString() }) } }, [debouncedPattern]) @@ -138,7 +127,6 @@ export const SideNav: FC = ({ headless = false, spec }) => { let newTypes let newTypeTags const api = spec.api || ({} as ApiModel) - // setSearchPattern(searchPattern) if (searchPattern && api.search) { results = api.search(searchPattern, criteriaToSet(searchCriteria)) diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx index d8afc587d..171439b14 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx @@ -57,10 +57,6 @@ export const SideNavTypes = styled( setIsOpen(_isOpen) if (_isOpen) { navigate(`/${specKey}/types/${tag}`, history) - // history.push({ - // pathname: `/${specKey}/types/${tag}`, - // search: new URLSearchParams(location.search).toString(), - // }) } else { navigate(`/${specKey}/types`, history) } diff --git a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx index 19aba81c8..2b8cb833b 100644 --- a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx +++ b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx @@ -96,10 +96,8 @@ export const MethodScene: FC = ({ api }) => { if (api.tags[methodTag]) { // Found tag though navigate(`/${specKey}/methods/${methodTag}`, history) - // history.push(`/${specKey}/methods/${methodTag}`) } else { navigate(`/${specKey}/methods`, history) - // history.push(`/${specKey}/methods`) } } }, [api, history, methodName, methodTag, specKey]) diff --git a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx index 6db68dcd2..8cd499cd1 100644 --- a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx +++ b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx @@ -56,7 +56,6 @@ export const MethodTagScene: FC = ({ api }) => { useEffect(() => { if (!methods) { navigate(`/${specKey}/methods`, history) - // history.push(`/${specKey}/methods`) } }, [history, methods]) if (!methods) { diff --git a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx index d3a02eab0..410e75074 100644 --- a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx +++ b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx @@ -65,7 +65,6 @@ export const TypeScene: FC = ({ api }) => { ? `/${specKey}/types/${typeTag}` : `/${specKey}/types` navigate(route, history) - // history.push(`/${specKey}/types`) } }, [history, specKey, type]) diff --git a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx index 5d40e056c..85a20b64c 100644 --- a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx +++ b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx @@ -56,7 +56,6 @@ export const TypeTagScene: FC = ({ api }) => { useEffect(() => { if (!types) { navigate(`/${specKey}/types`, history) - // history.push(`/${specKey}/types`) } }, [history, types]) From 2a664cd62d87b399d701f270e6b5da69b25c4127 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Thu, 14 Jul 2022 01:18:56 +0000 Subject: [PATCH 17/30] Minor refactoring to add readability/cleanliness --- .../api-explorer/src/utils/navUtils.spec.ts | 6 +++--- packages/api-explorer/src/utils/navUtils.ts | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/api-explorer/src/utils/navUtils.spec.ts b/packages/api-explorer/src/utils/navUtils.spec.ts index 6c552d97c..bebf3e4d8 100644 --- a/packages/api-explorer/src/utils/navUtils.spec.ts +++ b/packages/api-explorer/src/utils/navUtils.spec.ts @@ -52,21 +52,21 @@ describe('Navigate', () => { }) }) - test('if query parameters is null, remove all parameters from URL', () => { + test('if query parameters is null, will remove all parameters from URL', () => { navigate(route, history, null) expect(mockHistoryPush).lastCalledWith({ pathname: route, }) }) - test('if query parameters is an empty object, remove all parameters from URL', () => { + test('if query parameters is an empty object, will remove all parameters from URL', () => { navigate(route, history, {}) expect(mockHistoryPush).lastCalledWith({ pathname: route, }) }) - test('if query parameters are passed in, push to the URL', () => { + test('if new query parameters are passed in, will push to the URL', () => { const newParams = 's=embedsso' navigate(route, history, { search: newParams }) expect(mockHistoryPush).lastCalledWith({ diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/navUtils.ts index 4e932b0e7..96ef22664 100644 --- a/packages/api-explorer/src/utils/navUtils.ts +++ b/packages/api-explorer/src/utils/navUtils.ts @@ -27,24 +27,24 @@ import type { History } from 'history' /** * - * @param route the pathname to which you want to push to history - * @param history caller's history object to be modified - * @param queryParams parameters to add to the URL + * @param path the pathname to navigate to using history (empty string maintains current location) + * @param history the current browser session's history object + * @param queryParams object determining the current URLs parameters */ export const navigate = ( - route: string, + path: string, history: History, queryParams?: { search?: string } | null ) => { const curParams = new URLSearchParams(history.location.search) if (queryParams === undefined) { - // if params passed in is undefined, maintain existing params in URL - history.push({ pathname: route, search: curParams.toString() }) + // if params passed in is undefined, maintain existing parameters in the URL + history.push({ pathname: path, search: curParams.toString() }) } else if (queryParams === null || Object.keys(queryParams).length === 0) { - // if params passed in is null or empty, remove all params from URL - history.push({ pathname: route }) + // if params passed in is null or empty, remove all parameters from the URL + history.push({ pathname: path }) } else { - // if we have new parameters passed in, push to URL - history.push({ pathname: route, search: queryParams.search }) + // if we have new parameters passed in, push them to the URL + history.push({ pathname: path, search: queryParams.search }) } } From 6cb274a45ed492c58d90bdabc726a6432724e769 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Fri, 15 Jul 2022 18:57:48 +0000 Subject: [PATCH 18/30] Per feedback from John and Joseph, WIP: navigation hook --- packages/api-explorer/src/ApiExplorer.tsx | 2 +- .../src/components/SelectorContainer/ApiSpecSelector.tsx | 2 +- .../api-explorer/src/components/SideNav/SideNav.spec.tsx | 2 +- packages/api-explorer/src/components/SideNav/SideNav.tsx | 3 +-- .../src/components/SideNav/SideNavMethods.tsx | 3 +-- .../api-explorer/src/components/SideNav/SideNavTypes.tsx | 3 +-- .../api-explorer/src/scenes/MethodScene/MethodScene.tsx | 3 +-- .../src/scenes/MethodTagScene/MethodTagScene.tsx | 3 +-- packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx | 2 +- .../api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx | 3 +-- packages/api-explorer/src/utils/index.ts | 1 + packages/api-explorer/src/utils/navUtils.spec.ts | 8 ++++---- packages/api-explorer/src/utils/navUtils.ts | 7 ++++--- 13 files changed, 19 insertions(+), 23 deletions(-) diff --git a/packages/api-explorer/src/ApiExplorer.tsx b/packages/api-explorer/src/ApiExplorer.tsx index e97ae6c92..05740eb17 100644 --- a/packages/api-explorer/src/ApiExplorer.tsx +++ b/packages/api-explorer/src/ApiExplorer.tsx @@ -125,7 +125,7 @@ export const ApiExplorer: FC = ({ useEffect(() => { const searchParams = new URLSearchParams(location.search) - const searchPattern = searchParams.get('s') ? searchParams.get('s') : '' + const searchPattern = searchParams.get('s') || '' setSearchPatternAction({ searchPattern: searchPattern! }) }, [location.search]) diff --git a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx index b0a3f60e5..2121707f9 100644 --- a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx +++ b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx @@ -30,7 +30,7 @@ import { Select } from '@looker/components' import { useHistory, useLocation } from 'react-router-dom' import type { SpecItem } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' -import { navigate } from '../../utils/navUtils' +import { navigate } from '../../utils' import { selectSpecs } from '../../state' diff --git a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx index 88483936e..17300ab7e 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.spec.tsx @@ -117,7 +117,7 @@ describe('Search', () => { }) }) - test('Store variable for search pattern tied to URL parameters drives filtering of methods and types', async () => { + test('sets search default value from store on load', async () => { const searchPattern = 'embedsso' const store = createTestStore({ settings: { searchPattern: searchPattern }, diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 2297dbc18..1bbf03d68 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -44,8 +44,7 @@ import type { } from '@looker/sdk-codegen' import { criteriaToSet, tagTypes } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' -import { useWindowSize } from '../../utils' -import { navigate } from '../../utils/navUtils' +import { useWindowSize, navigate } from '../../utils' import { HEADER_REM } from '../Header' import { selectSearchCriteria, selectSearchPattern } from '../../state' import { SideNavMethodTags } from './SideNavMethodTags' diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index 0278779e3..80ee8f787 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -30,9 +30,8 @@ import { Accordion2, Heading } from '@looker/components' import type { MethodList } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { useHistory, useLocation, useRouteMatch } from 'react-router-dom' -import { navigate } from '../../utils/navUtils' +import { navigate, buildMethodPath, highlightHTML } from '../../utils' import { Link } from '../Link' -import { buildMethodPath, highlightHTML } from '../../utils' import { selectSearchPattern } from '../../state' interface MethodsProps { diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx index 171439b14..2728662eb 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx @@ -30,9 +30,8 @@ import { Accordion2, Heading } from '@looker/components' import type { TypeList } from '@looker/sdk-codegen' import { useHistory, useRouteMatch } from 'react-router-dom' import { useSelector } from 'react-redux' -import { navigate } from '../../utils/navUtils' import { Link } from '../Link' -import { buildTypePath, highlightHTML } from '../../utils' +import { buildTypePath, highlightHTML, navigate } from '../../utils' import { selectSearchPattern } from '../../state' interface TypesProps { diff --git a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx index 2b8cb833b..6d715266a 100644 --- a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx +++ b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx @@ -41,9 +41,8 @@ import type { ApiModel } from '@looker/sdk-codegen' import { typeRefs } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import type { IEnvironmentAdaptor } from '@looker/extension-utils' -import { navigate } from '../../utils/navUtils' -import { getApixAdaptor } from '../../utils' +import { getApixAdaptor, navigate } from '../../utils' import { ApixSection, DocActivityType, diff --git a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx index 8cd499cd1..841376b0d 100644 --- a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx +++ b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx @@ -29,8 +29,7 @@ import { useHistory, useParams } from 'react-router-dom' import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' import { ApixSection, DocTitle, DocMethodSummary, Link } from '../../components' -import { buildMethodPath } from '../../utils' -import { navigate } from '../../utils/navUtils' +import { buildMethodPath, navigate } from '../../utils' import { getOperations } from './utils' interface MethodTagSceneProps { diff --git a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx index 410e75074..779f2ddd6 100644 --- a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx +++ b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx @@ -40,7 +40,7 @@ import { ExploreType, DocSchema, } from '../../components' -import { navigate } from '../../utils/navUtils' +import { navigate } from '../../utils' interface DocTypeProps { api: ApiModel diff --git a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx index 85a20b64c..faa916657 100644 --- a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx +++ b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx @@ -28,9 +28,8 @@ import React, { useEffect, useState } from 'react' import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' import { useParams, useHistory } from 'react-router-dom' -import { navigate } from '../../utils/navUtils' import { ApixSection, DocTitle, DocTypeSummary, Link } from '../../components' -import { buildTypePath } from '../../utils' +import { buildTypePath, navigate } from '../../utils' import { getMetaTypes } from './utils' interface TypeTagSceneProps { diff --git a/packages/api-explorer/src/utils/index.ts b/packages/api-explorer/src/utils/index.ts index 31475165d..2c6b01517 100644 --- a/packages/api-explorer/src/utils/index.ts +++ b/packages/api-explorer/src/utils/index.ts @@ -29,3 +29,4 @@ export { getLoded } from './lodeUtils' export { useWindowSize } from './useWindowSize' export * from './apixAdaptor' export * from './adaptorUtils' +export { navigate } from './navUtils' diff --git a/packages/api-explorer/src/utils/navUtils.spec.ts b/packages/api-explorer/src/utils/navUtils.spec.ts index bebf3e4d8..e6f854fa1 100644 --- a/packages/api-explorer/src/utils/navUtils.spec.ts +++ b/packages/api-explorer/src/utils/navUtils.spec.ts @@ -43,7 +43,7 @@ describe('Navigate', () => { const curParams = new URLSearchParams(history.location.search) // 's=test' const route = `/3.1` - test('if query parameters is undefined, will maintain existing parameters in URL', () => { + test('preserves existing query params when given params are undefined', () => { navigate(route, history, undefined) expect(curParams.get('s')).toBe('test') expect(mockHistoryPush).lastCalledWith({ @@ -52,21 +52,21 @@ describe('Navigate', () => { }) }) - test('if query parameters is null, will remove all parameters from URL', () => { + test('clears existing params when given params are null', () => { navigate(route, history, null) expect(mockHistoryPush).lastCalledWith({ pathname: route, }) }) - test('if query parameters is an empty object, will remove all parameters from URL', () => { + test('clears existing params when given params are an empty object', () => { navigate(route, history, {}) expect(mockHistoryPush).lastCalledWith({ pathname: route, }) }) - test('if new query parameters are passed in, will push to the URL', () => { + test('sets query parameters when given a populated query params object', () => { const newParams = 's=embedsso' navigate(route, history, { search: newParams }) expect(mockHistoryPush).lastCalledWith({ diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/navUtils.ts index 96ef22664..77ea64bbe 100644 --- a/packages/api-explorer/src/utils/navUtils.ts +++ b/packages/api-explorer/src/utils/navUtils.ts @@ -26,10 +26,11 @@ import type { History } from 'history' /** + * Navigates to given route with specified query params * - * @param path the pathname to navigate to using history (empty string maintains current location) - * @param history the current browser session's history object - * @param queryParams object determining the current URLs parameters + * @param path Pathname to navigate to + * @param history Current browser session's history object + * @param queryParams Hash of query param name/value pairs to include in the destination url */ export const navigate = ( path: string, From 516c1069bdacc0a622408344d4a2628b6b3f1d25 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Fri, 15 Jul 2022 22:54:31 +0000 Subject: [PATCH 19/30] Created custom navigation hook, replaced history pushes where necessary --- .../DocMarkdown/DocMarkdown.spec.tsx | 27 +++++++--- .../components/DocMarkdown/DocMarkdown.tsx | 8 +-- .../SelectorContainer/ApiSpecSelector.tsx | 8 +-- .../src/components/SideNav/SideNav.tsx | 14 ++--- .../src/components/SideNav/SideNavMethods.tsx | 10 ++-- .../src/components/SideNav/SideNavTypes.tsx | 7 +-- .../src/scenes/DiffScene/DiffScene.tsx | 12 ++--- .../DiffScene/DocDiff/DiffItem.spec.tsx | 7 +-- .../src/scenes/DiffScene/DocDiff/DiffItem.tsx | 7 ++- .../src/scenes/MethodScene/MethodScene.tsx | 7 +-- .../scenes/MethodTagScene/MethodTagScene.tsx | 5 +- .../src/scenes/TypeScene/TypeScene.tsx | 5 +- .../src/scenes/TypeTagScene/TypeTagScene.tsx | 5 +- packages/api-explorer/src/utils/index.ts | 2 +- .../api-explorer/src/utils/navUtils.spec.ts | 11 ++-- packages/api-explorer/src/utils/navUtils.ts | 52 +++++++++++++------ 16 files changed, 112 insertions(+), 75 deletions(-) diff --git a/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx b/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx index 98145c97d..f312359c1 100644 --- a/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx +++ b/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx @@ -26,13 +26,16 @@ import React from 'react' import { screen } from '@testing-library/react' -import { createTestStore, renderWithReduxProvider } from '../../test-utils' +import { + createTestStore, + renderWithRouterAndReduxProvider, +} from '../../test-utils' import { DocMarkdown } from './DocMarkdown' describe('DocMarkdown', () => { test('it renders markdown', () => { - renderWithReduxProvider( + renderWithRouterAndReduxProvider( { test('it remaps hashbang urls found in markdown input', () => { const input = 'A link to the [create_dashboard](#!/Dashboard/create_dashboard) endpoint' - renderWithReduxProvider() + renderWithRouterAndReduxProvider( + + ) expect(screen.getByText(/A link to the/)).toBeInTheDocument() expect(screen.getByText('create_dashboard')).toHaveAttribute( 'href', @@ -62,7 +67,7 @@ describe('DocMarkdown', () => { }) test('it leaves external urls untouched', () => { - renderWithReduxProvider( + renderWithRouterAndReduxProvider( { const store = createTestStore({ settings: { searchPattern: highlightPattern }, }) - renderWithReduxProvider( + renderWithRouterAndReduxProvider( , + undefined, store ) const mark = screen.getByText(highlightPattern) @@ -95,13 +101,14 @@ describe('DocMarkdown', () => { const store = createTestStore({ settings: { searchPattern: highlightPattern }, }) - renderWithReduxProvider( + renderWithRouterAndReduxProvider( , + undefined, store ) const link = screen.getByRole('link') @@ -115,7 +122,9 @@ describe('DocMarkdown', () => { test('it renders code blocks', () => { const code = '```\nAuthorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4\n```' - renderWithReduxProvider() + renderWithRouterAndReduxProvider( + + ) expect( screen.getByText( 'Authorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4' @@ -125,7 +134,9 @@ describe('DocMarkdown', () => { test('it renders inline code', () => { const markdown = 'Some text with code: `const noop = () => null`' - renderWithReduxProvider() + renderWithRouterAndReduxProvider( + + ) expect(screen.getByText('const noop = () => null')).toBeInTheDocument() }) }) diff --git a/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.tsx b/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.tsx index 71a83b5a7..98ebecc08 100644 --- a/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.tsx +++ b/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.tsx @@ -26,11 +26,11 @@ import type { FC } from 'react' import React from 'react' -import { useHistory } from 'react-router-dom' import { Markdown } from '@looker/code-editor' import { useSelector } from 'react-redux' import { getEnvAdaptor } from '@looker/extension-utils' import { selectSearchPattern } from '../../state' +import { useNavigation } from '../../utils' import { transformURL } from './utils' interface DocMarkdownProps { @@ -40,13 +40,13 @@ interface DocMarkdownProps { export const DocMarkdown: FC = ({ source, specKey }) => { const searchPattern = useSelector(selectSearchPattern) - const history = useHistory() + const navigate = useNavigation() const linkClickHandler = (pathname: string, url: string) => { if (pathname.startsWith(`/${specKey}`)) { - history.push(pathname) + navigate(pathname) } else if (url.startsWith(`/${specKey}`)) { - history.push(url) + navigate(url) } else if (url.startsWith('https://')) { const adaptor = getEnvAdaptor() adaptor.openBrowserWindow(url) diff --git a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx index 2121707f9..9f4800025 100644 --- a/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx +++ b/packages/api-explorer/src/components/SelectorContainer/ApiSpecSelector.tsx @@ -27,10 +27,10 @@ import type { FC } from 'react' import React from 'react' import { Select } from '@looker/components' -import { useHistory, useLocation } from 'react-router-dom' +import { useLocation } from 'react-router-dom' import type { SpecItem } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' -import { navigate } from '../../utils' +import { useNavigation } from '../../utils' import { selectSpecs } from '../../state' @@ -39,8 +39,8 @@ interface ApiSpecSelectorProps { } export const ApiSpecSelector: FC = ({ spec }) => { - const history = useHistory() const location = useLocation() + const navigate = useNavigation() const specs = useSelector(selectSpecs) const options = Object.entries(specs).map(([key, spec]) => ({ value: key, @@ -50,7 +50,7 @@ export const ApiSpecSelector: FC = ({ spec }) => { const handleChange = (specKey: string) => { const matchPath = location.pathname.replace(`/${spec.key}`, `/${specKey}`) - navigate(matchPath, history) + navigate(matchPath) } return ( diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 1bbf03d68..2e19eb6a6 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -26,7 +26,7 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' -import { useHistory, useLocation } from 'react-router-dom' +import { useLocation } from 'react-router-dom' import { TabList, Tab, @@ -44,7 +44,7 @@ import type { } from '@looker/sdk-codegen' import { criteriaToSet, tagTypes } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' -import { useWindowSize, navigate } from '../../utils' +import { useWindowSize, useNavigation } from '../../utils' import { HEADER_REM } from '../Header' import { selectSearchCriteria, selectSearchPattern } from '../../state' import { SideNavMethodTags } from './SideNavMethodTags' @@ -67,8 +67,8 @@ interface SideNavProps { } export const SideNav: FC = ({ headless = false, spec }) => { - const history = useHistory() const location = useLocation() + const navigate = useNavigation() const searchParams = new URLSearchParams(location.search) const specKey = spec.key const tabNames = ['methods', 'types'] @@ -83,12 +83,12 @@ export const SideNav: FC = ({ headless = false, spec }) => { if (parts[1] === 'diff') { if (parts[3] !== tabNames[index]) { parts[3] = tabNames[index] - navigate(parts.join('/'), history) + navigate(parts.join('/')) } } else { if (parts[2] !== tabNames[index]) { parts[2] = tabNames[index] - navigate(parts.join('/'), history) + navigate(parts.join('/')) } } } @@ -113,10 +113,10 @@ export const SideNav: FC = ({ headless = false, spec }) => { useEffect(() => { if (debouncedPattern && debouncedPattern !== searchParams.get('s')) { searchParams.set('s', debouncedPattern) - navigate(location.pathname, history, { search: searchParams.toString() }) + navigate(location.pathname, { search: searchParams.toString() }) } else if (!debouncedPattern && searchParams.get('s')) { searchParams.delete('s') - navigate(location.pathname, history, { search: searchParams.toString() }) + navigate(location.pathname, { search: searchParams.toString() }) } }, [debouncedPattern]) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index 80ee8f787..1e2ccb9d1 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -29,8 +29,8 @@ import styled from 'styled-components' import { Accordion2, Heading } from '@looker/components' import type { MethodList } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' -import { useHistory, useLocation, useRouteMatch } from 'react-router-dom' -import { navigate, buildMethodPath, highlightHTML } from '../../utils' +import { useLocation, useRouteMatch } from 'react-router-dom' +import { useNavigation, buildMethodPath, highlightHTML } from '../../utils' import { Link } from '../Link' import { selectSearchPattern } from '../../state' @@ -44,8 +44,8 @@ interface MethodsProps { export const SideNavMethods = styled( ({ className, methods, tag, specKey, defaultOpen = false }: MethodsProps) => { - const history = useHistory() const location = useLocation() + const navigate = useNavigation() const searchParams = new URLSearchParams(location.search) const searchPattern = useSelector(selectSearchPattern) const match = useRouteMatch<{ methodTag: string }>( @@ -56,9 +56,9 @@ export const SideNavMethods = styled( const _isOpen = !isOpen setIsOpen(_isOpen) if (_isOpen) { - navigate(`/${specKey}/methods/${tag}`, history) + navigate(`/${specKey}/methods/${tag}`) } else { - navigate(`/${specKey}/methods`, history) + navigate(`/${specKey}/methods`) } } diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx index 2728662eb..e4ee6dd4d 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx @@ -31,7 +31,7 @@ import type { TypeList } from '@looker/sdk-codegen' import { useHistory, useRouteMatch } from 'react-router-dom' import { useSelector } from 'react-redux' import { Link } from '../Link' -import { buildTypePath, highlightHTML, navigate } from '../../utils' +import { buildTypePath, highlightHTML, useNavigation } from '../../utils' import { selectSearchPattern } from '../../state' interface TypesProps { @@ -45,6 +45,7 @@ interface TypesProps { export const SideNavTypes = styled( ({ className, types, tag, specKey, defaultOpen = false }: TypesProps) => { const history = useHistory() + const navigate = useNavigation() const searchParams = new URLSearchParams(history.location.search) const searchPattern = useSelector(selectSearchPattern) const match = useRouteMatch<{ typeTag: string }>( @@ -55,9 +56,9 @@ export const SideNavTypes = styled( const _isOpen = !isOpen setIsOpen(_isOpen) if (_isOpen) { - navigate(`/${specKey}/types/${tag}`, history) + navigate(`/${specKey}/types/${tag}`) } else { - navigate(`/${specKey}/types`, history) + navigate(`/${specKey}/types`) } } diff --git a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx index 0adf0855e..f14c1ec59 100644 --- a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx +++ b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx @@ -27,7 +27,7 @@ import type { FC } from 'react' import React, { useState, useEffect } from 'react' import type { ApiModel, DiffRow, SpecList } from '@looker/sdk-codegen' -import { useHistory, useRouteMatch } from 'react-router-dom' +import { useRouteMatch } from 'react-router-dom' import { Box, Flex, @@ -42,7 +42,7 @@ import { useSelector } from 'react-redux' import { ApixSection } from '../../components' import { selectCurrentSpec } from '../../state' -import { diffPath, getApixAdaptor } from '../../utils' +import { diffPath, getApixAdaptor, useNavigation } from '../../utils' import { diffSpecs, standardDiffToggles } from './diffUtils' import { DocDiff } from './DocDiff' @@ -84,7 +84,7 @@ const validateParam = (specs: SpecList, specKey = '') => { export const DiffScene: FC = ({ specs, toggleNavigation }) => { const adaptor = getApixAdaptor() - const history = useHistory() + const navigate = useNavigation() const spec = useSelector(selectCurrentSpec) const currentSpecKey = spec.key const match = useRouteMatch<{ l: string; r: string }>(`/${diffPath}/:l?/:r?`) @@ -123,14 +123,14 @@ export const DiffScene: FC = ({ specs, toggleNavigation }) => { const [delta, setDelta] = useState([]) const handleLeftChange = (newLeft: string) => { - history.push(`/${diffPath}/${newLeft}/${rightKey}`) + navigate(`/${diffPath}/${newLeft}/${rightKey}`) } const handleRightChange = (newRight: string) => { - history.push(`/${diffPath}/${leftKey}/${newRight}`) + navigate(`/${diffPath}/${leftKey}/${newRight}`) } const handleSwitch = () => { - history.push(`/${diffPath}/${rightKey}/${leftKey}`) + navigate(`/${diffPath}/${rightKey}/${leftKey}`) } useEffect(() => { diff --git a/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.spec.tsx b/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.spec.tsx index 8ba670eaa..01109034c 100644 --- a/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.spec.tsx +++ b/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.spec.tsx @@ -51,9 +51,10 @@ describe('DiffMethodLink', () => { const link = screen.getByRole('link') expect(link).toHaveTextContent(`${method.name} for ${specKey}`) fireEvent.click(link) - expect(pushSpy).toHaveBeenCalledWith( - `/${specKey}/methods/${method.schema.tags[0]}/${method.name}` - ) + expect(pushSpy).toHaveBeenCalledWith({ + pathname: `/${specKey}/methods/${method.schema.tags[0]}/${method.name}`, + search: '', + }) }) test('it renders missing method and does not navigate on click', () => { diff --git a/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.tsx b/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.tsx index aac37734c..4c7feb409 100644 --- a/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.tsx +++ b/packages/api-explorer/src/scenes/DiffScene/DocDiff/DiffItem.tsx @@ -25,7 +25,6 @@ */ import type { FC } from 'react' import React, { useState, useEffect } from 'react' -import { useHistory } from 'react-router' import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer' import styled from 'styled-components' import { Accordion2, Box, Card, Grid, Heading, Link } from '@looker/components' @@ -33,7 +32,7 @@ import type { DiffRow } from '@looker/sdk-codegen/src' import type { ApiModel, IMethod } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { selectSdkLanguage } from '../../../state' -import { buildMethodPath } from '../../../utils' +import { buildMethodPath, useNavigation } from '../../../utils' import { DiffBanner } from './DiffBanner' import { differ } from './docDiffUtils' @@ -60,7 +59,7 @@ export const DiffMethodLink: FC = ({ method, specKey, }) => { - const history = useHistory() + const navigate = useNavigation() if (!method) return {`Missing in ${specKey}`} @@ -71,7 +70,7 @@ export const DiffMethodLink: FC = ({ { - history.push(path) + navigate(path) }} >{`${method.name} for ${specKey}`} ) diff --git a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx index 6d715266a..64ccf0726 100644 --- a/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx +++ b/packages/api-explorer/src/scenes/MethodScene/MethodScene.tsx @@ -42,7 +42,7 @@ import { typeRefs } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import type { IEnvironmentAdaptor } from '@looker/extension-utils' -import { getApixAdaptor, navigate } from '../../utils' +import { getApixAdaptor, useNavigation } from '../../utils' import { ApixSection, DocActivityType, @@ -78,6 +78,7 @@ const showRunIt = async (adaptor: IEnvironmentAdaptor) => { export const MethodScene: FC = ({ api }) => { const adaptor = getApixAdaptor() const history = useHistory() + const navigate = useNavigation() const sdkLanguage = useSelector(selectSdkLanguage) const { specKey, methodTag, methodName } = useParams() const { value, toggle, setOn } = useToggle() @@ -94,9 +95,9 @@ export const MethodScene: FC = ({ api }) => { // Invalid method if (api.tags[methodTag]) { // Found tag though - navigate(`/${specKey}/methods/${methodTag}`, history) + navigate(`/${specKey}/methods/${methodTag}`) } else { - navigate(`/${specKey}/methods`, history) + navigate(`/${specKey}/methods`) } } }, [api, history, methodName, methodTag, specKey]) diff --git a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx index 841376b0d..85905053a 100644 --- a/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx +++ b/packages/api-explorer/src/scenes/MethodTagScene/MethodTagScene.tsx @@ -29,7 +29,7 @@ import { useHistory, useParams } from 'react-router-dom' import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' import { ApixSection, DocTitle, DocMethodSummary, Link } from '../../components' -import { buildMethodPath, navigate } from '../../utils' +import { buildMethodPath, useNavigation } from '../../utils' import { getOperations } from './utils' interface MethodTagSceneProps { @@ -44,6 +44,7 @@ interface MethodTagSceneParams { export const MethodTagScene: FC = ({ api }) => { const { specKey, methodTag } = useParams() const history = useHistory() + const navigate = useNavigation() const [value, setValue] = useState('ALL') useEffect(() => { @@ -54,7 +55,7 @@ export const MethodTagScene: FC = ({ api }) => { const methods = api.tags[methodTag] useEffect(() => { if (!methods) { - navigate(`/${specKey}/methods`, history) + navigate(`/${specKey}/methods`) } }, [history, methods]) if (!methods) { diff --git a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx index 779f2ddd6..5491ed255 100644 --- a/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx +++ b/packages/api-explorer/src/scenes/TypeScene/TypeScene.tsx @@ -40,7 +40,7 @@ import { ExploreType, DocSchema, } from '../../components' -import { navigate } from '../../utils' +import { useNavigation } from '../../utils' interface DocTypeProps { api: ApiModel @@ -56,6 +56,7 @@ export const TypeScene: FC = ({ api }) => { const { specKey, typeTag, typeName } = useParams() const type = api.types[typeName] const history = useHistory() + const navigate = useNavigation() const typesUsed = typeRefs(api, type?.customTypes) const methodsUsedBy = methodRefs(api, type?.methodRefs) const typesUsedBy = typeRefs(api, type?.parentTypes) @@ -64,7 +65,7 @@ export const TypeScene: FC = ({ api }) => { const route = api.typeTags[typeTag] ? `/${specKey}/types/${typeTag}` : `/${specKey}/types` - navigate(route, history) + navigate(route) } }, [history, specKey, type]) diff --git a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx index faa916657..ca7e990ed 100644 --- a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx +++ b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx @@ -29,7 +29,7 @@ import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' import { useParams, useHistory } from 'react-router-dom' import { ApixSection, DocTitle, DocTypeSummary, Link } from '../../components' -import { buildTypePath, navigate } from '../../utils' +import { buildTypePath, useNavigation } from '../../utils' import { getMetaTypes } from './utils' interface TypeTagSceneProps { @@ -44,6 +44,7 @@ interface TypeTagSceneParams { export const TypeTagScene: FC = ({ api }) => { const { specKey, typeTag } = useParams() const history = useHistory() + const navigate = useNavigation() const [value, setValue] = useState('ALL') useEffect(() => { @@ -54,7 +55,7 @@ export const TypeTagScene: FC = ({ api }) => { const types = api.typeTags[typeTag] useEffect(() => { if (!types) { - navigate(`/${specKey}/types`, history) + navigate(`/${specKey}/types`) } }, [history, types]) diff --git a/packages/api-explorer/src/utils/index.ts b/packages/api-explorer/src/utils/index.ts index 2c6b01517..7b27ec040 100644 --- a/packages/api-explorer/src/utils/index.ts +++ b/packages/api-explorer/src/utils/index.ts @@ -29,4 +29,4 @@ export { getLoded } from './lodeUtils' export { useWindowSize } from './useWindowSize' export * from './apixAdaptor' export * from './adaptorUtils' -export { navigate } from './navUtils' +export { useNavigation } from './navUtils' diff --git a/packages/api-explorer/src/utils/navUtils.spec.ts b/packages/api-explorer/src/utils/navUtils.spec.ts index e6f854fa1..0f6b8d792 100644 --- a/packages/api-explorer/src/utils/navUtils.spec.ts +++ b/packages/api-explorer/src/utils/navUtils.spec.ts @@ -24,7 +24,7 @@ */ import { useHistory } from 'react-router-dom' -import { navigate } from './navUtils' +import { useNavigation } from './navUtils' const mockHistoryPush = jest.fn() jest.mock('react-router-dom', () => { @@ -40,11 +40,12 @@ jest.mock('react-router-dom', () => { describe('Navigate', () => { const history = useHistory() + const navigate = useNavigation() const curParams = new URLSearchParams(history.location.search) // 's=test' const route = `/3.1` test('preserves existing query params when given params are undefined', () => { - navigate(route, history, undefined) + navigate(route) expect(curParams.get('s')).toBe('test') expect(mockHistoryPush).lastCalledWith({ pathname: route, @@ -53,14 +54,14 @@ describe('Navigate', () => { }) test('clears existing params when given params are null', () => { - navigate(route, history, null) + navigate(route, null) expect(mockHistoryPush).lastCalledWith({ pathname: route, }) }) test('clears existing params when given params are an empty object', () => { - navigate(route, history, {}) + navigate(route, {}) expect(mockHistoryPush).lastCalledWith({ pathname: route, }) @@ -68,7 +69,7 @@ describe('Navigate', () => { test('sets query parameters when given a populated query params object', () => { const newParams = 's=embedsso' - navigate(route, history, { search: newParams }) + navigate(route, { search: newParams }) expect(mockHistoryPush).lastCalledWith({ pathname: route, search: newParams, diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/navUtils.ts index 77ea64bbe..48a7f3172 100644 --- a/packages/api-explorer/src/utils/navUtils.ts +++ b/packages/api-explorer/src/utils/navUtils.ts @@ -23,29 +23,49 @@ SOFTWARE. */ -import type { History } from 'history' +import { useHistory } from 'react-router-dom' /** * Navigates to given route with specified query params * * @param path Pathname to navigate to - * @param history Current browser session's history object * @param queryParams Hash of query param name/value pairs to include in the destination url */ -export const navigate = ( - path: string, - history: History, - queryParams?: { search?: string } | null -) => { + +export const useNavigation = () => { + const history = useHistory() const curParams = new URLSearchParams(history.location.search) - if (queryParams === undefined) { - // if params passed in is undefined, maintain existing parameters in the URL - history.push({ pathname: path, search: curParams.toString() }) - } else if (queryParams === null || Object.keys(queryParams).length === 0) { - // if params passed in is null or empty, remove all parameters from the URL - history.push({ pathname: path }) - } else { - // if we have new parameters passed in, push them to the URL - history.push({ pathname: path, search: queryParams.search }) + + const navigate = (path: string, queryParams?: { search?: string } | null) => { + if (queryParams === undefined) { + // if params passed in is undefined, maintain existing parameters in the URL + history.push({ pathname: path, search: curParams.toString() }) + } else if (queryParams === null || Object.keys(queryParams).length === 0) { + // if params passed in is null or empty, remove all parameters from the URL + history.push({ pathname: path }) + } else { + // if we have new parameters passed in, push them to the URL + history.push({ pathname: path, search: queryParams.search }) + } } + + return navigate } + +// export const navigate = ( +// path: string, +// history: History, +// queryParams?: { search?: string } | null +// ) => { +// const curParams = new URLSearchParams(history.location.search) +// if (queryParams === undefined) { +// // if params passed in is undefined, maintain existing parameters in the URL +// history.push({ pathname: path, search: curParams.toString() }) +// } else if (queryParams === null || Object.keys(queryParams).length === 0) { +// // if params passed in is null or empty, remove all parameters from the URL +// history.push({ pathname: path }) +// } else { +// // if we have new parameters passed in, push them to the URL +// history.push({ pathname: path, search: queryParams.search }) +// } +// } From 0ed839a6cb2d7b5780fd1bb7a5125b726442b83c Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Sat, 16 Jul 2022 00:43:48 +0000 Subject: [PATCH 20/30] Added changes per Bryn's comments, new buildNavigationPath --- .../src/components/SideNav/SideNav.tsx | 2 +- .../src/components/SideNav/SideNavMethods.tsx | 12 ++++++----- .../src/components/SideNav/SideNavTypes.tsx | 18 +++++++++-------- .../src/scenes/TypeTagScene/TypeTagScene.tsx | 5 ++--- packages/api-explorer/src/utils/navUtils.ts | 20 +------------------ packages/api-explorer/src/utils/path.ts | 15 ++++++++++++++ 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 2e19eb6a6..38e82c23b 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -69,7 +69,6 @@ interface SideNavProps { export const SideNav: FC = ({ headless = false, spec }) => { const location = useLocation() const navigate = useNavigation() - const searchParams = new URLSearchParams(location.search) const specKey = spec.key const tabNames = ['methods', 'types'] const pathParts = location.pathname.split('/') @@ -111,6 +110,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { } useEffect(() => { + const searchParams = new URLSearchParams(location.search) if (debouncedPattern && debouncedPattern !== searchParams.get('s')) { searchParams.set('s', debouncedPattern) navigate(location.pathname, { search: searchParams.toString() }) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index 1e2ccb9d1..a1e93c4d5 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -30,7 +30,7 @@ import { Accordion2, Heading } from '@looker/components' import type { MethodList } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { useLocation, useRouteMatch } from 'react-router-dom' -import { useNavigation, buildMethodPath, highlightHTML } from '../../utils' +import { useNavigation, highlightHTML, buildNavigationPath } from '../../utils' import { Link } from '../Link' import { selectSearchPattern } from '../../state' @@ -85,10 +85,12 @@ export const SideNavMethods = styled( {Object.values(methods).map((method) => (
    • {highlightHTML(searchPattern, method.summary)} diff --git a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx index e4ee6dd4d..f5e4bfd4e 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavTypes.tsx @@ -28,10 +28,10 @@ import React, { useEffect, useState } from 'react' import styled from 'styled-components' import { Accordion2, Heading } from '@looker/components' import type { TypeList } from '@looker/sdk-codegen' -import { useHistory, useRouteMatch } from 'react-router-dom' +import { useLocation, useRouteMatch } from 'react-router-dom' import { useSelector } from 'react-redux' import { Link } from '../Link' -import { buildTypePath, highlightHTML, useNavigation } from '../../utils' +import { buildNavigationPath, highlightHTML, useNavigation } from '../../utils' import { selectSearchPattern } from '../../state' interface TypesProps { @@ -44,9 +44,9 @@ interface TypesProps { export const SideNavTypes = styled( ({ className, types, tag, specKey, defaultOpen = false }: TypesProps) => { - const history = useHistory() + const location = useLocation() const navigate = useNavigation() - const searchParams = new URLSearchParams(history.location.search) + const searchParams = new URLSearchParams(location.search) const searchPattern = useSelector(selectSearchPattern) const match = useRouteMatch<{ typeTag: string }>( `/:specKey/types/:typeTag/:typeName?` @@ -85,10 +85,12 @@ export const SideNavTypes = styled( {Object.values(types).map((type) => (
    • {highlightHTML(searchPattern, type.name)} diff --git a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx index ca7e990ed..0e69b7779 100644 --- a/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx +++ b/packages/api-explorer/src/scenes/TypeTagScene/TypeTagScene.tsx @@ -27,7 +27,7 @@ import type { FC } from 'react' import React, { useEffect, useState } from 'react' import { Grid, ButtonToggle, ButtonItem } from '@looker/components' import type { ApiModel } from '@looker/sdk-codegen' -import { useParams, useHistory } from 'react-router-dom' +import { useParams } from 'react-router-dom' import { ApixSection, DocTitle, DocTypeSummary, Link } from '../../components' import { buildTypePath, useNavigation } from '../../utils' import { getMetaTypes } from './utils' @@ -43,7 +43,6 @@ interface TypeTagSceneParams { export const TypeTagScene: FC = ({ api }) => { const { specKey, typeTag } = useParams() - const history = useHistory() const navigate = useNavigation() const [value, setValue] = useState('ALL') @@ -57,7 +56,7 @@ export const TypeTagScene: FC = ({ api }) => { if (!types) { navigate(`/${specKey}/types`) } - }, [history, types]) + }, [navigate, types]) if (!types) { return <> diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/navUtils.ts index 48a7f3172..137c71d7f 100644 --- a/packages/api-explorer/src/utils/navUtils.ts +++ b/packages/api-explorer/src/utils/navUtils.ts @@ -34,11 +34,11 @@ import { useHistory } from 'react-router-dom' export const useNavigation = () => { const history = useHistory() - const curParams = new URLSearchParams(history.location.search) const navigate = (path: string, queryParams?: { search?: string } | null) => { if (queryParams === undefined) { // if params passed in is undefined, maintain existing parameters in the URL + const curParams = new URLSearchParams(history.location.search) history.push({ pathname: path, search: curParams.toString() }) } else if (queryParams === null || Object.keys(queryParams).length === 0) { // if params passed in is null or empty, remove all parameters from the URL @@ -51,21 +51,3 @@ export const useNavigation = () => { return navigate } - -// export const navigate = ( -// path: string, -// history: History, -// queryParams?: { search?: string } | null -// ) => { -// const curParams = new URLSearchParams(history.location.search) -// if (queryParams === undefined) { -// // if params passed in is undefined, maintain existing parameters in the URL -// history.push({ pathname: path, search: curParams.toString() }) -// } else if (queryParams === null || Object.keys(queryParams).length === 0) { -// // if params passed in is null or empty, remove all parameters from the URL -// history.push({ pathname: path }) -// } else { -// // if we have new parameters passed in, push them to the URL -// history.push({ pathname: path, search: queryParams.search }) -// } -// } diff --git a/packages/api-explorer/src/utils/path.ts b/packages/api-explorer/src/utils/path.ts index 5a38dd667..21efc5800 100644 --- a/packages/api-explorer/src/utils/path.ts +++ b/packages/api-explorer/src/utils/path.ts @@ -28,6 +28,21 @@ import type { ApiModel, IMethod, IType } from '@looker/sdk-codegen' import { firstMethodRef } from '@looker/sdk-codegen' import type { Location as HLocation } from 'history' +/** + * Builds a path matching the route used by scene with search parameters + * @param specKey A string to identify the spec in the URL + * @param tag Corresponding tag + * @param name A method or type name + * @param params Hash of query param name/value pairs to include in the destination url + * @returns a path including search parameters + */ +export const buildNavigationPath = ( + specKey: string, + tag: string, + name: string, + params: string +) => `/${specKey}/methods/${tag}/${name}?${params}` + /** * Builds a path matching the route used by MethodScene * @param specKey A string to identify the spec in the URL From addc668ab9f9760dbeef520c0f4478bf6ce39be0 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Sat, 16 Jul 2022 01:04:15 +0000 Subject: [PATCH 21/30] updated edit for buildNavigationPath --- .../src/components/SideNav/SideNavMethods.tsx | 1 + .../src/components/SideNav/SideNavTypes.tsx | 1 + packages/api-explorer/src/utils/path.spec.ts | 33 ++++++++++++++++++- packages/api-explorer/src/utils/path.ts | 3 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index a1e93c4d5..16217765e 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -87,6 +87,7 @@ export const SideNavMethods = styled( { + describe('buildNavigationPath', () => { + const testParam = 's=test' + test('it builds a method path with params', () => { + const path = buildNavigationPath( + '3.1', + 'methods', + 'Dashboard', + 'create_dashboard', + testParam + ) + expect(path).toEqual( + `/3.1/methods/Dashboard/create_dashboard?${testParam}` + ) + }) + test('it builds a type path with params', () => { + const path = buildNavigationPath( + '3.1', + 'types', + 'Dashboard', + 'create_dashboard', + testParam + ) + expect(path).toEqual(`/3.1/types/Dashboard/create_dashboard?${testParam}`) + }) + }) + describe('buildMethodPath', () => { test('it builds a method path', () => { const path = buildMethodPath('3.1', 'Dashboard', 'create_dashboard') diff --git a/packages/api-explorer/src/utils/path.ts b/packages/api-explorer/src/utils/path.ts index 21efc5800..0bda3697e 100644 --- a/packages/api-explorer/src/utils/path.ts +++ b/packages/api-explorer/src/utils/path.ts @@ -38,10 +38,11 @@ import type { Location as HLocation } from 'history' */ export const buildNavigationPath = ( specKey: string, + category: string, tag: string, name: string, params: string -) => `/${specKey}/methods/${tag}/${name}?${params}` +) => `/${specKey}/${category}/${tag}/${name}?${params}` /** * Builds a path matching the route used by MethodScene From 1f2857e33f852df341252b1cc764a596be5c3c06 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Tue, 19 Jul 2022 00:43:23 +0000 Subject: [PATCH 22/30] Changes per Joseph's feedback --- .../DocMarkdown/DocMarkdown.spec.tsx | 27 ++++--------- .../src/components/SideNav/SideNavMethods.tsx | 5 +-- .../src/components/SideNav/SideNavTypes.tsx | 5 +-- .../src/scenes/TypeTagScene/TypeTagScene.tsx | 2 +- .../utils/{navUtils.spec.ts => hooks.spec.ts} | 2 +- .../src/utils/{navUtils.ts => hooks.ts} | 2 +- packages/api-explorer/src/utils/index.ts | 2 +- packages/api-explorer/src/utils/path.spec.ts | 40 ++++++++----------- packages/api-explorer/src/utils/path.ts | 31 +++++--------- 9 files changed, 43 insertions(+), 73 deletions(-) rename packages/api-explorer/src/utils/{navUtils.spec.ts => hooks.spec.ts} (98%) rename packages/api-explorer/src/utils/{navUtils.ts => hooks.ts} (96%) diff --git a/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx b/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx index f312359c1..98145c97d 100644 --- a/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx +++ b/packages/api-explorer/src/components/DocMarkdown/DocMarkdown.spec.tsx @@ -26,16 +26,13 @@ import React from 'react' import { screen } from '@testing-library/react' -import { - createTestStore, - renderWithRouterAndReduxProvider, -} from '../../test-utils' +import { createTestStore, renderWithReduxProvider } from '../../test-utils' import { DocMarkdown } from './DocMarkdown' describe('DocMarkdown', () => { test('it renders markdown', () => { - renderWithRouterAndReduxProvider( + renderWithReduxProvider( { test('it remaps hashbang urls found in markdown input', () => { const input = 'A link to the [create_dashboard](#!/Dashboard/create_dashboard) endpoint' - renderWithRouterAndReduxProvider( - - ) + renderWithReduxProvider() expect(screen.getByText(/A link to the/)).toBeInTheDocument() expect(screen.getByText('create_dashboard')).toHaveAttribute( 'href', @@ -67,7 +62,7 @@ describe('DocMarkdown', () => { }) test('it leaves external urls untouched', () => { - renderWithRouterAndReduxProvider( + renderWithReduxProvider( { const store = createTestStore({ settings: { searchPattern: highlightPattern }, }) - renderWithRouterAndReduxProvider( + renderWithReduxProvider( , - undefined, store ) const mark = screen.getByText(highlightPattern) @@ -101,14 +95,13 @@ describe('DocMarkdown', () => { const store = createTestStore({ settings: { searchPattern: highlightPattern }, }) - renderWithRouterAndReduxProvider( + renderWithReduxProvider( , - undefined, store ) const link = screen.getByRole('link') @@ -122,9 +115,7 @@ describe('DocMarkdown', () => { test('it renders code blocks', () => { const code = '```\nAuthorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4\n```' - renderWithRouterAndReduxProvider( - - ) + renderWithReduxProvider() expect( screen.getByText( 'Authorization: token 4QDkCyCtZzYgj4C2p2cj3csJH7zqS5RzKs2kTnG4' @@ -134,9 +125,7 @@ describe('DocMarkdown', () => { test('it renders inline code', () => { const markdown = 'Some text with code: `const noop = () => null`' - renderWithRouterAndReduxProvider( - - ) + renderWithReduxProvider() expect(screen.getByText('const noop = () => null')).toBeInTheDocument() }) }) diff --git a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx index 16217765e..14f39cfba 100644 --- a/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNavMethods.tsx @@ -30,7 +30,7 @@ import { Accordion2, Heading } from '@looker/components' import type { MethodList } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' import { useLocation, useRouteMatch } from 'react-router-dom' -import { useNavigation, highlightHTML, buildNavigationPath } from '../../utils' +import { useNavigation, highlightHTML, buildMethodPath } from '../../utils' import { Link } from '../Link' import { selectSearchPattern } from '../../state' @@ -85,9 +85,8 @@ export const SideNavMethods = styled( {Object.values(methods).map((method) => (
    • (
    • = ({ api }) => { if (!types) { navigate(`/${specKey}/types`) } - }, [navigate, types]) + }, [types]) if (!types) { return <> diff --git a/packages/api-explorer/src/utils/navUtils.spec.ts b/packages/api-explorer/src/utils/hooks.spec.ts similarity index 98% rename from packages/api-explorer/src/utils/navUtils.spec.ts rename to packages/api-explorer/src/utils/hooks.spec.ts index 0f6b8d792..c962ef190 100644 --- a/packages/api-explorer/src/utils/navUtils.spec.ts +++ b/packages/api-explorer/src/utils/hooks.spec.ts @@ -24,7 +24,7 @@ */ import { useHistory } from 'react-router-dom' -import { useNavigation } from './navUtils' +import { useNavigation } from './hooks' const mockHistoryPush = jest.fn() jest.mock('react-router-dom', () => { diff --git a/packages/api-explorer/src/utils/navUtils.ts b/packages/api-explorer/src/utils/hooks.ts similarity index 96% rename from packages/api-explorer/src/utils/navUtils.ts rename to packages/api-explorer/src/utils/hooks.ts index 137c71d7f..b3b4d1095 100644 --- a/packages/api-explorer/src/utils/navUtils.ts +++ b/packages/api-explorer/src/utils/hooks.ts @@ -26,7 +26,7 @@ import { useHistory } from 'react-router-dom' /** - * Navigates to given route with specified query params + * Hook for navigating to given route with specified query params * * @param path Pathname to navigate to * @param queryParams Hash of query param name/value pairs to include in the destination url diff --git a/packages/api-explorer/src/utils/index.ts b/packages/api-explorer/src/utils/index.ts index 7b27ec040..4dd859051 100644 --- a/packages/api-explorer/src/utils/index.ts +++ b/packages/api-explorer/src/utils/index.ts @@ -29,4 +29,4 @@ export { getLoded } from './lodeUtils' export { useWindowSize } from './useWindowSize' export * from './apixAdaptor' export * from './adaptorUtils' -export { useNavigation } from './navUtils' +export { useNavigation } from './hooks' diff --git a/packages/api-explorer/src/utils/path.spec.ts b/packages/api-explorer/src/utils/path.spec.ts index 028716b5d..f36f0e1e1 100644 --- a/packages/api-explorer/src/utils/path.spec.ts +++ b/packages/api-explorer/src/utils/path.spec.ts @@ -25,20 +25,18 @@ */ import { api } from '../test-data' -import { - buildMethodPath, - buildPath, - buildTypePath, - buildNavigationPath, -} from './path' +import { buildMethodPath, buildPath, buildTypePath } from './path' describe('path utils', () => { - describe('buildNavigationPath', () => { - const testParam = 's=test' + const testParam = 's=test' + describe('buildMethodPath', () => { + test('it builds a method path', () => { + const path = buildMethodPath('3.1', 'Dashboard', 'create_dashboard') + expect(path).toEqual('/3.1/methods/Dashboard/create_dashboard') + }) test('it builds a method path with params', () => { - const path = buildNavigationPath( + const path = buildMethodPath( '3.1', - 'methods', 'Dashboard', 'create_dashboard', testParam @@ -47,10 +45,16 @@ describe('path utils', () => { `/3.1/methods/Dashboard/create_dashboard?${testParam}` ) }) + }) + + describe('buildTypePath', () => { + test('it builds a type path', () => { + const path = buildTypePath('3.1', 'Dashboard', 'WriteDashboard') + expect(path).toEqual('/3.1/types/Dashboard/WriteDashboard') + }) test('it builds a type path with params', () => { - const path = buildNavigationPath( + const path = buildTypePath( '3.1', - 'types', 'Dashboard', 'create_dashboard', testParam @@ -59,18 +63,6 @@ describe('path utils', () => { }) }) - describe('buildMethodPath', () => { - test('it builds a method path', () => { - const path = buildMethodPath('3.1', 'Dashboard', 'create_dashboard') - expect(path).toEqual('/3.1/methods/Dashboard/create_dashboard') - }) - }) - - describe('buildTypePath', () => { - const path = buildTypePath('3.1', 'Dashboard', 'WriteDashboard') - expect(path).toEqual('/3.1/types/Dashboard/WriteDashboard') - }) - describe('buildPath', () => { test('given a method it builds a method path', () => { const path = buildPath(api, api.methods.create_dashboard, '3.1') diff --git a/packages/api-explorer/src/utils/path.ts b/packages/api-explorer/src/utils/path.ts index 0bda3697e..4088867f6 100644 --- a/packages/api-explorer/src/utils/path.ts +++ b/packages/api-explorer/src/utils/path.ts @@ -28,44 +28,35 @@ import type { ApiModel, IMethod, IType } from '@looker/sdk-codegen' import { firstMethodRef } from '@looker/sdk-codegen' import type { Location as HLocation } from 'history' -/** - * Builds a path matching the route used by scene with search parameters - * @param specKey A string to identify the spec in the URL - * @param tag Corresponding tag - * @param name A method or type name - * @param params Hash of query param name/value pairs to include in the destination url - * @returns a path including search parameters - */ -export const buildNavigationPath = ( - specKey: string, - category: string, - tag: string, - name: string, - params: string -) => `/${specKey}/${category}/${tag}/${name}?${params}` - /** * Builds a path matching the route used by MethodScene * @param specKey A string to identify the spec in the URL * @param tag Corresponding method tag * @param methodName A method name + * @param params Hash of query param name/value pairs to include in the destination url * @returns a Method path */ export const buildMethodPath = ( specKey: string, tag: string, - methodName: string -) => `/${specKey}/methods/${tag}/${methodName}` + methodName: string, + params?: string +) => `/${specKey}/methods/${tag}/${methodName}${params ? `?${params}` : ''}` /** * Builds a path matching the route used by TypeScene * @param specKey A string to identify the spec in the URL * @param tag Corresponding type tag * @param typeName A type name + * @param params Hash of query param name/value pairs to include in the destination url * @returns a Type path */ -export const buildTypePath = (specKey: string, tag: string, typeName: string) => - `/${specKey}/types/${tag}/${typeName}` +export const buildTypePath = ( + specKey: string, + tag: string, + typeName: string, + params?: string +) => `/${specKey}/types/${tag}/${typeName}${params ? `?${params}` : ''}` export const diffPath = 'diff' export const oAuthPath = 'oauth' From 7c3124e612b5f8459a29152bbc8658c1794db152 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Tue, 19 Jul 2022 18:02:43 +0000 Subject: [PATCH 23/30] Added location.search as dependency in SideNav useEffect --- packages/api-explorer/src/components/SideNav/SideNav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 38e82c23b..0074e71bb 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -118,7 +118,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { searchParams.delete('s') navigate(location.pathname, { search: searchParams.toString() }) } - }, [debouncedPattern]) + }, [location.search, debouncedPattern]) useEffect(() => { let results From e69bb824f2640c1758537297aa1fe978befe2b58 Mon Sep 17 00:00:00 2001 From: Niral Patel Date: Sat, 16 Jul 2022 02:12:23 +0000 Subject: [PATCH 24/30] Copy button functional within inner search box; need to migrate to separate file --- .../CopyLinkButton/CopyLinkButton.spec.tsx | 25 +++++++ .../CopyLinkButton/CopyLinkButton.tsx | 58 +++++++++++++++++ .../src/components/CopyLinkButton/index.ts | 26 ++++++++ .../src/components/SideNav/SideNav.tsx | 65 +++++++++++++++---- 4 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx create mode 100644 packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx create mode 100644 packages/api-explorer/src/components/CopyLinkButton/index.ts diff --git a/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx new file mode 100644 index 000000000..03796f369 --- /dev/null +++ b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.spec.tsx @@ -0,0 +1,25 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ diff --git a/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx new file mode 100644 index 000000000..b001fadf7 --- /dev/null +++ b/packages/api-explorer/src/components/CopyLinkButton/CopyLinkButton.tsx @@ -0,0 +1,58 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ +import React, { useState } from 'react' +import { IconButton } from '@looker/components' +import { Link } from '@styled-icons/material-outlined/Link' +import styled from 'styled-components' + +export const CopyLink = styled('span')` + position: absolute; + top: 4px; + right: 36px; + display: ${(props) => props.display}; +` + +export const SearchWithLinkCopy = styled('div')` + position: relative; + width: 100%; +` + +export const CopyLinkTooltip = () => { + const [title, CopyLinkTooltip] = useState('Copy link to this page view') + return ( + { + CopyLinkTooltip('Copied to clipboard') + await navigator.clipboard.writeText(location.href) + }} + onMouseEnter={() => CopyLinkTooltip('Copy link to this section')} + size="small" + icon={} + label={title} + tooltipPlacement="bottom" + /> + ) +} diff --git a/packages/api-explorer/src/components/CopyLinkButton/index.ts b/packages/api-explorer/src/components/CopyLinkButton/index.ts new file mode 100644 index 000000000..98548aea9 --- /dev/null +++ b/packages/api-explorer/src/components/CopyLinkButton/index.ts @@ -0,0 +1,26 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ +export { CopyLinkButton } from './CopyLinkButton' diff --git a/packages/api-explorer/src/components/SideNav/SideNav.tsx b/packages/api-explorer/src/components/SideNav/SideNav.tsx index 0074e71bb..58d0a1fe4 100644 --- a/packages/api-explorer/src/components/SideNav/SideNav.tsx +++ b/packages/api-explorer/src/components/SideNav/SideNav.tsx @@ -34,7 +34,10 @@ import { TabPanels, useTabs, InputSearch, + IconButton, + Box2, } from '@looker/components' +import { Link } from '@styled-icons/material-outlined/Link' import type { SpecItem, ISearchResult, @@ -44,6 +47,7 @@ import type { } from '@looker/sdk-codegen' import { criteriaToSet, tagTypes } from '@looker/sdk-codegen' import { useSelector } from 'react-redux' +import styled from 'styled-components' import { useWindowSize, useNavigation } from '../../utils' import { HEADER_REM } from '../Header' import { selectSearchCriteria, selectSearchPattern } from '../../state' @@ -66,6 +70,23 @@ interface SideNavProps { spec: SpecItem } +const CopyLinkTooltip = () => { + const [title, CopyLinkTooltip] = useState('Copy link to this page view') + return ( + { + CopyLinkTooltip('Copied to clipboard') + await navigator.clipboard.writeText(location.href) + }} + onMouseEnter={() => CopyLinkTooltip('Copy link to this section')} + size="small" + icon={} + label={title} + tooltipPlacement="bottom" + /> + ) +} + export const SideNav: FC = ({ headless = false, spec }) => { const location = useLocation() const navigate = useNavigation() @@ -95,6 +116,7 @@ export const SideNav: FC = ({ headless = false, spec }) => { const searchCriteria = useSelector(selectSearchCriteria) const searchPattern = useSelector(selectSearchPattern) const [pattern, setSearchPattern] = useState(searchPattern) + const [copyLinkButtonDisplay, setCopyLinkButtonDisplay] = useState('none') const debouncedPattern = useDebounce(pattern, 250) const [sideNavState, setSideNavState] = useState(() => ({ tags: spec?.api?.tags || {}, @@ -158,20 +180,39 @@ export const SideNav: FC = ({ headless = false, spec }) => { const headlessOffset = headless ? 200 : 120 const menuH = size.height - 16 * HEADER_REM - headlessOffset + const CopyLink = styled('span')` + position: absolute; + top: 4px; + right: 36px; + display: ${copyLinkButtonDisplay}; + ` + + const SearchWithLinkCopy = styled('div')` + position: relative; + width: 100%; + ` + return (