Skip to content

Commit

Permalink
Merge pull request #1142 from jay-hodgson/PORTALS-3052
Browse files Browse the repository at this point in the history
  • Loading branch information
jay-hodgson authored Aug 20, 2024
2 parents f82edca + 3e0662c commit 6ae87f8
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 18 deletions.
71 changes: 54 additions & 17 deletions packages/synapse-react-client/src/components/FullTextSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { Collapse, TextField } from '@mui/material'
import React, { ChangeEvent, useRef, useState } from 'react'
import { TextMatchesQueryFilter } from '@sage-bionetworks/synapse-types'
import {
ColumnSingleValueFilterOperator,
ColumnSingleValueQueryFilter,
TextMatchesQueryFilter,
} from '@sage-bionetworks/synapse-types'
import { useQueryContext } from './QueryContext'
import { useQueryVisualizationContext } from './QueryVisualizationWrapper'
import { HelpPopover } from './HelpPopover/HelpPopover'
import IconSvg from './IconSvg/IconSvg'
import { IconSvgButton } from './IconSvgButton'
import { SYNAPSE_ENTITY_ID_REGEX } from '../utils/functions/RegularExpressions'
import { useAtomValue } from 'jotai'
import { tableQueryDataAtom } from './QueryWrapper/QueryWrapper'
import { getFileColumnModelId } from './SynapseTable/SynapseTableUtils'

// See PLFM-7011
const MIN_SEARCH_QUERY_LENGTH = 3
Expand All @@ -20,6 +28,8 @@ export const FullTextSearch: React.FunctionComponent<FullTextSearchProps> = ({
helpUrl,
}: FullTextSearchProps) => {
const { executeQueryRequest } = useQueryContext()
const data = useAtomValue(tableQueryDataAtom)
const columnModels = data?.columnModels
const { showSearchBar } = useQueryVisualizationContext()
const [searchText, setSearchText] = useState('')
const searchInputRef = useRef<HTMLInputElement>(null)
Expand All @@ -36,23 +46,50 @@ export const FullTextSearch: React.FunctionComponent<FullTextSearchProps> = ({
} else {
executeQueryRequest(request => {
const { additionalFilters = [] } = request.query

const textMatchesQueryFilter: TextMatchesQueryFilter = {
concreteType:
'org.sagebionetworks.repo.model.table.TextMatchesQueryFilter',
searchExpression: searchText,
}
// PORTALS-2093: does this additional filter already exist?
const found = additionalFilters.find(
filter =>
filter.concreteType == textMatchesQueryFilter.concreteType &&
filter.searchExpression == textMatchesQueryFilter.searchExpression,
)
if (found) {
return request
const synIdColumnModelId = getFileColumnModelId(columnModels)
const isSynapseID = searchText.match(SYNAPSE_ENTITY_ID_REGEX)
if (isSynapseID && synIdColumnModelId) {
const idColumnModel = columnModels?.filter(
el => el.id === synIdColumnModelId,
)
const singleValueQueryFilter: ColumnSingleValueQueryFilter = {
concreteType:
'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',
columnName: idColumnModel![0].name,
operator: ColumnSingleValueFilterOperator.IN,
values: [searchText],
}
// Replace the active filter on the column, if one exists
const matchingFilter = additionalFilters.find(
filter =>
filter.concreteType == singleValueQueryFilter.concreteType &&
filter.columnName == singleValueQueryFilter.columnName,
) as ColumnSingleValueQueryFilter
if (matchingFilter) {
if (!matchingFilter.values.includes(searchText)) {
matchingFilter.values.push(searchText)
}
return request
}
additionalFilters.push(singleValueQueryFilter)
} else {
const textMatchesQueryFilter: TextMatchesQueryFilter = {
concreteType:
'org.sagebionetworks.repo.model.table.TextMatchesQueryFilter',
searchExpression: searchText,
}
// PORTALS-2093: does this additional filter already exist?
const found = additionalFilters.find(
filter =>
filter.concreteType == textMatchesQueryFilter.concreteType &&
filter.searchExpression ==
textMatchesQueryFilter.searchExpression,
)
if (found) {
return request
}
additionalFilters.push(textMatchesQueryFilter)
}
additionalFilters.push(textMatchesQueryFilter)

request.query.additionalFilters = additionalFilters
// reset the search text after adding this filter
setSearchText('')
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ import {
QueryContextType,
} from '../../src/components/QueryContext/QueryContext'
import { createWrapper } from '../../src/testutils/TestingLibraryUtils'
import {
ColumnModel,
ColumnSingleValueFilterOperator,
ColumnTypeEnum,
} from '@sage-bionetworks/synapse-types'
import { useSetAtom } from 'jotai'
import { tableQueryDataAtom } from '../../src/components/QueryWrapper/QueryWrapper'
import { mockQueryResultBundle } from '../../src/mocks/mockFileViewQuery'

let setQueryData: ReturnType<typeof useSetAtom> | undefined

function ContextReceiver(props: React.PropsWithChildren<any>) {
setQueryData = useSetAtom(tableQueryDataAtom)
return <>{props.children}</>
}

const renderComponent = (
queryContext: Partial<QueryContextType>,
Expand All @@ -21,7 +36,9 @@ const renderComponent = (
<QueryVisualizationContextProvider
queryVisualizationContext={queryVisualizationContext}
>
<FullTextSearch />
<ContextReceiver>
<FullTextSearch />
</ContextReceiver>
</QueryVisualizationContextProvider>
</QueryContextProvider>,
{
Expand Down Expand Up @@ -90,6 +107,35 @@ describe('FullTextSearch tests', () => {
)
})

it('adds the appropriate QueryFilter when searching for Synapse ID', async () => {
setQueryData!(mockQueryResultBundle)
renderComponent(queryContext, queryVisualizationContext)

const searchBox = screen.getByRole('textbox')

const searchQuery = 'syn123'
await userEvent.type(searchBox, searchQuery + '{enter}')

expect(mockExecuteQueryRequest).toHaveBeenCalled()
const queryTransformFn = mockExecuteQueryRequest.mock.lastCall[0]
expect(typeof queryTransformFn).toBe('function')
expect(queryTransformFn({ query: {} })).toEqual(
expect.objectContaining({
query: expect.objectContaining({
additionalFilters: [
{
concreteType:
'org.sagebionetworks.repo.model.table.ColumnSingleValueQueryFilter',
columnName: 'id',
operator: ColumnSingleValueFilterOperator.IN,
values: [searchQuery],
},
],
}),
}),
)
})

it('enforces a minimum character requirement', async () => {
renderComponent(queryContext, queryVisualizationContext)

Expand Down

0 comments on commit 6ae87f8

Please sign in to comment.