import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | ||||
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; | ||||
import { getSuggestions } from '../../../api/components'; | import { getSuggestions } from '../../../api/components'; | ||||
import { getCodeUrl, getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { ComponentQualifier } from '../../../types/component'; | import { ComponentQualifier } from '../../../types/component'; | ||||
import RecentHistory from '../RecentHistory'; | import RecentHistory from '../RecentHistory'; | ||||
import './Search.css'; | import './Search.css'; | ||||
return next; | return next; | ||||
}, []); | }, []); | ||||
findFile = (key: string) => { | |||||
const findInResults = (results: ComponentResult[] | undefined) => | |||||
results && results.find(r => r.key === key); | |||||
const file = findInResults(this.state.results['FIL']); | |||||
if (file) { | |||||
return file; | |||||
} | |||||
const test = findInResults(this.state.results['UTS']); | |||||
if (test) { | |||||
return test; | |||||
} | |||||
return undefined; | |||||
}; | |||||
stopLoading = () => { | stopLoading = () => { | ||||
if (this.mounted) { | if (this.mounted) { | ||||
this.setState({ loading: false }); | this.setState({ loading: false }); | ||||
if (selected.startsWith('qualifier###')) { | if (selected.startsWith('qualifier###')) { | ||||
this.searchMore(selected.substr(12)); | this.searchMore(selected.substr(12)); | ||||
} else { | } else { | ||||
const file = this.findFile(selected); | |||||
if (file) { | |||||
this.props.router.push(getCodeUrl(file.project!, undefined, file.key)); | |||||
} else { | |||||
let qualifier = ComponentQualifier.Project; | |||||
if ((results[ComponentQualifier.Portfolio] ?? []).find(r => r.key === selected)) { | |||||
qualifier = ComponentQualifier.Portfolio; | |||||
} else if ((results[ComponentQualifier.SubPortfolio] ?? []).find(r => r.key === selected)) { | |||||
qualifier = ComponentQualifier.SubPortfolio; | |||||
} | |||||
let qualifier = ComponentQualifier.Project; | |||||
this.props.router.push(getComponentOverviewUrl(selected, qualifier)); | |||||
if ((results[ComponentQualifier.Portfolio] ?? []).find(r => r.key === selected)) { | |||||
qualifier = ComponentQualifier.Portfolio; | |||||
} else if ((results[ComponentQualifier.SubPortfolio] ?? []).find(r => r.key === selected)) { | |||||
qualifier = ComponentQualifier.SubPortfolio; | |||||
} | } | ||||
this.props.router.push(getComponentOverviewUrl(selected, qualifier)); | |||||
this.closeSearch(); | this.closeSearch(); | ||||
} | } | ||||
}; | }; |
import ClockIcon from 'sonar-ui-common/components/icons/ClockIcon'; | import ClockIcon from 'sonar-ui-common/components/icons/ClockIcon'; | ||||
import FavoriteIcon from 'sonar-ui-common/components/icons/FavoriteIcon'; | import FavoriteIcon from 'sonar-ui-common/components/icons/FavoriteIcon'; | ||||
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; | ||||
import { getCodeUrl, getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { ComponentQualifier } from '../../../../js/types/component'; | |||||
import { getComponentOverviewUrl } from '../../../helpers/urls'; | |||||
import { ComponentResult } from './utils'; | import { ComponentResult } from './utils'; | ||||
interface Props { | interface Props { | ||||
this.props.onSelect(this.props.component.key); | this.props.onSelect(this.props.component.key); | ||||
}; | }; | ||||
renderOrganization = (component: ComponentResult) => { | |||||
if (!this.props.appState.organizationsEnabled) { | |||||
return null; | |||||
} | |||||
if (!['VW', 'SVW', 'APP', 'TRK'].includes(component.qualifier) || !component.organization) { | |||||
return null; | |||||
} | |||||
const organization = this.props.organizations[component.organization]; | |||||
return organization ? ( | |||||
<div className="navbar-search-item-right text-muted-2">{organization.name}</div> | |||||
) : null; | |||||
}; | |||||
renderProject = (component: ComponentResult) => { | renderProject = (component: ComponentResult) => { | ||||
if (!['BRC', 'FIL', 'UTS'].includes(component.qualifier) || component.project == null) { | |||||
if ( | |||||
ComponentQualifier.SubProject !== (component.qualifier as ComponentQualifier) || | |||||
component.project == null | |||||
) { | |||||
return null; | return null; | ||||
} | } | ||||
render() { | render() { | ||||
const { component } = this.props; | const { component } = this.props; | ||||
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | |||||
const to = isFile | |||||
? getCodeUrl(component.project!, undefined, component.key) | |||||
: getComponentOverviewUrl(component.key, component.qualifier); | |||||
const to = getComponentOverviewUrl(component.key, component.qualifier); | |||||
return ( | return ( | ||||
<li | <li | ||||
<span className="navbar-search-item-match">{component.name}</span> | <span className="navbar-search-item-match">{component.name}</span> | ||||
)} | )} | ||||
{this.renderOrganization(component)} | |||||
{this.renderProject(component)} | {this.renderProject(component)} | ||||
</span> | </span> | ||||
</Link> | </Link> |
expect(wrapper).toMatchSnapshot(); | expect(wrapper).toMatchSnapshot(); | ||||
}); | }); | ||||
it('renders organizations', () => { | |||||
const component = { | |||||
isRecentlyBrowsed: true, | |||||
key: 'foo', | |||||
name: 'foo', | |||||
qualifier: 'TRK', | |||||
organization: 'bar' | |||||
}; | |||||
const wrapper = shallowRender({ appState: { organizationsEnabled: true }, component }); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
wrapper.setProps({ appState: { organizationsEnabled: false } }); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
}); | |||||
it('shows tooltip after delay', () => { | it('shows tooltip after delay', () => { | ||||
const wrapper = shallowRender(); | const wrapper = shallowRender(); | ||||
expect(wrapper.find('Tooltip').prop('visible')).toBe(false); | expect(wrapper.find('Tooltip').prop('visible')).toBe(false); |
</li> | </li> | ||||
`; | `; | ||||
exports[`renders organizations 1`] = ` | |||||
<li | |||||
key="foo" | |||||
> | |||||
<Tooltip | |||||
mouseEnterDelay={1} | |||||
overlay="foo" | |||||
placement="left" | |||||
visible={false} | |||||
> | |||||
<Link | |||||
data-key="foo" | |||||
onClick={[MockFunction]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/dashboard", | |||||
"query": Object { | |||||
"id": "foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<span | |||||
className="navbar-search-item-link" | |||||
onMouseEnter={[Function]} | |||||
> | |||||
<span | |||||
className="navbar-search-item-icons little-spacer-right" | |||||
> | |||||
<ClockIcon | |||||
size={12} | |||||
/> | |||||
<QualifierIcon | |||||
className="little-spacer-right" | |||||
qualifier="TRK" | |||||
/> | |||||
</span> | |||||
<span | |||||
className="navbar-search-item-match" | |||||
> | |||||
foo | |||||
</span> | |||||
<div | |||||
className="navbar-search-item-right text-muted-2" | |||||
> | |||||
bar | |||||
</div> | |||||
</span> | |||||
</Link> | |||||
</Tooltip> | |||||
</li> | |||||
`; | |||||
exports[`renders organizations 2`] = ` | |||||
<li | |||||
key="foo" | |||||
> | |||||
<Tooltip | |||||
mouseEnterDelay={1} | |||||
overlay="foo" | |||||
placement="left" | |||||
visible={false} | |||||
> | |||||
<Link | |||||
data-key="foo" | |||||
onClick={[MockFunction]} | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/dashboard", | |||||
"query": Object { | |||||
"id": "foo", | |||||
}, | |||||
} | |||||
} | |||||
> | |||||
<span | |||||
className="navbar-search-item-link" | |||||
onMouseEnter={[Function]} | |||||
> | |||||
<span | |||||
className="navbar-search-item-icons little-spacer-right" | |||||
> | |||||
<ClockIcon | |||||
size={12} | |||||
/> | |||||
<QualifierIcon | |||||
className="little-spacer-right" | |||||
qualifier="TRK" | |||||
/> | |||||
</span> | |||||
<span | |||||
className="navbar-search-item-match" | |||||
> | |||||
foo | |||||
</span> | |||||
</span> | |||||
</Link> | |||||
</Tooltip> | |||||
</li> | |||||
`; | |||||
exports[`renders projects 1`] = ` | exports[`renders projects 1`] = ` | ||||
<li | <li | ||||
key="qwe" | key="qwe" |
<ul | <ul | ||||
className="menu" | className="menu" | ||||
> | > | ||||
<li | |||||
className="menu-header" | |||||
key="header-FIL" | |||||
> | |||||
qualifiers.FIL | |||||
</li> | |||||
<span | |||||
key="zux" | |||||
> | |||||
zux | |||||
</span> | |||||
<li | |||||
className="divider" | |||||
key="divider-TRK" | |||||
/> | |||||
<li | <li | ||||
className="menu-header" | className="menu-header" | ||||
key="header-TRK" | key="header-TRK" | ||||
> | > | ||||
qux | qux | ||||
</span> | </span> | ||||
<li | |||||
className="divider" | |||||
key="divider-FIL" | |||||
/> | |||||
<li | |||||
className="menu-header" | |||||
key="header-FIL" | |||||
> | |||||
qualifiers.FIL | |||||
</li> | |||||
<span | |||||
key="zux" | |||||
> | |||||
zux | |||||
</span> | |||||
</ul> | </ul> | ||||
`; | `; | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { sortBy } from 'lodash'; | import { sortBy } from 'lodash'; | ||||
import { ComponentQualifier } from '../../../../js/types/component'; | |||||
const ORDER = ['DEV', 'VW', 'SVW', 'APP', 'TRK', 'BRC', 'FIL', 'UTS']; | |||||
const ORDER = [ | |||||
ComponentQualifier.Developper, | |||||
ComponentQualifier.Portfolio, | |||||
ComponentQualifier.SubPortfolio, | |||||
ComponentQualifier.Application, | |||||
ComponentQualifier.Project, | |||||
ComponentQualifier.SubProject | |||||
]; | |||||
export function sortQualifiers(qualifiers: string[]) { | export function sortQualifiers(qualifiers: string[]) { | ||||
return sortBy(qualifiers, qualifier => ORDER.indexOf(qualifier)); | |||||
return sortBy(qualifiers, qualifier => ORDER.indexOf(qualifier as ComponentQualifier)); | |||||
} | } | ||||
export interface ComponentResult { | export interface ComponentResult { |
#------------------------------------------------------------------------------ | #------------------------------------------------------------------------------ | ||||
search.shortcut_hint=Hint: Press {shortcut} from anywhere to open this search bar. | search.shortcut_hint=Hint: Press {shortcut} from anywhere to open this search bar. | ||||
search.show_more.hint=Press {key} to display | search.show_more.hint=Press {key} to display | ||||
search.placeholder=Search for projects and files... | |||||
search.placeholder=Search for projects... | |||||
search.search_for_projects=Search for projects... | search.search_for_projects=Search for projects... | ||||
search.search_for_members=Search for members... | search.search_for_members=Search for members... | ||||
search.search_for_users=Search for users... | search.search_for_users=Search for users... |