Переглянути джерело

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

tags/6.4-RC1
Grégoire Aubert 7 роки тому
джерело
коміт
fff745c5b6

+ 11
- 5
server/sonar-web/src/main/js/api/components.js Переглянути файл

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);

+ 2
- 1
server/sonar-web/src/main/js/apps/overview/components/App.js Переглянути файл

component: { component: {
id: string, id: string,
key: string, key: string,
qualifier: string
qualifier: string,
tags: Array<string>
}, },
router: Object router: Object
}; };

+ 12
- 1
server/sonar-web/src/main/js/apps/overview/meta/Meta.js Переглянути файл

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

+ 6
- 1
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js Переглянути файл

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

+ 6
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js Переглянути файл

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();
});

+ 41
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap Переглянути файл

</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>
`;

+ 20
- 0
server/sonar-web/src/main/js/components/ui/TagsList.css Переглянути файл

.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 Переглянути файл

/*
* 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 Переглянути файл

/*
* 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 Переглянути файл

*/ */
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]));

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Переглянути файл

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

Завантаження…
Відмінити
Зберегти