Browse Source

SONAR-8843 Add the tags on the projects page and project homepage (#1801)

tags/6.4-RC1
Grégoire Aubert 7 years ago
parent
commit
fff745c5b6

+ 11
- 5
server/sonar-web/src/main/js/api/components.js View File

@@ -107,20 +107,26 @@ export function getTree(component: string, options?: Object = {}) {
return getJSON(url, data);
}

export function getParents({ id, key }: { id: string, key: string }) {
export function getComponentShow(component: string) {
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) {
const url = '/api/components/show';
return getJSON(url, { component }).then(r => {
return getComponentShow(component).then(r => {
const reversedAncestors = [...r.ancestors].reverse();
return [...reversedAncestors, r.component];
});
}

export function getComponentTags(component: string) {
return getComponentShow(component).then(r => r.component.tags || []);
}

export function getMyProjects(data?: Object) {
const url = '/api/projects/search_my_projects';
return getJSON(url, data);

+ 2
- 1
server/sonar-web/src/main/js/apps/overview/components/App.js View File

@@ -29,7 +29,8 @@ type Props = {
component: {
id: string,
key: string,
qualifier: string
qualifier: string,
tags: Array<string>
},
router: Object
};

+ 12
- 1
server/sonar-web/src/main/js/apps/overview/meta/Meta.js View File

@@ -26,7 +26,9 @@ import MetaQualityGate from './MetaQualityGate';
import MetaQualityProfiles from './MetaQualityProfiles';
import AnalysesList from '../events/AnalysesList';
import MetaSize from './MetaSize';
import TagsList from '../../../components/ui/TagsList';
import { areThereCustomOrganizations } from '../../../store/rootReducer';
import { translate } from '../../../helpers/l10n';

const Meta = ({ component, measures, areThereCustomOrganizations }) => {
const { qualifier, description, qualityProfiles, qualityGate } = component;
@@ -41,9 +43,10 @@ const Meta = ({ component, measures, areThereCustomOrganizations }) => {

const shouldShowQualityProfiles = !isView && !isDeveloper && hasQualityProfiles;
const shouldShowQualityGate = !isView && !isDeveloper && hasQualityGate;

const shouldShowOrganizationKey = component.organization != null && areThereCustomOrganizations;

const configuration = component.configuration || {};

return (
<div className="overview-meta">
{hasDescription &&
@@ -53,6 +56,14 @@ const Meta = ({ component, measures, areThereCustomOrganizations }) => {

<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} />}

{shouldShowQualityProfiles && <MetaQualityProfiles profiles={qualityProfiles} />}

+ 6
- 1
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js View File

@@ -26,6 +26,7 @@ import ProjectCardQualityGate from './ProjectCardQualityGate';
import ProjectCardMeasures from './ProjectCardMeasures';
import FavoriteContainer from '../../../components/controls/FavoriteContainer';
import Organization from '../../../components/shared/Organization';
import TagsList from '../../../components/ui/TagsList';
import { translate, translateWithParameters } from '../../../helpers/l10n';

export default class ProjectCard extends React.PureComponent {
@@ -35,7 +36,10 @@ export default class ProjectCard extends React.PureComponent {
project?: {
analysisDate?: string,
key: string,
name: string
name: string,
tags: Array<string>,
isFavorite?: boolean,
organization?: string
}
};

@@ -74,6 +78,7 @@ export default class ProjectCard extends React.PureComponent {
{project.name}
</Link>
</h2>
{project.tags.length > 0 && <TagsList tags={project.tags} />}
</div>

{isProjectAnalyzed

+ 6
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js View File

@@ -21,7 +21,7 @@ import React from 'react';
import { shallow } from 'enzyme';
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 = {};

it('should display analysis date', () => {
@@ -44,3 +44,8 @@ it('should NOT display analysis date', () => {
it('should display loading', () => {
expect(shallow(<ProjectCard project={PROJECT} />)).toMatchSnapshot();
});

it('should display tags', () => {
const project = { ...PROJECT, tags: ['foo', 'bar'] };
expect(shallow(<ProjectCard project={project} />)).toMatchSnapshot();
});

+ 41
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap View File

@@ -36,3 +36,44 @@ exports[`test should display loading 1`] = `
</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>
`;

+ 20
- 0
server/sonar-web/src/main/js/components/ui/TagsList.css View File

@@ -0,0 +1,20 @@
.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;
}

+ 53
- 0
server/sonar-web/src/main/js/components/ui/TagsList.js View File

@@ -0,0 +1,53 @@
/*
* 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>
);
}
}

+ 50
- 0
server/sonar-web/src/main/js/components/ui/__tests__/TagsList-test.js View File

@@ -0,0 +1,50 @@
/*
* 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);
});

+ 3
- 1
server/sonar-web/src/main/js/store/rootActions.js View File

@@ -19,6 +19,7 @@
*/
import { getLanguages } from '../api/languages';
import { getGlobalNavigation, getComponentNavigation } from '../api/nav';
import { getComponentTags } from '../api/components';
import * as auth from '../api/auth';
import { getOrganizations } from '../api/organizations';
import { receiveLanguages } from './languages/actions';
@@ -57,7 +58,8 @@ const addQualifier = project => ({

export const fetchProject = key =>
dispatch =>
getComponentNavigation(key).then(component => {
Promise.all([getComponentNavigation(key), getComponentTags(key)]).then(([component, tags]) => {
component.tags = tags;
dispatch(receiveComponents([addQualifier(component)]));
if (component.organization != null) {
dispatch(fetchOrganizations([component.organization]));

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -109,6 +109,7 @@ name_too_long_x=Name is too long (maximum is {0} characters)
navigation=Navigation
never=Never
none=None
no_tags=No tags
off=Off
on=On
organization_key=Organization Key

Loading…
Cancel
Save