return getJSON(url, data); | return getJSON(url, data); | ||||
} | } | ||||
export function getParents({ id, key }: { id: string, key: string }) { | |||||
export function getComponentShow(component: string) { | |||||
const url = '/api/components/show'; | const url = '/api/components/show'; | ||||
const data = id ? { id } : { key }; | |||||
return getJSON(url, data).then(r => r.ancestors); | |||||
return getJSON(url, { component }); | |||||
} | |||||
export function getParents(component: string) { | |||||
return getComponentShow(component).then(r => r.ancestors); | |||||
} | } | ||||
export function getBreadcrumbs(component: string) { | export function getBreadcrumbs(component: string) { | ||||
const url = '/api/components/show'; | |||||
return getJSON(url, { component }).then(r => { | |||||
return getComponentShow(component).then(r => { | |||||
const reversedAncestors = [...r.ancestors].reverse(); | const reversedAncestors = [...r.ancestors].reverse(); | ||||
return [...reversedAncestors, r.component]; | return [...reversedAncestors, r.component]; | ||||
}); | }); | ||||
} | } | ||||
export function getComponentTags(component: string) { | |||||
return getComponentShow(component).then(r => r.component.tags || []); | |||||
} | |||||
export function getMyProjects(data?: Object) { | export function getMyProjects(data?: Object) { | ||||
const url = '/api/projects/search_my_projects'; | const url = '/api/projects/search_my_projects'; | ||||
return getJSON(url, data); | return getJSON(url, data); |
component: { | component: { | ||||
id: string, | id: string, | ||||
key: string, | key: string, | ||||
qualifier: string | |||||
qualifier: string, | |||||
tags: Array<string> | |||||
}, | }, | ||||
router: Object | router: Object | ||||
}; | }; |
import MetaQualityProfiles from './MetaQualityProfiles'; | import MetaQualityProfiles from './MetaQualityProfiles'; | ||||
import AnalysesList from '../events/AnalysesList'; | import AnalysesList from '../events/AnalysesList'; | ||||
import MetaSize from './MetaSize'; | import MetaSize from './MetaSize'; | ||||
import TagsList from '../../../components/ui/TagsList'; | |||||
import { areThereCustomOrganizations } from '../../../store/rootReducer'; | import { areThereCustomOrganizations } from '../../../store/rootReducer'; | ||||
import { translate } from '../../../helpers/l10n'; | |||||
const Meta = ({ component, measures, areThereCustomOrganizations }) => { | const Meta = ({ component, measures, areThereCustomOrganizations }) => { | ||||
const { qualifier, description, qualityProfiles, qualityGate } = component; | const { qualifier, description, qualityProfiles, qualityGate } = component; | ||||
const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles; | const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles; | ||||
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate; | const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate; | ||||
const shouldShowOrganizationKey = component.organization != null && areThereCustomOrganizations; | const shouldShowOrganizationKey = component.organization != null && areThereCustomOrganizations; | ||||
const configuration = component.configuration || {}; | |||||
return ( | return ( | ||||
<div className="overview-meta"> | <div className="overview-meta"> | ||||
{hasDescription && | {hasDescription && | ||||
<MetaSize component={component} measures={measures} /> | <MetaSize component={component} measures={measures} /> | ||||
<div className="overview-meta-card"> | |||||
<TagsList | |||||
tags={component.tags.length ? component.tags : [translate('no_tags')]} | |||||
allowUpdate={configuration.showSettings} | |||||
allowMultiLine={true} | |||||
/> | |||||
</div> | |||||
{shouldShowQualityGate && <MetaQualityGate gate={qualityGate} />} | {shouldShowQualityGate && <MetaQualityGate gate={qualityGate} />} | ||||
{shouldShowQualityProfiles && <MetaQualityProfiles profiles={qualityProfiles} />} | {shouldShowQualityProfiles && <MetaQualityProfiles profiles={qualityProfiles} />} |
import ProjectCardMeasures from './ProjectCardMeasures'; | import ProjectCardMeasures from './ProjectCardMeasures'; | ||||
import FavoriteContainer from '../../../components/controls/FavoriteContainer'; | import FavoriteContainer from '../../../components/controls/FavoriteContainer'; | ||||
import Organization from '../../../components/shared/Organization'; | import Organization from '../../../components/shared/Organization'; | ||||
import TagsList from '../../../components/ui/TagsList'; | |||||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | import { translate, translateWithParameters } from '../../../helpers/l10n'; | ||||
export default class ProjectCard extends React.PureComponent { | export default class ProjectCard extends React.PureComponent { | ||||
project?: { | project?: { | ||||
analysisDate?: string, | analysisDate?: string, | ||||
key: string, | key: string, | ||||
name: string | |||||
name: string, | |||||
tags: Array<string>, | |||||
isFavorite?: boolean, | |||||
organization?: string | |||||
} | } | ||||
}; | }; | ||||
{project.name} | {project.name} | ||||
</Link> | </Link> | ||||
</h2> | </h2> | ||||
{project.tags.length > 0 && <TagsList tags={project.tags} />} | |||||
</div> | </div> | ||||
{isProjectAnalyzed | {isProjectAnalyzed |
import { shallow } from 'enzyme'; | import { shallow } from 'enzyme'; | ||||
import ProjectCard from '../ProjectCard'; | import ProjectCard from '../ProjectCard'; | ||||
const PROJECT = { analysisDate: '2017-01-01', key: 'foo', name: 'Foo' }; | |||||
const PROJECT = { analysisDate: '2017-01-01', key: 'foo', name: 'Foo', tags: [] }; | |||||
const MEASURES = {}; | const MEASURES = {}; | ||||
it('should display analysis date', () => { | it('should display analysis date', () => { | ||||
it('should display loading', () => { | it('should display loading', () => { | ||||
expect(shallow(<ProjectCard project={PROJECT} />)).toMatchSnapshot(); | expect(shallow(<ProjectCard project={PROJECT} />)).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should display tags', () => { | |||||
const project = { ...PROJECT, tags: ['foo', 'bar'] }; | |||||
expect(shallow(<ProjectCard project={project} />)).toMatchSnapshot(); | |||||
}); |
</div> | </div> | ||||
</div> | </div> | ||||
`; | `; | ||||
exports[`test should display tags 1`] = ` | |||||
<div | |||||
className="boxed-group project-card boxed-group-loading" | |||||
data-key="foo"> | |||||
<div | |||||
className="boxed-group-header"> | |||||
<h2 | |||||
className="project-card-name"> | |||||
<Link | |||||
onlyActiveOnIndex={false} | |||||
style={Object {}} | |||||
to={ | |||||
Object { | |||||
"pathname": "/dashboard", | |||||
"query": Object { | |||||
"id": "foo", | |||||
}, | |||||
} | |||||
}> | |||||
Foo | |||||
</Link> | |||||
</h2> | |||||
<TagsList | |||||
allowMultiLine={false} | |||||
allowUpdate={false} | |||||
tags={ | |||||
Array [ | |||||
"foo", | |||||
"bar", | |||||
] | |||||
} /> | |||||
</div> | |||||
<div | |||||
className="boxed-group-inner" /> | |||||
<div | |||||
className="project-card-analysis-date note"> | |||||
overview.last_analysis_on_x.January 1, 2017 12:00 AM | |||||
</div> | |||||
</div> | |||||
`; |
.tags-list { | |||||
padding-left: 6px; | |||||
} | |||||
.tags-list i { | |||||
padding-left: 4px; | |||||
} | |||||
.tags-list i::before { | |||||
font-size: 12px; | |||||
} | |||||
.tags-list span { | |||||
display: inline-block; | |||||
vertical-align: text-top; | |||||
max-width: 220px; | |||||
padding-left: 4px; | |||||
margin-top: 2px; | |||||
opacity: 0.6; | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
// @flow | |||||
import React from 'react'; | |||||
import classNames from 'classnames'; | |||||
import './TagsList.css'; | |||||
type Props = { | |||||
tags: Array<string>, | |||||
allowUpdate: boolean, | |||||
allowMultiLine: boolean | |||||
}; | |||||
export default class TagsList extends React.PureComponent { | |||||
props: Props; | |||||
static defaultProps = { | |||||
allowUpdate: false, | |||||
allowMultiLine: false | |||||
}; | |||||
render() { | |||||
const { tags, allowUpdate } = this.props; | |||||
const spanClass = classNames('note', { | |||||
'text-ellipsis': !this.props.allowMultiLine | |||||
}); | |||||
return ( | |||||
<span className="tags-list" title={tags.join(', ')}> | |||||
<i className="icon-tags icon-half-transparent" /> | |||||
<span className={spanClass}>{tags.join(', ')}</span> | |||||
{allowUpdate && <i className="icon-dropdown icon-half-transparent" />} | |||||
</span> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2017 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import React from 'react'; | |||||
import TagsList from '../TagsList'; | |||||
const tags = ['foo', 'bar']; | |||||
it('should render with a list of tag', () => { | |||||
const taglist = shallow(<TagsList tags={tags} />); | |||||
expect(taglist.text()).toBe(tags.join(', ')); | |||||
expect(taglist.find('i').length).toBe(1); | |||||
expect(taglist.find('span.note').hasClass('text-ellipsis')).toBe(true); | |||||
}); | |||||
it('should FAIL to render without tags', () => { | |||||
expect(() => shallow(<TagsList />)).toThrow(); | |||||
}); | |||||
it('should correctly handle a lot of tags', () => { | |||||
const lotOfTags = []; | |||||
for (let i = 0; i < 20; i++) { | |||||
lotOfTags.push(tags); | |||||
} | |||||
const taglist = shallow(<TagsList tags={lotOfTags} allowMultiLine={true} />); | |||||
expect(taglist.text()).toBe(lotOfTags.join(', ')); | |||||
expect(taglist.find('span.note').hasClass('text-ellipsis')).toBe(false); | |||||
}); | |||||
it('should render with a caret on the right if update is allowed', () => { | |||||
const taglist = shallow(<TagsList tags={tags} allowUpdate={true} />); | |||||
expect(taglist.find('i').length).toBe(2); | |||||
}); |
*/ | */ | ||||
import { getLanguages } from '../api/languages'; | import { getLanguages } from '../api/languages'; | ||||
import { getGlobalNavigation, getComponentNavigation } from '../api/nav'; | import { getGlobalNavigation, getComponentNavigation } from '../api/nav'; | ||||
import { getComponentTags } from '../api/components'; | |||||
import * as auth from '../api/auth'; | import * as auth from '../api/auth'; | ||||
import { getOrganizations } from '../api/organizations'; | import { getOrganizations } from '../api/organizations'; | ||||
import { receiveLanguages } from './languages/actions'; | import { receiveLanguages } from './languages/actions'; | ||||
export const fetchProject = key => | export const fetchProject = key => | ||||
dispatch => | dispatch => | ||||
getComponentNavigation(key).then(component => { | |||||
Promise.all([getComponentNavigation(key), getComponentTags(key)]).then(([component, tags]) => { | |||||
component.tags = tags; | |||||
dispatch(receiveComponents([addQualifier(component)])); | dispatch(receiveComponents([addQualifier(component)])); | ||||
if (component.organization != null) { | if (component.organization != null) { | ||||
dispatch(fetchOrganizations([component.organization])); | dispatch(fetchOrganizations([component.organization])); |
navigation=Navigation | navigation=Navigation | ||||
never=Never | never=Never | ||||
none=None | none=None | ||||
no_tags=No tags | |||||
off=Off | off=Off | ||||
on=On | on=On | ||||
organization_key=Organization Key | organization_key=Organization Key |