diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-08-17 21:39:59 +0200 |
---|---|---|
committer | Janos Gyerik <janos.gyerik@sonarsource.com> | 2017-09-12 11:34:36 +0200 |
commit | cff416d7f9910c258bc8d7175c08afff96a9eb2a (patch) | |
tree | 78500908a2afde31b28ef938d4d61609d448f279 /server/sonar-web/src/main/js/apps | |
parent | 139467cf51932ba232190f363ad864e60eb3fd4d (diff) | |
download | sonarqube-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')
34 files changed, 206 insertions, 200 deletions
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js index 9fa9496d367..8128a3827e3 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js @@ -38,7 +38,6 @@ import { } from '../../../api/ce'; import { updateTask, mapFiltersToParameters } from '../utils'; /*:: import type { Task } from '../types'; */ -import { getComponent } from '../../../store/rootReducer'; import '../background-tasks.css'; import { fetchOrganizations } from '../../../store/rootActions'; import { translate } from '../../../helpers/l10n'; @@ -257,12 +256,6 @@ class BackgroundTasksApp extends React.PureComponent { } } -const mapStateToProps = (state, ownProps) => ({ - component: ownProps.location.query.id - ? getComponent(state, ownProps.location.query.id) - : undefined -}); - const mapDispatchToProps = { fetchOrganizations }; -export default connect(mapStateToProps, mapDispatchToProps)(BackgroundTasksApp); +export default connect(null, mapDispatchToProps)(BackgroundTasksApp); diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js index 5a19455b17b..b2ef518c558 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js @@ -20,7 +20,7 @@ /* @flow */ import React from 'react'; import { STATUSES } from './../constants'; -import PendingIcon from '../../../components/shared/pending-icon'; +import PendingIcon from '../../../components/icons-components/PendingIcon'; import { translate } from '../../../helpers/l10n'; /*:: import type { Task } from '../types'; */ diff --git a/server/sonar-web/src/main/js/apps/code/components/App.js b/server/sonar-web/src/main/js/apps/code/components/App.js index 3a0f994aa1c..c59cac6bade 100644 --- a/server/sonar-web/src/main/js/apps/code/components/App.js +++ b/server/sonar-web/src/main/js/apps/code/components/App.js @@ -20,7 +20,6 @@ import classNames from 'classnames'; import React from 'react'; import Helmet from 'react-helmet'; -import { connect } from 'react-redux'; import Components from './Components'; import Breadcrumbs from './Breadcrumbs'; import SourceViewer from './../../../components/SourceViewer/SourceViewer'; @@ -33,11 +32,10 @@ import { parseError } from '../utils'; import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket'; -import { getComponent } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; import '../code.css'; -class App extends React.PureComponent { +export default class App extends React.PureComponent { state = { loading: true, baseComponent: null, @@ -75,7 +73,7 @@ class App extends React.PureComponent { this.setState({ loading: true }); const isPortfolio = ['VW', 'SVW'].includes(component.qualifier); - retrieveComponentChildren(component.key, isPortfolio) + retrieveComponentChildren(component.key, isPortfolio, component.branch) .then(r => { addComponent(r.baseComponent); this.handleUpdate(); @@ -92,7 +90,7 @@ class App extends React.PureComponent { this.setState({ loading: true }); const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier); - retrieveComponent(componentKey, isPortfolio) + retrieveComponent(componentKey, isPortfolio, this.props.component.branch) .then(r => { if (this.mounted) { if (['FIL', 'UTS'].includes(r.component.qualifier)) { @@ -132,10 +130,10 @@ class App extends React.PureComponent { this.loadComponent(finalKey); } - handleLoadMore() { + handleLoadMore = () => { const { baseComponent, page } = this.state; const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier); - loadMoreChildren(baseComponent.key, page + 1, isPortfolio) + loadMoreChildren(baseComponent.key, page + 1, isPortfolio, this.props.component.branch) .then(r => { if (this.mounted) { this.setState({ @@ -148,16 +146,16 @@ class App extends React.PureComponent { .catch(e => { if (this.mounted) { this.setState({ loading: false }); - parseError(e).then(this.handleError.bind(this)); + parseError(e).then(this.handleError); } }); - } + }; - handleError(error) { + handleError = error => { if (this.mounted) { this.setState({ error }); } - } + }; render() { const { component, location } = this.props; @@ -186,7 +184,7 @@ class App extends React.PureComponent { {error} </div>} - <Search location={location} component={component} onError={this.handleError.bind(this)} /> + <Search location={location} component={component} onError={this.handleError} /> <div className="code-components"> {shouldShowBreadcrumbs && @@ -202,24 +200,14 @@ class App extends React.PureComponent { </div>} {shouldShowComponents && - <ListFooter - count={components.length} - total={total} - loadMore={this.handleLoadMore.bind(this)} - />} + <ListFooter count={components.length} total={total} loadMore={this.handleLoadMore} />} {shouldShowSourceViewer && <div className="spacer-top"> - <SourceViewer component={sourceViewer.key} /> + <SourceViewer branch={component.branch} component={sourceViewer.key} /> </div>} </div> </div> ); } } - -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/code/components/Component.js b/server/sonar-web/src/main/js/apps/code/components/Component.js index e36ad13bcf6..4fe40dfa618 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Component.js +++ b/server/sonar-web/src/main/js/apps/code/components/Component.js @@ -70,10 +70,10 @@ export default class Component extends React.PureComponent { switch (component.qualifier) { case 'FIL': case 'UTS': - componentAction = <ComponentPin component={component} />; + componentAction = <ComponentPin branch={rootComponent.branch} component={component} />; break; default: - componentAction = <ComponentDetach component={component} />; + componentAction = <ComponentDetach branch={rootComponent.branch} component={component} />; } } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentDetach.js b/server/sonar-web/src/main/js/apps/code/components/ComponentDetach.js index 5277be98427..30fccdfa7bf 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentDetach.js +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentDetach.js @@ -21,10 +21,10 @@ import React from 'react'; import { Link } from 'react-router'; import { translate } from '../../../helpers/l10n'; -export default function ComponentDetach({ component }) { +export default function ComponentDetach({ component, branch }) { return ( <Link - to={{ pathname: '/dashboard', query: { id: component.refKey || component.key } }} + to={{ pathname: '/dashboard', query: { branch, id: component.refKey || component.key } }} className="icon-detach" title={translate('code.open_component_page')} /> diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentName.js b/server/sonar-web/src/main/js/apps/code/components/ComponentName.js index bad7f487537..921aa3f724c 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentName.js +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentName.js @@ -71,7 +71,7 @@ const ComponentName = ({ component, rootComponent, previous, canBrowse }) => { </Link> ); } else if (canBrowse) { - const query = { id: rootComponent.key }; + const query = { id: rootComponent.key, branch: rootComponent.branch }; if (component.key !== rootComponent.key) { Object.assign(query, { selected: component.key }); } diff --git a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js index 7d0f9478c59..641017207d7 100644 --- a/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js +++ b/server/sonar-web/src/main/js/apps/code/components/ComponentPin.js @@ -22,10 +22,10 @@ import Workspace from '../../../components/workspace/main'; import PinIcon from '../../../components/shared/pin-icon'; import { translate } from '../../../helpers/l10n'; -const ComponentPin = ({ component }) => { +const ComponentPin = ({ branch, component }) => { const handleClick = e => { e.preventDefault(); - Workspace.openComponent({ key: component.key }); + Workspace.openComponent({ branch, key: component.key }); }; return ( diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.js b/server/sonar-web/src/main/js/apps/code/components/Search.js index a34d87e3c9e..4d772e7a3b1 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Search.js +++ b/server/sonar-web/src/main/js/apps/code/components/Search.js @@ -46,7 +46,7 @@ export default class Search extends React.PureComponent { }; componentWillMount() { - this.handleSearch = debounce(this.handleSearch.bind(this), 250); + this.handleSearch = debounce(this.handleSearch, 250); } componentDidMount() { @@ -100,6 +100,7 @@ export default class Search extends React.PureComponent { this.context.router.push({ pathname: '/code', query: { + branch: component.branch, id: component.key, selected: selected.key } @@ -126,7 +127,7 @@ export default class Search extends React.PureComponent { } } - handleSearch(query) { + handleSearch = query => { // first time check if value has changed due to debounce if (this.mounted && this.checkInputValue(query)) { const { component, onError } = this.props; @@ -135,7 +136,12 @@ export default class Search extends React.PureComponent { const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier); const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL'; - getTree(component.key, { q: query, s: 'qualifier,name', qualifiers }) + getTree(component.key, { + branch: component.branch, + q: query, + s: 'qualifier,name', + qualifiers + }) .then(r => { // second time check if value has change due to api request if (this.mounted && this.checkInputValue(query)) { @@ -154,7 +160,7 @@ export default class Search extends React.PureComponent { } }); } - } + }; handleQueryChange(query) { this.setState({ query }); diff --git a/server/sonar-web/src/main/js/apps/code/routes.ts b/server/sonar-web/src/main/js/apps/code/routes.ts index fcffeff191c..9eb0b5ef7c3 100644 --- a/server/sonar-web/src/main/js/apps/code/routes.ts +++ b/server/sonar-web/src/main/js/apps/code/routes.ts @@ -22,7 +22,7 @@ import { RouterState, IndexRouteProps } from 'react-router'; const routes = [ { getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { - import('./components/App').then(i => callback(null, { component: i.default })); + import('./components/App').then(i => callback(null, { component: (i as any).default })); } } ]; diff --git a/server/sonar-web/src/main/js/apps/code/utils.js b/server/sonar-web/src/main/js/apps/code/utils.js index 2a1f5552cc0..52975be78ec 100644 --- a/server/sonar-web/src/main/js/apps/code/utils.js +++ b/server/sonar-web/src/main/js/apps/code/utils.js @@ -120,7 +120,7 @@ function getMetrics(isPortfolio) { * @param {boolean} isPortfolio * @returns {Promise} */ -function retrieveComponentBase(componentKey, isPortfolio) { +function retrieveComponentBase(componentKey, isPortfolio, branch) { const existing = getComponentFromBucket(componentKey); if (existing) { return Promise.resolve(existing); @@ -128,7 +128,7 @@ function retrieveComponentBase(componentKey, isPortfolio) { const metrics = getMetrics(isPortfolio); - return getComponent(componentKey, metrics).then(component => { + return getComponent(componentKey, metrics, branch).then(component => { addComponent(component); return component; }); @@ -139,7 +139,7 @@ function retrieveComponentBase(componentKey, isPortfolio) { * @param {boolean} isPortfolio * @returns {Promise} */ -export function retrieveComponentChildren(componentKey, isPortfolio) { +export function retrieveComponentChildren(componentKey, isPortfolio, branch) { const existing = getComponentChildren(componentKey); if (existing) { return Promise.resolve({ @@ -151,7 +151,7 @@ export function retrieveComponentChildren(componentKey, isPortfolio) { const metrics = getMetrics(isPortfolio); - return getChildren(componentKey, metrics, { ps: PAGE_SIZE, s: 'qualifier,name' }) + return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, s: 'qualifier,name' }) .then(prepareChildren) .then(expandRootDir(metrics)) .then(r => { @@ -162,13 +162,13 @@ export function retrieveComponentChildren(componentKey, isPortfolio) { }); } -function retrieveComponentBreadcrumbs(componentKey) { +function retrieveComponentBreadcrumbs(componentKey, branch) { const existing = getComponentBreadcrumbs(componentKey); if (existing) { return Promise.resolve(existing); } - return getBreadcrumbs(componentKey).then(skipRootDir).then(breadcrumbs => { + return getBreadcrumbs(componentKey, branch).then(skipRootDir).then(breadcrumbs => { addComponentBreadcrumbs(componentKey, breadcrumbs); return breadcrumbs; }); @@ -179,11 +179,11 @@ function retrieveComponentBreadcrumbs(componentKey) { * @param {boolean} isPortfolio * @returns {Promise} */ -export function retrieveComponent(componentKey, isPortfolio) { +export function retrieveComponent(componentKey, isPortfolio, branch) { return Promise.all([ - retrieveComponentBase(componentKey, isPortfolio), - retrieveComponentChildren(componentKey, isPortfolio), - retrieveComponentBreadcrumbs(componentKey) + retrieveComponentBase(componentKey, isPortfolio, branch), + retrieveComponentChildren(componentKey, isPortfolio, branch), + retrieveComponentBreadcrumbs(componentKey, branch) ]).then(r => { return { component: r[0], @@ -195,10 +195,10 @@ export function retrieveComponent(componentKey, isPortfolio) { }); } -export function loadMoreChildren(componentKey, page, isPortfolio) { +export function loadMoreChildren(componentKey, page, isPortfolio, branch) { const metrics = getMetrics(isPortfolio); - return getChildren(componentKey, metrics, { ps: PAGE_SIZE, p: page }) + return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, p: page }) .then(prepareChildren) .then(expandRootDir(metrics)) .then(r => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js index f3d91451c47..f823961bb6f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js @@ -22,12 +22,7 @@ import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import App from './App'; import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { - getComponent, - getCurrentUser, - getMetrics, - getMetricsKey -} from '../../../store/rootReducer'; +import { getCurrentUser, getMetrics, getMetricsKey } from '../../../store/rootReducer'; import { fetchMetrics } from '../../../store/rootActions'; import { getMeasuresAndMeta } from '../../../api/measures'; import { getLeakPeriod } from '../../../helpers/periods'; @@ -35,8 +30,7 @@ import { enhanceMeasure } from '../../../components/measure/utils'; /*:: import type { Component, Period } from '../types'; */ /*:: import type { Measure, MeasureEnhanced } from '../../../components/measure/types'; */ -const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id), +const mapStateToProps = state => ({ currentUser: getCurrentUser(state), metrics: getMetrics(state), metricsKey: getMetricsKey(state) diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js b/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js index 1f40c41775f..4f2f1ba1ca6 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js @@ -19,12 +19,10 @@ */ import React from 'react'; import Helmet from 'react-helmet'; -import { connect } from 'react-redux'; import init from '../init'; -import { getComponent } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; -class CustomMeasuresAppContainer extends React.PureComponent { +export default class CustomMeasuresAppContainer extends React.PureComponent { componentDidMount() { init(this.refs.container, this.props.component); } @@ -38,9 +36,3 @@ class CustomMeasuresAppContainer extends React.PureComponent { ); } } - -const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id) -}); - -export default connect(mapStateToProps)(CustomMeasuresAppContainer); diff --git a/server/sonar-web/src/main/js/apps/custom-measures/routes.ts b/server/sonar-web/src/main/js/apps/custom-measures/routes.ts index ad092ba1d48..cc0a32c95c1 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/routes.ts +++ b/server/sonar-web/src/main/js/apps/custom-measures/routes.ts @@ -23,7 +23,7 @@ const routes = [ { getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { import('./components/CustomMeasuresAppContainer').then(i => - callback(null, { component: i.default }) + callback(null, { component: (i as any).default }) ); } } diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index 4a0040f2991..509a69dddce 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -63,6 +63,7 @@ import '../styles.css'; /*:: export type Props = { + branch?: { name: string }, component?: Component, currentUser: CurrentUser, fetchIssues: (query: RawQuery) => Promise<*>, @@ -171,6 +172,7 @@ export default class App extends React.PureComponent { const { query } = this.props.location; const { query: prevQuery } = prevProps.location; if ( + prevProps.component !== this.props.component || !areQueriesEqual(prevQuery, query) || areMyIssuesSelected(prevQuery) !== areMyIssuesSelected(query) ) { @@ -306,6 +308,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery(this.state.query), + branch: this.props.branch && this.props.branch.name, id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined, open: issue @@ -324,6 +327,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery(this.state.query), + branch: this.props.branch && this.props.branch.name, id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined, open: undefined @@ -359,6 +363,8 @@ export default class App extends React.PureComponent { : undefined; const parameters = { + branch: this.props.branch && this.props.branch.name, + componentKeys: component && component.key, s: 'FILE_LINE', ...serializeQuery(query), ps: '100', @@ -367,10 +373,6 @@ export default class App extends React.PureComponent { ...additional }; - if (component) { - Object.assign(parameters, { componentKeys: component.key }); - } - // only sorting by CREATION_DATE is allowed, so let's sort DESC if (query.sort) { Object.assign(parameters, { asc: 'false' }); @@ -552,6 +554,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery({ ...this.state.query, ...changes }), + branch: this.props.branch && this.props.branch.name, id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined } @@ -567,6 +570,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...serializeQuery({ ...this.state.query, assigned: true, assignees: [] }), + branch: this.props.branch && this.props.branch.name, id: this.props.component && this.props.component.key, myIssues: myIssues ? 'true' : undefined } @@ -593,6 +597,7 @@ export default class App extends React.PureComponent { pathname: this.props.location.pathname, query: { ...DEFAULT_QUERY, + branch: this.props.branch && this.props.branch.name, id: this.props.component && this.props.component.key, myIssues: this.state.myIssues ? 'true' : undefined } @@ -885,6 +890,8 @@ export default class App extends React.PureComponent { <div> {openIssue ? <IssuesSourceViewer + branch={this.props.branch} + component={component} openIssue={openIssue} loadIssues={this.fetchIssuesForComponent} onIssueChange={this.handleIssueChange} diff --git a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js index 040f0c4718f..2f6b4368a87 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js @@ -24,17 +24,14 @@ import { withRouter } from 'react-router'; import { uniq } from 'lodash'; import App from './App'; import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { getComponent, getCurrentUser } from '../../../store/rootReducer'; +import { getCurrentUser } from '../../../store/rootReducer'; import { getOrganizations } from '../../../api/organizations'; import { receiveOrganizations } from '../../../store/organizations/duck'; import { searchIssues } from '../../../api/issues'; import { parseIssueFromResponse } from '../../../helpers/issues'; /*:: import type { RawQuery } from '../../../helpers/query'; */ -const mapStateToProps = (state, ownProps) => ({ - component: ownProps.location.query.id - ? getComponent(state, ownProps.location.query.id) - : undefined, +const mapStateToProps = state => ({ currentUser: getCurrentUser(state) }); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js index 498f2e377ce..6561fd547fa 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesSourceViewer.js @@ -21,10 +21,13 @@ import React from 'react'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import { scrollToElement } from '../../../helpers/scrolling'; +/*:: import type { Component, } from '../utils'; */ /*:: import type { Issue } from '../../../components/issue/types'; */ /*:: type Props = {| + branch?: { name: string }, + component: Component, loadIssues: (string, number, number) => Promise<*>, onIssueChange: Issue => void, onIssueSelect: string => void, @@ -83,6 +86,7 @@ export default class IssuesSourceViewer extends React.PureComponent { <div ref={node => (this.node = node)}> <SourceViewer aroundLine={openIssue.textRange ? openIssue.textRange.endLine : undefined} + branch={this.props.branch && this.props.branch.name} component={openIssue.component} displayAllIssues={true} highlightedLocations={locations} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js index 6170138afa2..95174567951 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js @@ -215,6 +215,10 @@ export default class CreationDateFacet extends React.PureComponent { renderPredefinedPeriods() { const { component, createdInLast, sinceLeakPeriod } = this.props; + if (component != null && component.branch != null) { + // FIXME handle long-living branches + return null; + } return ( <div className="spacer-top issues-predefined-periods"> <FacetItem 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/projects/components/ProjectTagsSelectorContainer.js b/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js index 2cb4ab4e54a..76e25c9c1f1 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectTagsSelectorContainer.js +++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaTagsSelector.js @@ -19,18 +19,16 @@ */ //@flow import React from 'react'; -import { connect } from 'react-redux'; import { debounce, without } from 'lodash'; import TagsSelector from '../../../components/tags/TagsSelector'; import { searchProjectTags } from '../../../api/components'; -import { setProjectTags } from '../store/actions'; /*:: type Props = { position: {}, project: string, selectedTags: Array<string>, - setProjectTags: (string, Array<string>) => void + setProjectTags: (Array<string>) => void }; */ @@ -42,7 +40,7 @@ type State = { const LIST_SIZE = 10; -class ProjectTagsSelectorContainer extends React.PureComponent { +export default class MetaTagsSelector extends React.PureComponent { /*:: props: Props; */ /*:: state: State; */ @@ -68,11 +66,11 @@ class ProjectTagsSelectorContainer extends React.PureComponent { }; onSelect = (tag /*: string */) => { - this.props.setProjectTags(this.props.project, [...this.props.selectedTags, tag]); + this.props.setProjectTags([...this.props.selectedTags, tag]); }; onUnselect = (tag /*: string */) => { - this.props.setProjectTags(this.props.project, without(this.props.selectedTags, tag)); + this.props.setProjectTags(without(this.props.selectedTags, tag)); }; render() { @@ -89,5 +87,3 @@ class ProjectTagsSelectorContainer extends React.PureComponent { ); } } - -export default connect(null, { setProjectTags })(ProjectTagsSelectorContainer); 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 })); } } ]; diff --git a/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js b/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js index 5aff00dd956..9cd6a47ea96 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js +++ b/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js @@ -18,36 +18,17 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import React from 'react'; -import PropTypes from 'prop-types'; import Helmet from 'react-helmet'; -import { connect } from 'react-redux'; import Header from './Header'; import Form from './Form'; -import { getComponent } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; -class Deletion extends React.PureComponent { - static propTypes = { - component: PropTypes.object - }; - - render() { - if (!this.props.component) { - return null; - } - - return ( - <div className="page page-limited"> - <Helmet title={translate('deletion.page')} /> - <Header component={this.props.component} /> - <Form component={this.props.component} /> - </div> - ); - } +export default function Deletion(props) { + return ( + <div className="page page-limited"> + <Helmet title={translate('deletion.page')} /> + <Header component={props.component} /> + <Form component={props.component} /> + </div> + ); } - -const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id) -}); - -export default connect(mapStateToProps)(Deletion); diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js index 130d67cd4c2..4a340ab6371 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js @@ -35,11 +35,11 @@ import { import { parseError } from '../../code/utils'; import { reloadUpdateKeyPage } from './utils'; import RecentHistory from '../../../app/components/RecentHistory'; -import { getProjectAdminProjectModules, getComponent } from '../../../store/rootReducer'; +import { getProjectAdminProjectModules } from '../../../store/rootReducer'; class Key extends React.PureComponent { static propTypes = { - component: PropTypes.object.isRequired, + component: PropTypes.object, fetchProjectModules: PropTypes.func.isRequired, changeKey: PropTypes.func.isRequired, addGlobalErrorMessage: PropTypes.func.isRequired, @@ -141,7 +141,6 @@ class Key extends React.PureComponent { } const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id), modules: getProjectAdminProjectModules(state, ownProps.location.query.id) }); diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js index fede0404e07..592ae5fc092 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js +++ b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js @@ -25,12 +25,12 @@ import Header from './Header'; import Table from './Table'; import DeletionModal from './views/DeletionModal'; import { fetchProjectLinks, deleteProjectLink, createProjectLink } from '../store/actions'; -import { getProjectAdminProjectLinks, getComponent } from '../../../store/rootReducer'; +import { getProjectAdminProjectLinks } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; class Links extends React.PureComponent { static propTypes = { - component: PropTypes.object.isRequired, + component: PropTypes.object, links: PropTypes.array }; @@ -67,7 +67,6 @@ class Links extends React.PureComponent { } const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id), links: getProjectAdminProjectLinks(state, ownProps.location.query.id) }); diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js b/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js index f07fbed428b..6194c04371a 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js @@ -24,16 +24,12 @@ import { connect } from 'react-redux'; import Header from './Header'; import Form from './Form'; import { fetchProjectGate, setProjectGate } from '../store/actions'; -import { - getProjectAdminAllGates, - getProjectAdminProjectGate, - getComponent -} from '../../../store/rootReducer'; +import { getProjectAdminAllGates, getProjectAdminProjectGate } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; class QualityGate extends React.PureComponent { static propTypes = { - component: PropTypes.object.isRequired, + component: PropTypes.object, allGates: PropTypes.array, gate: PropTypes.object }; @@ -62,7 +58,6 @@ class QualityGate extends React.PureComponent { } const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id), allGates: getProjectAdminAllGates(state), gate: getProjectAdminProjectGate(state, ownProps.location.query.id) }); diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js index 4551f410a25..02ff3c3eac5 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js @@ -27,8 +27,7 @@ import { fetchProjectProfiles, setProjectProfile } from '../store/actions'; import { areThereCustomOrganizations, getProjectAdminAllProfiles, - getProjectAdminProjectProfiles, - getComponent + getProjectAdminProjectProfiles } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; @@ -80,7 +79,6 @@ class QualityProfiles extends React.PureComponent { } const mapStateToProps = (state, ownProps) => ({ - component: getComponent(state, ownProps.location.query.id), customOrganizations: areThereCustomOrganizations(state), allProfiles: getProjectAdminAllProfiles(state), profiles: getProjectAdminProjectProfiles(state, ownProps.location.query.id) diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js index 1d3c9845ecd..de7cde52b7a 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js @@ -19,11 +19,9 @@ */ // @flow import React from 'react'; -import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; +import PropTypes from 'prop-types'; import ProjectActivityApp from './ProjectActivityApp'; import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { getComponent } from '../../../store/rootReducer'; import { getAllTimeMachineData } from '../../../api/time-machine'; import { getMetrics } from '../../../api/metrics'; import * as api from '../../../api/projectActivity'; @@ -45,15 +43,11 @@ import { /*:: type Props = { location: { pathname: string, query: RawQuery }, - project: { + component: { configuration?: { showHistory: boolean }, key: string, leakPeriodDate: string, qualifier: string - }, - router: { - push: ({ pathname: string, query?: RawQuery }) => void, - replace: ({ pathname: string, query?: RawQuery }) => void } }; */ @@ -71,11 +65,15 @@ export type State = { }; */ -class ProjectActivityAppContainer extends React.PureComponent { +export default class ProjectActivityAppContainer extends React.PureComponent { /*:: mounted: boolean; */ /*:: props: Props; */ /*:: state: State; */ + static contextTypes = { + router: PropTypes.object + }; + constructor(props /*: Props */) { super(props); this.state = { @@ -93,7 +91,7 @@ class ProjectActivityAppContainer extends React.PureComponent { if (isCustomGraph(newQuery.graph)) { newQuery.customMetrics = getCustomGraph(); } - this.props.router.replace({ + this.context.router.replace({ pathname: props.location.pathname, query: serializeUrlQuery(newQuery) }); @@ -182,7 +180,7 @@ class ProjectActivityAppContainer extends React.PureComponent { if (metrics.length <= 0) { return Promise.resolve([]); } - return getAllTimeMachineData(this.props.project.key, metrics).then( + return getAllTimeMachineData(this.props.component.key, metrics).then( ({ measures }) => measures.map(measure => ({ metric: measure.metric, @@ -279,11 +277,11 @@ class ProjectActivityAppContainer extends React.PureComponent { ...this.state.query, ...newQuery }); - this.props.router.push({ + this.context.router.push({ pathname: this.props.location.pathname, query: { ...query, - id: this.props.project.key + id: this.props.component.key } }); }; @@ -319,16 +317,10 @@ class ProjectActivityAppContainer extends React.PureComponent { initializing={!this.state.initialized} metrics={this.state.metrics} measuresHistory={this.state.measuresHistory} - project={this.props.project} + project={this.props.component} query={this.state.query} updateQuery={this.updateQuery} /> ); } } - -const mapStateToProps = (state, ownProps) => ({ - project: getComponent(state, ownProps.location.query.id) -}); - -export default connect(mapStateToProps)(withRouter(ProjectActivityAppContainer)); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.ts b/server/sonar-web/src/main/js/apps/projectActivity/routes.ts index 47d7bda3dae..8641ea7391c 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/routes.ts +++ b/server/sonar-web/src/main/js/apps/projectActivity/routes.ts @@ -23,7 +23,7 @@ const routes = [ { getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { import('./components/ProjectActivityAppContainer').then(i => - callback(null, { component: i.default }) + callback(null, { component: (i as any).default }) ); } } diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js index 8e80ca6cb6f..cbc69d53cfa 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.js @@ -20,12 +20,9 @@ import { connect } from 'react-redux'; import App from './App'; import { fetchSettings } from '../store/actions'; -import { getComponent, getSettingsAppDefaultCategory } from '../../../store/rootReducer'; +import { getSettingsAppDefaultCategory } from '../../../store/rootReducer'; -const mapStateToProps = (state, ownProps) => ({ - component: ownProps.location.query.id - ? getComponent(state, ownProps.location.query.id) - : undefined, +const mapStateToProps = state => ({ defaultCategory: getSettingsAppDefaultCategory(state) }); |