aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorGrégoire Aubert <gregaubert@users.noreply.github.com>2017-03-20 13:15:55 +0100
committerGitHub <noreply@github.com>2017-03-20 13:15:55 +0100
commitfff745c5b6f9e713a66dd9393ceafd9aad397f0d (patch)
tree837fc437a3781e7b1d0b31dffeec3add493083e4 /server/sonar-web/src/main/js
parentab8377a211348bc4ca17c731a71f6cd80f855eca (diff)
downloadsonarqube-fff745c5b6f9e713a66dd9393ceafd9aad397f0d.tar.gz
sonarqube-fff745c5b6f9e713a66dd9393ceafd9aad397f0d.zip
SONAR-8843 Add the tags on the projects page and project homepage (#1801)
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/api/components.js16
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.js3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.js13
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap41
-rw-r--r--server/sonar-web/src/main/js/components/ui/TagsList.css20
-rw-r--r--server/sonar-web/src/main/js/components/ui/TagsList.js53
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/TagsList-test.js50
-rw-r--r--server/sonar-web/src/main/js/store/rootActions.js4
10 files changed, 204 insertions, 10 deletions
diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js
index c2ec0a454f5..75fa13cb001 100644
--- a/server/sonar-web/src/main/js/api/components.js
+++ b/server/sonar-web/src/main/js/api/components.js
@@ -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);
diff --git a/server/sonar-web/src/main/js/apps/overview/components/App.js b/server/sonar-web/src/main/js/apps/overview/components/App.js
index 83a1f73c1e3..517569d47f9 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/App.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/App.js
@@ -29,7 +29,8 @@ type Props = {
component: {
id: string,
key: string,
- qualifier: string
+ qualifier: string,
+ tags: Array<string>
},
router: Object
};
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
index b5102e26d64..c5dfaa64ecb 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/Meta.js
@@ -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} />}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
index 18efa9f2e64..6f3691c943d 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
@@ -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
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js
index 23d90396a25..3961e537d86 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCard-test.js
@@ -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();
+});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap
index 43c9b8fa589..b53a20d538b 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCard-test.js.snap
@@ -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>
+`;
diff --git a/server/sonar-web/src/main/js/components/ui/TagsList.css b/server/sonar-web/src/main/js/components/ui/TagsList.css
new file mode 100644
index 00000000000..bb48ef2bf21
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/ui/TagsList.css
@@ -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;
+}
diff --git a/server/sonar-web/src/main/js/components/ui/TagsList.js b/server/sonar-web/src/main/js/components/ui/TagsList.js
new file mode 100644
index 00000000000..6f568df0357
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/ui/TagsList.js
@@ -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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/TagsList-test.js b/server/sonar-web/src/main/js/components/ui/__tests__/TagsList-test.js
new file mode 100644
index 00000000000..9eec4be8ed2
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/TagsList-test.js
@@ -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);
+});
diff --git a/server/sonar-web/src/main/js/store/rootActions.js b/server/sonar-web/src/main/js/store/rootActions.js
index 0e95f02d56c..58685e5cb27 100644
--- a/server/sonar-web/src/main/js/store/rootActions.js
+++ b/server/sonar-web/src/main/js/store/rootActions.js
@@ -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]));