aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/overview
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-08-17 21:39:59 +0200
committerJanos Gyerik <janos.gyerik@sonarsource.com>2017-09-12 11:34:36 +0200
commitcff416d7f9910c258bc8d7175c08afff96a9eb2a (patch)
tree78500908a2afde31b28ef938d4d61609d448f279 /server/sonar-web/src/main/js/apps/overview
parent139467cf51932ba232190f363ad864e60eb3fd4d (diff)
downloadsonarqube-cff416d7f9910c258bc8d7175c08afff96a9eb2a.tar.gz
sonarqube-cff416d7f9910c258bc8d7175c08afff96a9eb2a.zip
SONAR-9702 Build UI for short-lived branches (#2371)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/overview')
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/App.js11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/AppContainer.js28
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/Meta.js11
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js16
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js89
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js61
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/routes.ts2
9 files changed, 192 insertions, 39 deletions
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 37e04ee3742..aa36f8402aa 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
@@ -22,10 +22,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import OverviewApp from './OverviewApp';
import EmptyOverview from './EmptyOverview';
+import { isShortLivingBranch } from '../../../helpers/branches';
+import { getProjectBranchUrl } from '../../../helpers/urls';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
/*::
type Props = {
+ branch: {},
component: {
analysisDate?: string,
id: string,
@@ -33,6 +36,7 @@ type Props = {
qualifier: string,
tags: Array<string>
},
+ onComponentChange: {} => void,
router: Object
};
*/
@@ -52,6 +56,9 @@ export default class App extends React.PureComponent {
query: { id: this.props.component.key }
});
}
+ if (isShortLivingBranch(this.props.branch)) {
+ this.context.router.replace(getProjectBranchUrl(this.props.component.key, this.props.branch));
+ }
}
isPortfolio() {
@@ -59,7 +66,7 @@ export default class App extends React.PureComponent {
}
render() {
- if (this.isPortfolio()) {
+ if (this.isPortfolio() || isShortLivingBranch(this.props.branch)) {
return null;
}
@@ -77,6 +84,6 @@ export default class App extends React.PureComponent {
return <EmptyOverview component={component} />;
}
- return <OverviewApp component={component} />;
+ return <OverviewApp component={component} onComponentChange={this.props.onComponentChange} />;
}
}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js b/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js
deleted file mode 100644
index a6025ae8fe4..00000000000
--- a/server/sonar-web/src/main/js/apps/overview/components/AppContainer.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 { connect } from 'react-redux';
-import App from './App';
-import { getComponent } from '../../../store/rootReducer';
-
-const mapStateToProps = (state, ownProps) => ({
- component: getComponent(state, ownProps.location.query.id)
-});
-
-export default connect(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
index ac67fbae194..9e28bf48386 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
@@ -41,7 +41,8 @@ import '../styles.css';
/*::
type Props = {
- component: Component
+ component: Component,
+ onComponentChange: {} => void
};
*/
@@ -175,7 +176,12 @@ export default class OverviewApp extends React.PureComponent {
</div>
<div className="page-sidebar-fixed">
- <Meta component={component} history={history} measures={measures} />
+ <Meta
+ component={component}
+ history={history}
+ measures={measures}
+ onComponentChange={this.props.onComponentChange}
+ />
</div>
</div>
</div>
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 fd849228480..a79b44dbd41 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
@@ -30,7 +30,14 @@ import MetaSize from './MetaSize';
import MetaTags from './MetaTags';
import { areThereCustomOrganizations } from '../../../store/rootReducer';
-const Meta = ({ component, history, measures, areThereCustomOrganizations, router }) => {
+const Meta = ({
+ component,
+ history,
+ measures,
+ areThereCustomOrganizations,
+ onComponentChange,
+ router
+}) => {
const { qualifier, description, qualityProfiles, qualityGate } = component;
const isProject = qualifier === 'TRK';
@@ -53,7 +60,7 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route
<MetaSize component={component} measures={measures} />
- {isProject && <MetaTags component={component} />}
+ {isProject && <MetaTags component={component} onComponentChange={onComponentChange} />}
{(isProject || isApplication) &&
<AnalysesList
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js
index b9d67b8e474..7254f0e2197 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaTags.js
@@ -19,9 +19,10 @@
*/
//@flow
import React from 'react';
+import { setProjectTags } from '../../../api/components';
import { translate } from '../../../helpers/l10n';
import TagsList from '../../../components/tags/TagsList';
-import ProjectTagsSelectorContainer from '../../projects/components/ProjectTagsSelectorContainer';
+import MetaTagsSelector from './MetaTagsSelector';
/*::
type Props = {
@@ -31,7 +32,8 @@ type Props = {
configuration?: {
showSettings?: boolean
}
- }
+ },
+ onComponentChange: {} => void
};
*/
@@ -104,6 +106,13 @@ export default class MetaTags extends React.PureComponent {
};
}
+ handleSetProjectTags = (tags /*: Array<string> */) => {
+ setProjectTags({ project: this.props.component.key, tags: tags.join(',') }).then(
+ () => this.props.onComponentChange({ tags }),
+ () => {}
+ );
+ };
+
render() {
const { tags, key } = this.props.component;
const { popupOpen, popupPosition } = this.state;
@@ -119,10 +128,11 @@ export default class MetaTags extends React.PureComponent {
</button>
{popupOpen &&
<div ref={tagsSelector => (this.tagsSelector = tagsSelector)}>
- <ProjectTagsSelectorContainer
+ <MetaTagsSelector
position={popupPosition}
project={key}
selectedTags={tags}
+ setProjectTags={this.handleSetProjectTags}
/>
</div>}
</div>
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js
new file mode 100644
index 00000000000..76e25c9c1f1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js
@@ -0,0 +1,89 @@
+/*
+ * 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 { debounce, without } from 'lodash';
+import TagsSelector from '../../../components/tags/TagsSelector';
+import { searchProjectTags } from '../../../api/components';
+
+/*::
+type Props = {
+ position: {},
+ project: string,
+ selectedTags: Array<string>,
+ setProjectTags: (Array<string>) => void
+};
+*/
+
+/*::
+type State = {
+ searchResult: Array<string>
+};
+*/
+
+const LIST_SIZE = 10;
+
+export default class MetaTagsSelector extends React.PureComponent {
+ /*:: props: Props; */
+ /*:: state: State; */
+
+ constructor(props /*: Props */) {
+ super(props);
+ this.state = { searchResult: [] };
+ this.onSearch = debounce(this.onSearch, 250);
+ }
+
+ componentDidMount() {
+ this.onSearch('');
+ }
+
+ onSearch = (query /*: string */) => {
+ searchProjectTags({
+ q: query || '',
+ ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, 100)
+ }).then(result => {
+ this.setState({
+ searchResult: result.tags
+ });
+ });
+ };
+
+ onSelect = (tag /*: string */) => {
+ this.props.setProjectTags([...this.props.selectedTags, tag]);
+ };
+
+ onUnselect = (tag /*: string */) => {
+ this.props.setProjectTags(without(this.props.selectedTags, tag));
+ };
+
+ render() {
+ return (
+ <TagsSelector
+ position={this.props.position}
+ tags={this.state.searchResult}
+ selectedTags={this.props.selectedTags}
+ listSize={LIST_SIZE}
+ onSearch={this.onSearch}
+ onSelect={this.onSelect}
+ onUnselect={this.onUnselect}
+ />
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js
new file mode 100644
index 00000000000..59744a204de
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/MetaTagsSelector-test.js
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+/* eslint-disable import/order, import/first */
+import * as React from 'react';
+import { mount, shallow } from 'enzyme';
+import MetaTagsSelector from '../MetaTagsSelector';
+
+jest.mock('../../../../api/components', () => ({
+ searchProjectTags: jest.fn()
+}));
+
+jest.useFakeTimers();
+
+import { searchProjectTags } from '../../../../api/components';
+
+it('searches tags on mount', () => {
+ searchProjectTags.mockImplementation(() => Promise.resolve({ tags: ['foo', 'bar'] }));
+
+ mount(
+ <MetaTagsSelector position={{}} project="foo" selectedTags={[]} setProjectTags={jest.fn()} />
+ );
+ jest.runAllTimers();
+
+ expect(searchProjectTags).toBeCalledWith({ ps: 9, q: '' });
+});
+
+it('selects and deselects tags', () => {
+ const setProjectTags = jest.fn();
+ const wrapper = shallow(
+ <MetaTagsSelector
+ position={{}}
+ project="foo"
+ selectedTags={['foo', 'bar']}
+ setProjectTags={setProjectTags}
+ />
+ );
+
+ wrapper.find('TagsSelector').prop('onSelect')('baz');
+ expect(setProjectTags).toHaveBeenLastCalledWith(['foo', 'bar', 'baz']);
+
+ // note that the `selectedTags` is a prop and so it wasn't changed
+ wrapper.find('TagsSelector').prop('onUnselect')('bar');
+ expect(setProjectTags).toHaveBeenLastCalledWith(['foo']);
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap
index 0eb1415ab03..875302462d5 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.js.snap
@@ -40,7 +40,7 @@ exports[`should open the tag selector on click 2`] = `
/>
</button>
<div>
- <Connect(ProjectTagsSelectorContainer)
+ <MetaTagsSelector
position={
Object {
"right": 0,
@@ -54,6 +54,7 @@ exports[`should open the tag selector on click 2`] = `
"bar",
]
}
+ setProjectTags={[Function]}
/>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/overview/routes.ts b/server/sonar-web/src/main/js/apps/overview/routes.ts
index 21e41c6b087..9eb0b5ef7c3 100644
--- a/server/sonar-web/src/main/js/apps/overview/routes.ts
+++ b/server/sonar-web/src/main/js/apps/overview/routes.ts
@@ -22,7 +22,7 @@ import { RouterState, IndexRouteProps } from 'react-router';
const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
- import('./components/AppContainer').then(i => callback(null, { component: i.default }));
+ import('./components/App').then(i => callback(null, { component: (i as any).default }));
}
}
];