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

SWC-6910 - Entity Finder 'Selected' tab #1442

Merged
merged 5 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export function ChallengeDataDownload({
)

const onAddClick = useCallback(() => {
const entities = selectedEntities.toArray().map(entity => {
const entities = Array.from(selectedEntities.values()).map(reference => {
return {
fileEntityId: entity[0],
versionNumber: entity[1],
fileEntityId: reference.targetId,
versionNumber: reference.targetVersionNumber,
}
})
addBatchToDownloadList(entities)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
isContainerType,
isVersionableEntityType,
} from '../../utils/functions/EntityTypeUtils'
import { NO_VERSION_NUMBER } from '../EntityFinder/EntityFinder'
import { VersionSelectionType } from '../EntityFinder/VersionSelectionType'
import {
CellRendererProps,
Expand Down Expand Up @@ -130,14 +129,7 @@ export const ChallengeDataTable: React.FunctionComponent<DetailsViewProps> = ({
return selected.has(e.id)
})
.map(e => {
const selectedVersion = selected.get(e.id)
return {
targetId: e.id,
targetVersionNumber:
selectedVersion === NO_VERSION_NUMBER
? undefined
: selectedVersion,
}
return selected.get(e.id)!
}),
)
} else {
Expand Down Expand Up @@ -231,13 +223,12 @@ export const ChallengeDataTable: React.FunctionComponent<DetailsViewProps> = ({
// only include entities that should not be hidden
const entityType = getEntityTypeFromHeader(entity)

const currentSelectedVersion = selected.get(entity.id)
const currentSelectedVersion = selected.get(
entity.id,
)?.targetVersionNumber
let versionNumber: number | undefined = undefined
if ('versionNumber' in entity) {
if (
currentSelectedVersion != null &&
currentSelectedVersion !== NO_VERSION_NUMBER
) {
if (currentSelectedVersion != null) {
// if a version is selected, the row should show that version's data
versionNumber = currentSelectedVersion
} else if (versionSelection === VersionSelectionType.REQUIRED) {
Expand Down Expand Up @@ -406,10 +397,7 @@ export const ChallengeDataTable: React.FunctionComponent<DetailsViewProps> = ({

toggleSelection({
targetId: id,
targetVersionNumber:
currentSelectedVersion === NO_VERSION_NUMBER
? undefined
: currentSelectedVersion,
targetVersionNumber: currentSelectedVersion,
})
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const EntityFileBrowser: React.FunctionComponent<
[setBreadcrumbsProps],
)
const projectId = entityBundle?.path.path[1].id ?? undefined
const emptyMap = Map<string, number>()
const emptyMap = Map<string, Reference>()
const types: EntityType[] = [
EntityType.FOLDER,
EntityType.FILE,
Expand All @@ -66,7 +66,7 @@ export const EntityFileBrowser: React.FunctionComponent<
},
}
return (
<div className="EntityFinderReflexContainer">
<div className="EntityFileBrowser EntityFinderReflexContainer">
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added class name to apply cascaded styles

<SizeMe>
{({ size }) => (
<ReflexContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import EntityFinder from './EntityFinder'
import { FinderScope } from './tree/EntityTree'
import { EntityType } from '@sage-bionetworks/synapse-types'
import { VersionSelectionType } from './VersionSelectionType'
import { fn } from '@storybook/test'

const meta = {
title: 'Synapse/EntityFinder',
Expand All @@ -24,37 +25,30 @@ export const DualPane: Story = {
args: {
treeOnly: false,
initialScope: FinderScope.CURRENT_PROJECT,
projectId: 'syn23567475',
initialContainer: 'syn24183903',
projectId: 'syn5550376',
initialContainer: 'syn5550376',
selectMultiple: true,
visibleTypesInList: Object.values(EntityType),
versionSelection: VersionSelectionType.TRACKED,
onSelectedChange: selected => {
console.log('Selection changed:', selected)
},
onSelectedChange: fn(),
selectableTypes: Object.values(EntityType),
selectedCopy: count => {
return `${count} Item${count > 1 ? 's' : ''} Selected`
},
Comment on lines -36 to -38
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed now-unused selectedCopy prop.

},
}

export const SinglePane: Story = {
args: {
treeOnly: true,
initialScope: FinderScope.CURRENT_PROJECT,
projectId: 'syn23567475',
initialContainer: 'syn24183903',
projectId: 'syn5550376',
initialContainer: 'syn5550376',
selectMultiple: false,
visibleTypesInTree: [
EntityType.PROJECT,
EntityType.FOLDER,
EntityType.TABLE,
],
versionSelection: VersionSelectionType.DISALLOWED,
onSelectedChange: selected => {
console.log('Selection changed:', selected)
},
onSelectedChange: fn(),
selectableTypes: [EntityType.PROJECT, EntityType.FOLDER],
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from '../../../src/components/EntityFinder/details/EntityDetailsList'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved this file so it is now type-checked

import EntityFinder, {
EntityFinderProps,
NO_VERSION_NUMBER,
} from '../../../src/components/EntityFinder/EntityFinder'
import * as EntityTreeModule from '../../../src/components/EntityFinder/tree/EntityTree'
import { FinderScope } from '../../../src/components/EntityFinder/tree/EntityTree'
Expand All @@ -27,6 +26,7 @@ import {
import { Map } from 'immutable'
import * as useEntityBundleModule from '../../../src/synapse-queries/entity/useEntityBundle'
import SynapseClient from '../../../src/synapse-client'
import { getUseQuerySuccessMock } from '../../testutils/ReactQueryMockUtils'

jest.mock('react-reflex', () => {
return {
Expand All @@ -42,14 +42,14 @@ jest.mock('react-reflex', () => {

const mockUseGetEntityBundle = jest.spyOn(useEntityBundleModule, 'default')

const mockEntityTree = jest
jest
.spyOn(EntityTreeModule, 'EntityTree')
.mockImplementation(({ toggleSelection, setDetailsViewConfiguration }) => {
invokeToggleSelectionViaTree = reference => {
toggleSelection(reference)
toggleSelection!(reference)
}
invokeSetConfigViaTree = configuration => {
setDetailsViewConfiguration(configuration)
setDetailsViewConfiguration!(configuration)
}
return <div role="tree"></div>
})
Expand Down Expand Up @@ -85,30 +85,34 @@ const defaultProps: EntityFinderProps = {
visibleTypesInTree: [EntityType.PROJECT, EntityType.FOLDER],
selectableTypes: Object.values(EntityType),
treeOnly: false,
selectedCopy: 'Chosen Entities',
}

function renderComponent(propOverrides?: Partial<EntityFinderProps>) {
return render(
const user = userEvent.setup()
render(
<SynapseTestContext>
<EntityFinder {...defaultProps} {...propOverrides} />
</SynapseTestContext>,
)

const searchInput = screen.getByRole('textbox')

return { user, searchInput }
}

describe('EntityFinder tests', () => {
beforeEach(() => {
jest.clearAllMocks()

mockUseGetEntityBundle.mockReturnValue({
data: {
mockUseGetEntityBundle.mockReturnValue(
getUseQuerySuccessMock({
entity: {
id: 'syn123',
name: 'My file entity',
concreteType: 'org.sagebionetworks.repo.model.FileEntity',
},
},
})
}),
)
})

describe('single-select toggleSelection validation', () => {
Expand Down Expand Up @@ -328,7 +332,9 @@ describe('EntityFinder tests', () => {
await waitFor(() =>
expect(mockDetailsList).toHaveBeenLastCalledWith(
expect.objectContaining({
selected: Map([[reference.targetId, NO_VERSION_NUMBER]]),
selected: Map([
[reference.targetId, { targetId: reference.targetId }],
]),
}),
{},
),
Expand All @@ -355,61 +361,24 @@ describe('EntityFinder tests', () => {
})

describe('Search', () => {
it('Updates the search button text when only one type is selectable', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The search input field is no longer hidden behind a button press, so many of these search tests are no longer relevant.

// Folders are the only selectable type, so only folders will appear in search.
renderComponent({ selectableTypes: [EntityType.FOLDER] })

// Search button text should match
await screen.findByText('Search for Folders')
})

it('Updates the search button text when only table types are selectable', async () => {
// Datasets and entity views are table types
renderComponent({
selectableTypes: [EntityType.DATASET, EntityType.ENTITY_VIEW],
it('handles searching for terms', async () => {
const { user, searchInput } = renderComponent({
treeOnly: true,
selectableTypes: [EntityType.FILE],
})

// Search button text should match
await screen.findByText('Search for Tables')
})

it('clicking the search button opens the input field', async () => {
renderComponent({ treeOnly: true })

// Tree should be visible before we start search. No table should be visible
expect(() => screen.getByRole('tree')).not.toThrowError()
expect(() => screen.getByRole('table')).toThrowError()

// Don't show the search box before the button is clicked
expect(() => screen.getByRole('textbox')).toThrowError()

await userEvent.click(screen.getByText('Search all of Synapse'))
await waitFor(() => screen.getByRole('textbox'))

// The tree should be hidden when searching. The table of search results should be visible
expect(() => screen.getByRole('tree')).toThrowError()
expect(() => screen.getByRole('table')).not.toThrowError()

// Close the search
await userEvent.click(screen.getByText('Back to Browse'))

// Tree should come back, table should be gone
await waitFor(() => screen.getByRole('tree'))
expect(() => screen.getByRole('table')).toThrowError()

// Search input field should be gone too
expect(() => screen.getByRole('textbox')).toThrowError()
})

it('handles searching for terms', async () => {
renderComponent({ selectableTypes: [EntityType.FILE] })
expect(() => screen.getByRole('tree')).not.toThrow()
expect(() => screen.getByRole('table')).toThrow()

const query = 'my search terms '
const queryTerms = ['my', 'search', 'terms']
await userEvent.click(screen.getByText('Search for Files'))
await waitFor(() => screen.getByRole('textbox'))
await userEvent.type(screen.getByRole('textbox'), query)
await userEvent.type(screen.getByRole('textbox'), '{enter}')
await user.type(searchInput, query)
await user.type(searchInput, '{enter}')

// The tree should be hidden when searching. The table of search results should be visible
expect(() => screen.getByRole('tree')).toThrow()
expect(() => screen.getByRole('table')).not.toThrow()

await waitFor(() =>
expect(mockDetailsList).toBeCalledWith(
Expand Down Expand Up @@ -453,19 +422,51 @@ describe('EntityFinder tests', () => {
{},
),
)

// Clear the search results and verify we go back to the original view
await user.click(screen.getByRole('button', { name: 'Clear Search' }))

// Tree should be visible once again and table should be hidden
expect(() => screen.getByRole('tree')).not.toThrow()
expect(() => screen.getByRole('table')).toThrow()
})

it('handles searching for a synId', async () => {
renderComponent()
const { user, searchInput } = renderComponent()

const entityId = 'syn123'
const version = 2

const entityHeaderResult = { results: [{ id: entityId }] }
const entityHeaderResultWithVersion: PaginatedResults<
Partial<EntityHeader>
> = {
results: [{ id: entityId, versionNumber: version }],
const entityHeaderResult: PaginatedResults<EntityHeader> = {
results: [
{
id: entityId,
name: 'foo',
benefactorId: 123,
type: 'org.sagebionetworks.repo.model.FileEntity',
createdOn: '',
modifiedOn: '',
createdBy: '',
modifiedBy: '',
isLatestVersion: false,
},
],
}
const entityHeaderResultWithVersion: PaginatedResults<EntityHeader> = {
results: [
{
id: entityId,
versionNumber: version,
name: 'foo',
benefactorId: 123,
type: 'org.sagebionetworks.repo.model.FileEntity',
createdOn: '',
modifiedOn: '',
createdBy: '',
modifiedBy: '',
isLatestVersion: true,
},
],
}

when(mockGetEntityHeaders)
Expand All @@ -479,10 +480,8 @@ describe('EntityFinder tests', () => {
)
.mockResolvedValue(entityHeaderResultWithVersion)

await userEvent.click(screen.getByText('Search all of Synapse'))
await waitFor(() => screen.getByRole('textbox'))
await userEvent.type(screen.getByRole('textbox'), entityId)
await userEvent.type(screen.getByRole('textbox'), '{enter}')
await user.type(searchInput, entityId)
await user.type(searchInput, '{enter}')

await waitFor(() =>
expect(mockDetailsList).toBeCalledWith(
Expand All @@ -498,12 +497,9 @@ describe('EntityFinder tests', () => {
expect(mockGetEntityHeaders).toBeCalledTimes(1)

// Search with a version number
await userEvent.clear(screen.getByRole('textbox'))
await userEvent.type(
screen.getByRole('textbox'),
`${entityId}.${version}`,
)
await userEvent.type(screen.getByRole('textbox'), '{enter}')
await user.clear(searchInput)
await user.type(searchInput, `${entityId}.${version}`)
await user.type(searchInput, '{enter}')
await waitFor(() =>
expect(mockDetailsList).toBeCalledWith(
expect.objectContaining({
Expand Down
Loading
Loading