Skip to content
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
6 changes: 4 additions & 2 deletions src/components/search/Search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import SearchResults from './SearchResults';
import SearchFilters from './SearchFilters'
import ReleaseSearchInfo from './ReleaseSearchInfo'
import { isReleaseOnlySearch } from './releaseOnlySearch'
import { getOwnerScopeFilters } from './ownerScopeFilters'
import { OperationsContext } from '../app/LayoutContext';
import ReferenceFilters from '../repos/ReferenceFilters'
import { EXCLUDE_FILTER_KEY } from './ResultConstants'
Expand Down Expand Up @@ -361,9 +362,10 @@ const Search = props => {
const handleResourceChange = (event, newTab) => {
event.preventDefault()
event.stopPropagation()
setFilters({})
const ownerScope = getAppliedFacetFromQueryParam(getOwnerScopeFilters(location.search, newTab))
setFilters(ownerScope)

history.push(getCurrentLayoutURL(getQueryParams(input, page, pageSize, {}), newTab))
history.push(getCurrentLayoutURL(getQueryParams(input, page, pageSize, ownerScope), newTab))
}

const getURL = __resource => {
Expand Down
12 changes: 7 additions & 5 deletions src/components/search/SearchInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,17 @@ const SearchInput = props => {
return false
}

const initiateSearch = (event, global, isMatch, forcedType) => {
performSearch(event, input, global, isMatch, forcedType)
const initiateSearch = (event, global, isMatch, forcedType, ownerScope) => {
performSearch(event, input, global, isMatch, forcedType, ownerScope)
setOpen(false)
setTimeout(blurInput, 1000)
}

const performSearch = (event, value, global, isMatch, forcedType) => {
const performSearch = (event, value, global, isMatch, forcedType, ownerScope) => {
event.preventDefault()
event.stopPropagation()

props.onSearch ? props.onSearch(value) : moveToSearchPage(value, global, isMatch, forcedType)
props.onSearch ? props.onSearch(value) : moveToSearchPage(value, global, isMatch, forcedType, ownerScope)
}

const blurInput = () => {
Expand Down Expand Up @@ -133,7 +133,7 @@ const SearchInput = props => {
return URL
}

const moveToSearchPage = (value, global, isMatch, forcedType) => {
const moveToSearchPage = (value, global, isMatch, forcedType, ownerScope) => {
if(!props.nested || global) {
let _input = value || '';
const queryParams = new URLSearchParams(location.search)
Expand All @@ -143,6 +143,8 @@ const SearchInput = props => {
// A facet filter (Class/Map Type) from a prior Searchlight search is unrelated to
// this new query - drop it so it doesn't silently carry over.
queryParams.delete('filters')
if(ownerScope)
queryParams.set('filters', JSON.stringify(ownerScope))
// A new search term means a different result set -- start over from the first page.
queryParams.delete('page')
if(isMatch)
Expand Down
52 changes: 43 additions & 9 deletions src/components/search/SearchInputDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,21 @@ const SearchInputDrawer = ({open, onClose, input, initiateSearch, inputProps, is
const user = getCurrentUser()
const canMatchSemantic = Boolean(isNested && (contextRepo?.version || version) !== 'HEAD' && contextRepo?.match_algorithms?.includes('llm') && user?.username && (!resource || resource === 'concepts')) && hasAuthGroup(user, 'mapper-approved') && hasAuthGroup(user, 'core_user')

let lastIndex = isNested ? 2 : 1
const ownerScope = {owner, ownerType: ownerType === 'orgs' ? 'Organization' : 'User'}
const canSearchOwnerConcepts = Boolean(isNested && (ownerType === 'orgs' || ownerType === 'users') && owner && !isURLRegistry)
const ownerConceptsIndex = canSearchOwnerConcepts ? 2 : null
const globalIndex = isNested ? (canSearchOwnerConcepts ? 3 : 2) : 1
const matchIndex = globalIndex + 1
let lastIndex = globalIndex
if (canMatchSemantic)
lastIndex += 1
const inputPlaceholder = input || '...'
const onClickOption = (event, nested) => {
event.persist()
initiateSearch(event, !nested, event?.currentTarget?.id === 'match_repo' && canMatchSemantic)
if(event?.currentTarget?.id === 'search_owner_concepts')
initiateSearch(event, true, false, 'concepts', ownerScope)
else
initiateSearch(event, !nested, event?.currentTarget?.id === 'match_repo' && canMatchSemantic)
}
const onRemoveFilter = () => {
removeFilter()
Expand Down Expand Up @@ -229,7 +237,11 @@ const SearchInputDrawer = ({open, onClose, input, initiateSearch, inputProps, is
inputRef.current.blur()
if(focus === 1)
inputProps.handleKeyPress(event)
else if(focus === 3 && canMatchSemantic) {
else if(focus === ownerConceptsIndex) {
setFocus(1)
initiateSearch(event, true, false, 'concepts', ownerScope)
}
else if(focus === matchIndex && canMatchSemantic) {
initiateSearch(event, false, true)
}
else {
Expand Down Expand Up @@ -408,25 +420,47 @@ const SearchInputDrawer = ({open, onClose, input, initiateSearch, inputProps, is
onKeyDown={event => onItemKeyDown(event, 1)}
/>
}
{
canSearchOwnerConcepts &&
<Option
id='search_owner_concepts'
index={ownerConceptsIndex}
placeholder={
<span>
<Trans
i18nKey='search.search_owner_concepts'
values={{owner, query: inputPlaceholder}}
components={[<b key='owner' />, <b key='query' />]}
shouldUnescape
/>
</span>
}
text={t('search.concepts')}
icon
selected={focus === ownerConceptsIndex}
onClick={onClickOption}
onKeyDown={event => onItemKeyDown(event, ownerConceptsIndex)}
/>
}
<Option
id='search_global'
index={2}
index={globalIndex}
placeholder={<span>{t('search.search_all_site')}"<b>{inputPlaceholder}</b>"</span>}
selected={focus === (isNested ? 2 : 1)}
selected={focus === globalIndex}
icon
onClick={onClickOption}
onKeyDown={event => onItemKeyDown(event, isNested ? 2 :1)}
onKeyDown={event => onItemKeyDown(event, globalIndex)}
/>
{
isNested && repoType && repo && !isURLRegistry && canMatchSemantic &&
<Option
id='match_repo'
nested
index={3}
index={matchIndex}
placeholder={<span>{t('common.match')} "<b>{inputPlaceholder}</b>" {t('common.to_concepts_in')} <b>{repo}</b></span>}
selected={focus == 3}
selected={focus === matchIndex}
onClick={onClickOption}
onKeyDown={event => onItemKeyDown(event, 3)}
onKeyDown={event => onItemKeyDown(event, matchIndex)}
text={t('common.run_match')}
isMatchOp
/>
Expand Down
26 changes: 26 additions & 0 deletions src/components/search/__tests__/ownerScopeFilters.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import test from 'node:test'
import assert from 'node:assert/strict'

import { getOwnerScopeFilters } from '../ownerScopeFilters.js'

const search = filters => '?' + new URLSearchParams({filters: JSON.stringify(filters)}).toString()

test('owner and ownerType carry to resources that have an owner facet, other facets are dropped', () => {
const url = search({owner: 'CIEL', ownerType: 'Organization', conceptClass: 'Diagnosis', locale: ['en']})
const expected = {owner: 'CIEL', ownerType: 'Organization'}
assert.deepEqual(getOwnerScopeFilters(url, 'concepts'), expected)
assert.deepEqual(getOwnerScopeFilters(url, 'mappings'), expected)
assert.deepEqual(getOwnerScopeFilters(url, 'repos'), expected)
})

test('resources without an owner facet get no scope', () => {
const url = search({owner: 'CIEL', ownerType: 'Organization'})
assert.deepEqual(getOwnerScopeFilters(url, 'orgs'), {})
assert.deepEqual(getOwnerScopeFilters(url, 'users'), {})
})

test('no scope when the URL has none or the filters are malformed', () => {
assert.deepEqual(getOwnerScopeFilters('', 'concepts'), {})
assert.deepEqual(getOwnerScopeFilters('?filters=%7B', 'concepts'), {})
assert.deepEqual(getOwnerScopeFilters(search({conceptClass: 'Diagnosis'}), 'concepts'), {})
})
19 changes: 19 additions & 0 deletions src/components/search/ownerScopeFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const OWNER_SCOPE_FIELDS = ['owner', 'ownerType']
const OWNER_SCOPED_RESOURCES = ['concepts', 'mappings', 'repos']

export const getOwnerScopeFilters = (search, resource) => {
if(!OWNER_SCOPED_RESOURCES.includes(resource))
return {}
let filters = {}
try {
filters = JSON.parse(new URLSearchParams(search).get('filters') || '{}')
} catch(e) {
return {}
}
const scope = {}
OWNER_SCOPE_FIELDS.forEach(field => {
if(filters?.[field])
scope[field] = filters[field]
})
return scope
}
1 change: 1 addition & 0 deletions src/i18n/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,7 @@
"search_all_site": "Search all of OCL for ",
"search_this_repository": "Search this repository",
"search_owner_repos": "Search <0>{{owner}}</0>'s repositories for \"<1>{{query}}</1>\"",
"search_owner_concepts": "Search concepts in <0>{{owner}}</0> for \"<1>{{query}}</1>\"",
"search_owner_map_projects": "Search <0>{{owner}}</0>'s mapping projects for \"<1>{{query}}</1>\"",
"search_repositories": "Search repositories",
"search_mapping_projects": "Search mapping projects",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/es/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@
"search_all_site": "Buscar en todo OCL para ",
"search_this_repository": "Buscar en este repositorio",
"search_owner_repos": "Buscar \"<1>{{query}}</1>\" en los repositorios de <0>{{owner}}</0>",
"search_owner_concepts": "Buscar \"<1>{{query}}</1>\" en los conceptos de <0>{{owner}}</0>",
"search_owner_map_projects": "Buscar \"<1>{{query}}</1>\" en los proyectos de mapeo de <0>{{owner}}</0>",
"search_repositories": "Buscar repositorios",
"search_mapping_projects": "Buscar proyectos de mapeo",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/pt-BR/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@
"search_all_site": "Buscar em todo o OCL por ",
"search_this_repository": "Buscar neste repositório",
"search_owner_repos": "Buscar \"<1>{{query}}</1>\" nos repositórios de <0>{{owner}}</0>",
"search_owner_concepts": "Buscar \"<1>{{query}}</1>\" nos conceitos de <0>{{owner}}</0>",
"search_owner_map_projects": "Buscar \"<1>{{query}}</1>\" nos projetos de mapeamento de <0>{{owner}}</0>",
"search_repositories": "Buscar repositórios",
"search_mapping_projects": "Buscar projetos de mapeamento",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/zh/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@
"search_all_site": "搜索全部符合搜索条件的 OCL 内容: ",
"search_this_repository": "搜索该存储库",
"search_owner_repos": "在 <0>{{owner}}</0> 的存储库中搜索“<1>{{query}}</1>”",
"search_owner_concepts": "在 <0>{{owner}}</0> 的概念中搜索“<1>{{query}}</1>”",
"search_owner_map_projects": "在 <0>{{owner}}</0> 的映射项目中搜索“<1>{{query}}</1>”",
"search_repositories": "搜索存储库",
"search_mapping_projects": "搜索映射项目",
Expand Down
Loading