Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ability to share a preconfigured diff scene #1158

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7fa7881
Clean branch off main with changes; WIP validating param
patnir41 Aug 9, 2022
1fe3e4a
Added utils in path for verifying filter
patnir41 Aug 9, 2022
e3b7e3d
Merge with main, sdk sharing with filter sharing
patnir41 Aug 9, 2022
853531a
Refactoring and unit testing implemented
patnir41 Aug 9, 2022
2d28c7f
Fixed bug on state of TypeTagScene
patnir41 Aug 9, 2022
7c04e61
WIP creating custom reconciliation and navigation hooks
patnir41 Aug 10, 2022
863bf47
Fix to not using isSynced
patnir41 Aug 10, 2022
6e3f9ea
Refactor using isSync variable determining render
patnir41 Aug 11, 2022
6339b11
Custom sync hooks, new navigate functions in hook for params
patnir41 Aug 11, 2022
0b09d4f
Code cleanup
patnir41 Aug 11, 2022
2d54f37
Code clean up
patnir41 Aug 11, 2022
caa189c
Fixed invalid verb param on tab switching, WIP hook testing
patnir41 Aug 12, 2022
ae47fa0
WIP globalStoreSync tests
patnir41 Aug 15, 2022
70ae6e2
globalSync and tagSync testing included, refactored files
patnir41 Aug 15, 2022
376e21b
Clean up per Jax comments
patnir41 Aug 15, 2022
e1e4c0a
Fix navigation hook function naming
patnir41 Aug 15, 2022
d8bfd8a
Merge branch 'patnir41/filter-sharing-custom-hooks' into patnir41/pre…
patnir41 Aug 15, 2022
93b959a
Diff scene options in URL, testing WIP
patnir41 Aug 16, 2022
6d46d80
Added testing & functional, WIP DiffScene.spec
patnir41 Aug 16, 2022
4048143
WIP DiffScene testing
patnir41 Aug 17, 2022
ffda1ca
Merging changes from main with tag filter PR
patnir41 Aug 17, 2022
071e0b5
WIP: finalizing tests
patnir41 Aug 18, 2022
358c56c
Merge branch 'main' into patnir41/preconfig-difs-8-15
patnir41 Aug 18, 2022
91917f5
Changes to util files to fix imports
patnir41 Aug 18, 2022
a6a47ca
Merge branch 'main' into patnir41/preconfig-difs-8-15
patnir41 Aug 19, 2022
d92df04
WIP DiffScene.spec testing still, functionality bug fix
patnir41 Aug 19, 2022
377dac8
DiffScene testing bug, no default state set immediately
patnir41 Aug 24, 2022
ea02ec2
WIP unable to init store with default settings in DiffScene spec
patnir41 Aug 30, 2022
73ca49c
Merge branch 'main' into patnir41/preconfig-difs-8-15
patnir41 Aug 31, 2022
c0297ed
Setting up diffscene spec for other tests
patnir41 Aug 31, 2022
ef95f97
Removing testReduxUtils, making spec file without this test
patnir41 Sep 1, 2022
90840d4
Moving forward with finalizing document ignoring spec bug
patnir41 Sep 2, 2022
f883a97
Merge branch 'main' into patnir41/preconfig-difs-8-15
patnir41 Sep 2, 2022
268ecd3
Refactoring file and tests
patnir41 Sep 2, 2022
7e8e558
Wrap up DiffScene spec, WIP e2e
patnir41 Sep 6, 2022
a8965ea
Added 1 e2e test for DiffScene
patnir41 Sep 6, 2022
c34fdee
Refactoring diffUtils function
patnir41 Sep 7, 2022
09a0666
Merge branch 'main' into patnir41/preconfig-difs-8-15
jkaster Sep 13, 2022
c54f9b3
Merge branch 'main' into patnir41/preconfig-difs-8-15
jkaster Sep 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/api-explorer/src/components/SideNav/SideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ interface SideNavProps {

export const SideNav: FC<SideNavProps> = ({ headless = false, spec }) => {
const location = useLocation()
const { navigate, navigateWithGlobalParams } = useNavigation()
const { navigate } = useNavigation()
const specKey = spec.key
const tabNames = ['methods', 'types']
const pathParts = location.pathname.split('/')
Expand All @@ -84,12 +84,12 @@ export const SideNav: FC<SideNavProps> = ({ headless = false, spec }) => {
if (parts[1] === 'diff') {
if (parts[3] !== tabNames[index]) {
parts[3] = tabNames[index]
navigateWithGlobalParams(parts.join('/'))
navigate(parts.join('/'), { t: null })
}
} else {
if (parts[2] !== tabNames[index]) {
parts[2] = tabNames[index]
navigateWithGlobalParams(parts.join('/'))
navigate(parts.join('/'), { t: null })
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export const SideNavMethods = styled(
const _isOpen = !isOpen
setIsOpen(_isOpen)
if (_isOpen) {
navigate(`/${specKey}/methods/${tag}`)
navigate(`/${specKey}/methods/${tag}`, { opts: null })
Copy link
Contributor Author

@patnir41 patnir41 Aug 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scenario to consider here (and with the same change in SideNavTypes): we are in a diff scene with selected options, and opts param in the url. When we open the sideNav and open a method/tag scene, this opts parameter should no longer persist. The reason we don't use navigateWithGlobalParams to remove this parameter is because if we started at a tag scene with a filter selected and want to select another tag, this filter should still persist when we open the new tag scene.

So, technically, the purpose of this line in plain english is more like "navigate to the tag scene and ONLY keep the tag filter variable if we have one in the URL"

This makes me reconsider what we initially had proposed where we have a specific navigateTo methods. I think in the long term there will be 2 cases:

  1. We navigate to a scene, remove all scene-specific params first, then push global params (currently the case with navigateWithGlobalParams, etc)
  2. We navigate to a scene, keeping ONLY specific parameters, and removing anything else in the URL.

To better account for case 2, I think having custom navigate methods makes the most sense. Here we'd explicitly have a navigateToTagScene instead of having to explicitly nullify any extra parameters that could persist when navigating to a tag scene from the SideNav.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not navigateWithGlobalParams?

} else {
navigate(`/${specKey}/methods`)
navigate(`/${specKey}/methods`, { opts: null })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export const SideNavTypes = styled(
const _isOpen = !isOpen
setIsOpen(_isOpen)
if (_isOpen) {
navigate(`/${specKey}/types/${tag}`)
navigate(`/${specKey}/types/${tag}`, { opts: null })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

} else {
navigate(`/${specKey}/types`)
navigate(`/${specKey}/types`, { opts: null })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}
}

Expand Down
147 changes: 147 additions & 0 deletions packages/api-explorer/src/scenes/DiffScene/DiffScene.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*

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 from 'react'
import { screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

import type { SpecItem } from '@looker/sdk-codegen'
import { useHistory } from 'react-router-dom'
import * as routerLocation from 'react-router-dom'
import type { Location } from 'history'
import * as reactRedux from 'react-redux'
import { getLoadedSpecs } from '../../test-data'
import {
createTestStore,
renderWithRouterAndReduxProvider,
} from '../../test-utils'
import { getApixAdaptor } from '../../utils'
import { DiffScene } from './DiffScene'

jest.mock('react-router', () => {
const ReactRouter = jest.requireActual('react-router')
return {
...ReactRouter,
useHistory: jest.fn().mockReturnValue({ push: jest.fn(), location }),
useLocation: jest.fn().mockReturnValue({ pathname: '/', search: '' }),
}
})

const specs = getLoadedSpecs()
class MockApixAdaptor {
async fetchSpec(spec: SpecItem) {
return new Promise(() => specs[spec.key])
}
}

const mockApixAdaptor = new MockApixAdaptor()
jest.mock('../../utils/apixAdaptor', () => {
const apixAdaptor = jest.requireActual('../../utils/apixAdaptor')
return {
...apixAdaptor,
getApixAdaptor: jest.fn(),
}
})

describe('DiffScene', () => {
const mockDispatch = jest.fn()

afterEach(() => {
jest.clearAllMocks()
})
;(getApixAdaptor as jest.Mock).mockReturnValue(mockApixAdaptor)
Element.prototype.scrollTo = jest.fn()
Element.prototype.scrollIntoView = jest.fn()

const toggleNavigation = () => false
test('toggling comparison option pushes param to url', async () => {
const { push } = useHistory()
const store = createTestStore({
specs: { specs, currentSpecKey: '3.1' },
settings: { initialized: true },
})
renderWithRouterAndReduxProvider(
<DiffScene specs={specs} toggleNavigation={toggleNavigation} />,
['/diff/3.1'],
store
)
userEvent.click(screen.getByPlaceholderText('Comparison options'))
userEvent.click(
screen.getByRole('option', {
name: 'Missing',
})
)
await waitFor(() => {
expect(push).toHaveBeenCalledWith({
pathname: '/',
search: 'opts=missing',
})
})
// TODO: test URL change leads to store dispatch? - change mock history push implementation to change our location
// TODO: test that toggling another will push both options to store/url
})

test.todo(
'rendering scene with opts param in url sets selected options in selector',
async () => {
jest.spyOn(routerLocation, 'useLocation').mockReturnValue({
pathname: `/`,
search: `opts=missing%2Ctype%2Cresponse`,
} as unknown as Location)
const store = createTestStore({
specs: { specs, currentSpecKey: '3.1' },
settings: { initialized: true, diffOptions: [] },
})
jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(mockDispatch)
renderWithRouterAndReduxProvider(
<DiffScene specs={specs} toggleNavigation={toggleNavigation} />,
['/diff/3.1'],
store
)
expect(mockDispatch).toHaveBeenLastCalledWith({
payload: { diffOptions: ['missing', 'type', 'response'] },
type: 'settings/setDiffOptionsAction',
})
expect(
screen.getByRole('option', {
name: 'Missing',
})
).toBeInTheDocument()
expect(
screen.getByRole('option', {
name: 'Type',
})
).toBeInTheDocument()
expect(
screen.getByRole('option', {
name: 'Response',
})
).toBeInTheDocument()
}
)

test.todo('unselecting comparison option will remove it from url opts param')
test.todo('selecting clear option will remove all params from url opts param')
})
30 changes: 25 additions & 5 deletions packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ import { SyncAlt } from '@styled-icons/material/SyncAlt'
import { useSelector } from 'react-redux'

import { ApixSection } from '../../components'
import { selectCurrentSpec } from '../../state'
import {
selectCurrentSpec,
selectDiffOptions,
useSettingActions,
} from '../../state'
import { diffPath, getApixAdaptor, useNavigation } from '../../utils'
import { diffSpecs, standardDiffToggles } from './diffUtils'
import { useDiffStoreSync } from '../utils/hooks/diffStoreSync'
import { diffSpecs, getDiffOptionsFromUrl } from './diffUtils'
import { DocDiff } from './DocDiff'

const diffToggles = [
Expand Down Expand Up @@ -85,6 +90,8 @@ const validateParam = (specs: SpecList, specKey = '') => {
export const DiffScene: FC<DiffSceneProps> = ({ specs, toggleNavigation }) => {
const adaptor = getApixAdaptor()
const { navigate } = useNavigation()
const selectedDiffOptions = useSelector(selectDiffOptions)
const { setDiffOptionsAction } = useSettingActions()
const spec = useSelector(selectCurrentSpec)
const currentSpecKey = spec.key
const match = useRouteMatch<{ l: string; r: string }>(`/${diffPath}/:l?/:r?`)
Expand All @@ -102,7 +109,8 @@ export const DiffScene: FC<DiffSceneProps> = ({ specs, toggleNavigation }) => {
const [rightApi, setRightApi] = useState<ApiModel>(() =>
rightKey ? specs[rightKey].api! : specs[leftKey].api!
)
const [toggles, setToggles] = useState<string[]>(standardDiffToggles)
const [toggles, setToggles] = useState<string[]>(selectedDiffOptions)
useDiffStoreSync()

useEffect(() => {
if (r !== rightKey) {
Expand Down Expand Up @@ -151,9 +159,21 @@ export const DiffScene: FC<DiffSceneProps> = ({ specs, toggleNavigation }) => {

const handleTogglesChange = (values?: string[]) => {
const newToggles = values || []
setToggles(newToggles)
navigate(location.pathname, { opts: newToggles.join(',') })
}

useEffect(() => {
const searchParams = new URLSearchParams(location.search)
const diffOptionsParam = getDiffOptionsFromUrl(searchParams.get('opts'))
setDiffOptionsAction({
diffOptions: diffOptionsParam || [],
})
}, [location.search])

useEffect(() => {
setToggles(selectedDiffOptions)
}, [selectedDiffOptions])

return (
<ApixSection>
<Box>
Expand Down Expand Up @@ -197,7 +217,7 @@ export const DiffScene: FC<DiffSceneProps> = ({ specs, toggleNavigation }) => {
id="options"
name="toggles"
placeholder="Comparison options"
defaultValues={toggles}
values={toggles}
onChange={handleTogglesChange}
options={diffToggles}
/>
Expand Down
18 changes: 17 additions & 1 deletion packages/api-explorer/src/scenes/DiffScene/diffUtils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import type { DiffRow } from '@looker/sdk-codegen'
import { startCount } from '@looker/sdk-codegen'
import { api, api40 } from '../../test-data'
import { diffToSpec } from './diffUtils'
import { diffToSpec, getDiffOptionsFromUrl } from './diffUtils'

describe('diffUtils', () => {
test('builds a psuedo spec from diff', () => {
Expand Down Expand Up @@ -63,4 +63,20 @@ describe('diffUtils', () => {
expect(Object.keys(spec.tags)).toEqual(['Query', 'Dashboard'])
expect(Object.keys(spec.types)).toEqual([])
})

describe('getDiffOptionsFromUrl', () => {
test('returns null if provided null input or given invalid diffscene options', () => {
expect(getDiffOptionsFromUrl(null)).toBeNull()
const testOptionsParam = 'INVALID,INVALID1,INVALID2'
expect(getDiffOptionsFromUrl(testOptionsParam)).toBeNull()
})

test('omits invalid diffScene options from input', () => {
const testOptionsParam = 'INVALID,missing,type'
expect(getDiffOptionsFromUrl(testOptionsParam)).toEqual([
'missing',
'type',
])
})
})
})
16 changes: 16 additions & 0 deletions packages/api-explorer/src/scenes/DiffScene/diffUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,19 @@ export const diffToSpec = (
result.types = {}
return result
}

/**
* Gets all valid diff options from the url opts parameter
* @param opts url diff options parameter value
*/
export const getDiffOptionsFromUrl = (opts: string | null) => {
// expect input to be a comma-delimited list as a string
if (!opts) return null
const diffOptions = []
for (const option of opts.split(',')) {
if (allDiffToggles.includes(option.toLowerCase())) {
diffOptions.push(option.toLowerCase())
}
}
return diffOptions.length ? diffOptions : null
patnir41 marked this conversation as resolved.
Show resolved Hide resolved
}
Loading