--- /dev/null
+/*
+ * 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 { getJSON } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
+
+export function getBranches(project: string): Promise<any> {
+ return getJSON('/api/project_branches/list', { project }).then(r => r.branches, throwGlobalError);
+}
+
+export function getBranch(project: string, branch: string): Promise<any> {
+ return getJSON('/api/project_branches/show', { component: project, branch }).then(
+ r => r.branch,
+ throwGlobalError
+ );
+}
return getComponentTree('leaves', componentKey, metrics, additional);
}
-export function getComponent(componentKey: string, metrics: string[] = []): Promise<any> {
- const data = { componentKey, metricKeys: metrics.join(',') };
+export function getComponent(
+ componentKey: string,
+ metrics: string[] = [],
+ branch?: string
+): Promise<any> {
+ const data = { branch, componentKey, metricKeys: metrics.join(',') };
return getJSON('/api/measures/component', data).then(r => r.component);
}
return getJSON('/api/components/tree', { ...options, component });
}
-export function getComponentShow(component: string): Promise<any> {
- return getJSON('/api/components/show', { component });
+export function getComponentShow(component: string, branch?: string): Promise<any> {
+ return getJSON('/api/components/show', { component, branch });
}
export function getParents(component: string): Promise<any> {
return getComponentShow(component).then(r => r.ancestors);
}
-export function getBreadcrumbs(component: string): Promise<any> {
- return getComponentShow(component).then(r => {
+export function getBreadcrumbs(component: string, branch?: string): Promise<any> {
+ return getComponentShow(component, branch).then(r => {
const reversedAncestors = [...r.ancestors].reverse();
return [...reversedAncestors, r.component];
});
}
-export function getComponentData(component: string): Promise<any> {
- return getComponentShow(component).then(r => r.component);
+export function getComponentData(component: string, branch?: string): Promise<any> {
+ return getComponentShow(component, branch).then(r => r.component);
}
export function getMyProjects(data: RequestData): Promise<any> {
return getJSON('/api/components/suggestions', data);
}
-export function getComponentForSourceViewer(component: string): Promise<any> {
- return getJSON('/api/components/app', { component });
+export function getComponentForSourceViewer(component: string, branch?: string): Promise<any> {
+ return getJSON('/api/components/app', { component, branch });
}
-export function getSources(component: string, from?: number, to?: number): Promise<any> {
- const data: RequestData = { key: component };
+export function getSources(
+ component: string,
+ from?: number,
+ to?: number,
+ branch?: string
+): Promise<any> {
+ const data: RequestData = { key: component, branch };
if (from) {
Object.assign(data, { from });
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
export function getGlobalNavigation(): Promise<any> {
return getJSON('/api/navigation/global');
}
-export function getComponentNavigation(componentKey: string): Promise<any> {
- return getJSON('/api/navigation/component', { componentKey });
+export function getComponentNavigation(componentKey: string, branch?: string): Promise<any> {
+ return getJSON('/api/navigation/component', { componentKey, branch }).catch(throwGlobalError);
}
export function getSettingsNavigation(): Promise<any> {
- return getJSON('/api/navigation/settings');
+ return getJSON('/api/navigation/settings').catch(throwGlobalError);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
-import { connect } from 'react-redux';
-import { getComponent } from '../../store/rootReducer';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
-class ProjectAdminContainer extends React.PureComponent {
+export default class ProjectAdminContainer extends React.PureComponent {
/*::
props: {
- project: {
+ component: {
configuration?: {
showSettings: boolean
}
}
isProjectAdmin() {
- const { configuration } = this.props.project;
+ const { configuration } = this.props.component;
return configuration != null && configuration.showSettings;
}
return null;
}
- return this.props.children;
+ return React.cloneElement(this.props.children, {
+ component: this.props.component
+ });
}
}
-
-const mapStateToProps = (state, ownProps) => ({
- project: getComponent(state, ownProps.location.query.id)
-});
-
-export default connect(mapStateToProps)(ProjectAdminContainer);
+++ /dev/null
-/*
- * 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 { connect } from 'react-redux';
-import ComponentNav from './nav/component/ComponentNav';
-import { fetchProject } from '../../store/rootActions';
-import { getComponent } from '../../store/rootReducer';
-import { addGlobalErrorMessage } from '../../store/globalMessages/duck';
-import { receiveComponents } from '../../store/components/actions';
-import { parseError } from '../../apps/code/utils';
-import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
-
-class ProjectContainer extends React.PureComponent {
- /*::
- props: {
- addGlobalErrorMessage: (message: string) => void,
- children?: React.Element<*>,
- location: {
- query: { id: string }
- },
- project?: {
- configuration: {},
- name: string,
- qualifier: string
- },
- fetchProject: string => Promise<*>,
- receiveComponents: (Array<*>) => void
- };
- */
-
- componentDidMount() {
- this.fetchProject();
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.location.query.id !== this.props.location.query.id) {
- this.fetchProject();
- }
- }
-
- fetchProject() {
- this.props.fetchProject(this.props.location.query.id).catch(e => {
- if (e.response && e.response.status === 403) {
- handleRequiredAuthorization();
- } else {
- parseError(e).then(message => this.props.addGlobalErrorMessage(message));
- }
- });
- }
-
- handleProjectChange = (changes /*: {} */) => {
- this.props.receiveComponents([{ ...this.props.project, ...changes }]);
- };
-
- render() {
- const { project } = this.props;
-
- // check `breadcrumbs` to be sure that /api/navigation/component has been already called
- if (!project || project.breadcrumbs == null) {
- return null;
- }
-
- const isFile = ['FIL', 'UTS'].includes(project.qualifier);
- const configuration = project.configuration || {};
-
- return (
- <div>
- {!isFile &&
- <ComponentNav component={project} conf={configuration} location={this.props.location} />}
- {/* $FlowFixMe */}
- {React.cloneElement(this.props.children, {
- component: project,
- onComponentChange: this.handleProjectChange
- })}
- </div>
- );
- }
-}
-
-const mapStateToProps = (state, ownProps) => ({
- project: getComponent(state, ownProps.location.query.id)
-});
-
-const mapDispatchToProps = { addGlobalErrorMessage, fetchProject, receiveComponents };
-
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectContainer);
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import ComponentNav from './nav/component/ComponentNav';
+import { Branch, Component } from '../types';
+import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
+import { getBranch } from '../../api/branches';
+import { getComponentData } from '../../api/components';
+import { getComponentNavigation } from '../../api/nav';
+import { MAIN_BRANCH } from '../../helpers/branches';
+
+interface Props {
+ children: any;
+ location: {
+ query: { branch?: string; id: string };
+ };
+}
+
+interface State {
+ branch: Branch | null;
+ loading: boolean;
+ component: Component | null;
+}
+
+export default class ProjectContainer extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = { branch: null, loading: true, component: null };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchProject();
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ // if the current branch has been changed, reset `branch` in state
+ // it prevents unwanted redirect in `overview/App#componentDidMount`
+ if (nextProps.location.query.branch !== this.props.location.query.branch) {
+ this.setState({ branch: null });
+ }
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (
+ prevProps.location.query.id !== this.props.location.query.id ||
+ prevProps.location.query.branch !== this.props.location.query.branch
+ ) {
+ this.fetchProject();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ addQualifier = (component: Component) => ({
+ ...component,
+ qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier
+ });
+
+ fetchProject() {
+ const { branch, id } = this.props.location.query;
+ this.setState({ loading: true });
+ Promise.all([
+ getComponentNavigation(id),
+ getComponentData(id, branch),
+ branch && getBranch(id, branch)
+ ]).then(
+ ([nav, data, branch]) => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ branch: branch || MAIN_BRANCH,
+ component: this.addQualifier({ ...nav, ...data })
+ });
+ }
+ },
+ error => {
+ if (this.mounted) {
+ if (error.response && error.response.status === 403) {
+ handleRequiredAuthorization();
+ } else {
+ this.setState({ loading: false });
+ }
+ }
+ }
+ );
+ }
+
+ handleProjectChange = (changes: {}) => {
+ if (this.mounted) {
+ this.setState(state => ({ component: { ...state.component, ...changes } }));
+ }
+ };
+
+ render() {
+ const { branch, component } = this.state;
+
+ if (!component || !branch) {
+ return null;
+ }
+
+ const isFile = ['FIL', 'UTS'].includes(component.qualifier);
+ const configuration = component.configuration || {};
+
+ return (
+ <div>
+ {!isFile &&
+ <ComponentNav
+ branch={branch}
+ component={component}
+ conf={configuration}
+ location={this.props.location}
+ />}
+ {React.cloneElement(this.props.children, {
+ branch,
+ component: component,
+ onComponentChange: this.handleProjectChange
+ })}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ProjectContainer from '../ProjectContainer';
+
+it('changes component', () => {
+ const Inner = () => <div />;
+
+ const wrapper = shallow(
+ <ProjectContainer location={{ query: { id: 'foo' } }}>
+ <Inner />
+ </ProjectContainer>
+ );
+ (wrapper.instance() as ProjectContainer).mounted = true;
+ wrapper.setState({
+ branch: { isMain: true },
+ component: { qualifier: 'TRK', visibility: 'public' },
+ loading: false
+ });
+
+ (wrapper.find(Inner).prop('onComponentChange') as Function)({ visibility: 'private' });
+ expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
+});
+++ /dev/null
-/*
- * 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 { Link } from 'react-router';
-
-export default class ExtensionNotFound extends React.PureComponent {
- componentDidMount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.add('dashboard-page');
- }
- }
-
- componentWillUnmount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.remove('dashboard-page');
- }
- }
-
- render() {
- return (
- <div id="bd" className="page-wrapper-simple">
- <div id="nonav" className="page-simple">
- <h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2>
- <p className="spacer-bottom">
- You may have mistyped the address or the page may have moved.
- </p>
- <p>
- <Link to="/">Go back to the homepage</Link>
- </p>
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import { Link } from 'react-router';
+
+export default class ExtensionNotFound extends React.PureComponent {
+ componentDidMount() {
+ const html = document.querySelector('html');
+ if (html) {
+ html.classList.add('dashboard-page');
+ }
+ }
+
+ componentWillUnmount() {
+ const html = document.querySelector('html');
+ if (html) {
+ html.classList.remove('dashboard-page');
+ }
+ }
+
+ render() {
+ return (
+ <div id="bd" className="page-wrapper-simple">
+ <div id="nonav" className="page-simple">
+ <h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2>
+ <p className="spacer-bottom">
+ You may have mistyped the address or the page may have moved.
+ </p>
+ <p>
+ <Link to="/">Go back to the homepage</Link>
+ </p>
+ </div>
+ </div>
+ );
+ }
+}
import { connect } from 'react-redux';
import Extension from './Extension';
import ExtensionNotFound from './ExtensionNotFound';
-import { getComponent } from '../../../store/rootReducer';
import { addGlobalErrorMessage } from '../../../store/globalMessages/duck';
/*::
: <ExtensionNotFound />;
}
-const mapStateToProps = (state, ownProps /*: Props */) => ({
- component: getComponent(state, ownProps.location.query.id)
-});
-
const mapDispatchToProps = { onFail: addGlobalErrorMessage };
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectAdminPageExtension);
+export default connect(null, mapDispatchToProps)(ProjectAdminPageExtension);
+++ /dev/null
-/*
- * 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 { connect } from 'react-redux';
-import Extension from './Extension';
-import ExtensionNotFound from './ExtensionNotFound';
-import { getComponent } from '../../../store/rootReducer';
-import { addGlobalErrorMessage } from '../../../store/globalMessages/duck';
-
-/*::
-type Props = {
- component: {
- extensions: Array<{ key: string }>
- },
- location: { query: { id: string } },
- params: {
- extensionKey: string,
- pluginKey: string
- }
-};
-*/
-
-function ProjectPageExtension(props /*: Props */) {
- const { extensionKey, pluginKey } = props.params;
- const { component } = props;
- const extension = component.extensions.find(p => p.key === `${pluginKey}/${extensionKey}`);
- return extension
- ? <Extension extension={extension} options={{ component }} />
- : <ExtensionNotFound />;
-}
-
-const mapStateToProps = (state, ownProps /*: Props */) => ({
- component: getComponent(state, ownProps.location.query.id)
-});
-
-const mapDispatchToProps = { onFail: addGlobalErrorMessage };
-
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectPageExtension);
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import Extension from './Extension';
+import ExtensionNotFound from './ExtensionNotFound';
+import { Component } from '../../types';
+
+interface Props {
+ component: Component;
+ location: { query: { id: string } };
+ params: {
+ extensionKey: string;
+ pluginKey: string;
+ };
+}
+
+export default function ProjectPageExtension(props: Props) {
+ const { extensionKey, pluginKey } = props.params;
+ const { component } = props;
+ const extension =
+ component.extensions &&
+ component.extensions.find(p => p.key === `${pluginKey}/${extensionKey}`);
+ return extension
+ ? <Extension extension={extension} options={{ component }} />
+ : <ExtensionNotFound />;
+}
--- /dev/null
+.branch-status {
+}
+
+.branch-status-indicator {
+ display: block;
+ width: 8px;
+ height: 8px;
+ border-radius: 8px;
+ margin: 4px 0;
+}
+
+.branch-status-indicator.is-failed {
+ background-color: #d4333f;
+}
+
+.branch-status-indicator.is-passed {
+ background-color: #00aa00;
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import * as classNames from 'classnames';
+import { Branch } from '../../../types';
+import BugIcon from '../../../../components/icons-components/BugIcon';
+import CodeSmellIcon from '../../../../components/icons-components/CodeSmellIcon';
+import VulnerabilityIcon from '../../../../components/icons-components/VulnerabilityIcon';
+import { isShortLivingBranch } from '../../../../helpers/branches';
+import './BranchStatus.css';
+
+interface Props {
+ branch: Branch;
+ concise?: boolean;
+}
+
+export default function BranchStatus({ branch, concise = false }: Props) {
+ // TODO handle long-living branches
+ if (!isShortLivingBranch(branch)) {
+ return null;
+ }
+
+ const totalIssues = branch.status.bugs + branch.status.vulnerabilities + branch.status.codeSmells;
+
+ return (
+ <ul className="list-inline branch-status">
+ <li>
+ <i
+ className={classNames('branch-status-indicator', {
+ 'is-failed': totalIssues > 0,
+ 'is-passed': totalIssues === 0
+ })}
+ />
+ </li>
+ {concise &&
+ <li>
+ {totalIssues}
+ </li>}
+ {!concise &&
+ <li>
+ {branch.status.bugs}
+ <BugIcon className="little-spacer-left" />
+ </li>}
+ {!concise &&
+ <li>
+ {branch.status.vulnerabilities}
+ <VulnerabilityIcon className="little-spacer-left" />
+ </li>}
+ {!concise &&
+ <li>
+ {branch.status.codeSmells}
+ <CodeSmellIcon className="little-spacer-left" />
+ </li>}
+ </ul>
+ );
+}
padding-top: 5px;
box-sizing: border-box;
}
+
+.navbar-context-branches {
+ float: left;
+ padding: 8px 0 6px;
+ margin-left: 16px;
+ line-height: 16px;
+}
+
+.navbar-context-meta-branch {
+ margin-top: 20px;
+ line-height: 16px;
+}
+
+.navbar-context-meta-branch-menu-item {
+ display: flex !important;
+ justify-content: space-between;
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import ComponentNavFavorite from './ComponentNavFavorite';
-import ComponentNavBreadcrumbs from './ComponentNavBreadcrumbs';
-import ComponentNavMeta from './ComponentNavMeta';
-import ComponentNavMenu from './ComponentNavMenu';
-import RecentHistory from '../../RecentHistory';
-import ContextNavBar from '../../../../components/nav/ContextNavBar';
-import { getTasksForComponent } from '../../../../api/ce';
-import { STATUSES } from '../../../../apps/background-tasks/constants';
-import './ComponentNav.css';
-
-export default class ComponentNav extends React.PureComponent {
- componentDidMount() {
- this.mounted = true;
-
- this.loadStatus();
- this.populateRecentHistory();
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- loadStatus = () => {
- getTasksForComponent(this.props.component.key).then(r => {
- if (this.mounted) {
- this.setState({
- isPending: r.queue.some(task => task.status === STATUSES.PENDING),
- isInProgress: r.queue.some(task => task.status === STATUSES.IN_PROGRESS),
- isFailed: r.current && r.current.status === STATUSES.FAILED,
- incremental: r.current && r.current.incremental
- });
- }
- });
- };
-
- populateRecentHistory = () => {
- const { breadcrumbs } = this.props.component;
- const { qualifier } = breadcrumbs[breadcrumbs.length - 1];
- if (['TRK', 'VW', 'APP', 'DEV'].indexOf(qualifier) !== -1) {
- RecentHistory.add(
- this.props.component.key,
- this.props.component.name,
- qualifier.toLowerCase(),
- this.props.component.organization
- );
- }
- };
-
- render() {
- return (
- <ContextNavBar id="context-navigation" height={65}>
- <ComponentNavFavorite
- component={this.props.component.key}
- favorite={this.props.component.isFavorite}
- />
-
- <ComponentNavBreadcrumbs
- component={this.props.component}
- breadcrumbs={this.props.component.breadcrumbs}
- />
-
- <ComponentNavMeta
- {...this.props}
- {...this.state}
- version={this.props.component.version}
- analysisDate={this.props.component.analysisDate}
- />
-
- <ComponentNavMenu
- component={this.props.component}
- conf={this.props.conf}
- location={this.props.location}
- />
- </ContextNavBar>
- );
- }
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import ComponentNavFavorite from './ComponentNavFavorite';
+import ComponentNavBreadcrumbs from './ComponentNavBreadcrumbs';
+import ComponentNavMeta from './ComponentNavMeta';
+import ComponentNavMenu from './ComponentNavMenu';
+import ComponentNavBranch from './ComponentNavBranch';
+import RecentHistory from '../../RecentHistory';
+import { Branch, Component, ComponentConfiguration } from '../../../types';
+import ContextNavBar from '../../../../components/nav/ContextNavBar';
+import { getTasksForComponent } from '../../../../api/ce';
+import { STATUSES } from '../../../../apps/background-tasks/constants';
+import './ComponentNav.css';
+
+interface Props {
+ branch: Branch;
+ component: Component;
+ conf: ComponentConfiguration;
+ location: {};
+}
+
+interface State {
+ incremental?: boolean;
+ isFailed?: boolean;
+ isInProgress?: boolean;
+ isPending?: boolean;
+}
+
+export default class ComponentNav extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ state: State = {};
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadStatus();
+ this.populateRecentHistory();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadStatus = () => {
+ getTasksForComponent(this.props.component.key).then((r: any) => {
+ if (this.mounted) {
+ this.setState({
+ isPending: r.queue.some((task: any) => task.status === STATUSES.PENDING),
+ isInProgress: r.queue.some((task: any) => task.status === STATUSES.IN_PROGRESS),
+ isFailed: r.current && r.current.status === STATUSES.FAILED,
+ incremental: r.current && r.current.incremental
+ });
+ }
+ });
+ };
+
+ populateRecentHistory = () => {
+ const { breadcrumbs } = this.props.component;
+ const { qualifier } = breadcrumbs[breadcrumbs.length - 1];
+ if (['TRK', 'VW', 'APP', 'DEV'].indexOf(qualifier) !== -1) {
+ RecentHistory.add(
+ this.props.component.key,
+ this.props.component.name,
+ qualifier.toLowerCase(),
+ this.props.component.organization
+ );
+ }
+ };
+
+ render() {
+ return (
+ <ContextNavBar id="context-navigation" height={65}>
+ <ComponentNavFavorite
+ component={this.props.component.key}
+ favorite={this.props.component.isFavorite}
+ />
+
+ <ComponentNavBreadcrumbs
+ component={this.props.component}
+ breadcrumbs={this.props.component.breadcrumbs}
+ />
+
+ <ComponentNavBranch branch={this.props.branch} project={this.props.component} />
+
+ <ComponentNavMeta
+ branch={this.props.branch}
+ component={this.props.component}
+ conf={this.props.conf}
+ incremental={this.state.incremental}
+ />
+
+ <ComponentNavMenu
+ branch={this.props.branch}
+ component={this.props.component}
+ conf={this.props.conf}
+ />
+ </ContextNavBar>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import * as classNames from 'classnames';
+import ComponentNavBranchesMenu from './ComponentNavBranchesMenu';
+import { Branch, Component } from '../../../types';
+import BranchIcon from '../../../../components/icons-components/BranchIcon';
+import { getBranchDisplayName } from '../../../../helpers/branches';
+
+interface Props {
+ branch: Branch;
+ project: Component;
+}
+
+interface State {
+ open: boolean;
+}
+
+export default class ComponentNavBranch extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { open: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (nextProps.project !== this.props.project || nextProps.branch !== this.props.branch) {
+ this.setState({ open: false });
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleClick = (event: React.SyntheticEvent<HTMLElement>) => {
+ event.preventDefault();
+ event.stopPropagation();
+ event.currentTarget.blur();
+ this.setState({ open: true });
+ };
+
+ closeDropdown = () => {
+ if (this.mounted) {
+ this.setState({ open: false });
+ }
+ };
+
+ render() {
+ return (
+ <div className={classNames('navbar-context-branches', 'dropdown', { open: this.state.open })}>
+ <a className="link-base-color link-no-underline" href="#" onClick={this.handleClick}>
+ <BranchIcon className="little-spacer-right" />
+ {getBranchDisplayName(this.props.branch)}
+ <i className="icon-dropdown little-spacer-left" />
+ </a>
+ {this.state.open &&
+ <ComponentNavBranchesMenu
+ branch={this.props.branch}
+ onClose={this.closeDropdown}
+ project={this.props.project}
+ />}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import * as PropTypes from 'prop-types';
+import { sortBy } from 'lodash';
+import ComponentNavBranchesMenuItem from './ComponentNavBranchesMenuItem';
+import { Branch, Component } from '../../../types';
+import { getBranches } from '../../../../api/branches';
+import { isShortLivingBranch, getBranchDisplayName } from '../../../../helpers/branches';
+import { translate } from '../../../../helpers/l10n';
+import { getProjectBranchUrl } from '../../../../helpers/urls';
+
+interface Props {
+ branch: Branch;
+ onClose: () => void;
+ project: Component;
+}
+
+interface State {
+ branches: Branch[];
+ loading: boolean;
+ query: string;
+ selected: string | null;
+}
+
+export default class ComponentNavBranchesMenu extends React.PureComponent<Props, State> {
+ private mounted: boolean;
+ private node: HTMLElement | null;
+
+ static contextTypes = {
+ router: PropTypes.object
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ branches: [],
+ loading: true,
+ query: '',
+ selected: null
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchBranches();
+ window.addEventListener('click', this.handleClickOutside);
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ window.removeEventListener('click', this.handleClickOutside);
+ }
+
+ fetchBranches = () => {
+ this.setState({ loading: true });
+ getBranches(this.props.project.key).then(
+ (branches: Branch[]) => {
+ if (this.mounted) {
+ this.setState({ branches: this.sortBranches(branches), loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ sortBranches = (branches: Branch[]): Branch[] =>
+ sortBy(
+ branches,
+ branch => !branch.isMain, // main branch first
+ branch => !isShortLivingBranch(branch), // then short-living branches
+ branch => getBranchDisplayName(branch) // then by name
+ );
+
+ getFilteredBranches = () =>
+ this.state.branches.filter(branch =>
+ getBranchDisplayName(branch).toLowerCase().includes(this.state.query.toLowerCase())
+ );
+
+ handleClickOutside = (event: Event) => {
+ if (!this.node || !this.node.contains(event.target as HTMLElement)) {
+ this.props.onClose();
+ }
+ };
+
+ handleSearchChange = (event: React.SyntheticEvent<HTMLInputElement>) =>
+ this.setState({ query: event.currentTarget.value, selected: null });
+
+ handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
+ switch (event.keyCode) {
+ case 13:
+ event.preventDefault();
+ this.openSelected();
+ return;
+ case 27:
+ event.preventDefault();
+ this.props.onClose();
+ return;
+ case 38:
+ event.preventDefault();
+ this.selectPrevious();
+ return;
+ case 40:
+ event.preventDefault();
+ this.selectNext();
+ return;
+ }
+ };
+
+ openSelected = () => {
+ const selected = this.getSelected();
+ const branch = this.getFilteredBranches().find(
+ branch => getBranchDisplayName(branch) === selected
+ );
+ if (branch) {
+ this.context.router.push(this.getProjectBranchUrl(branch));
+ }
+ };
+
+ selectPrevious = () => {
+ const selected = this.getSelected();
+ const branches = this.getFilteredBranches();
+ const index = branches.findIndex(branch => getBranchDisplayName(branch) === selected);
+ if (index > 0) {
+ this.setState({ selected: getBranchDisplayName(branches[index - 1]) });
+ }
+ };
+
+ selectNext = () => {
+ const selected = this.getSelected();
+ const branches = this.getFilteredBranches();
+ const index = branches.findIndex(branch => getBranchDisplayName(branch) === selected);
+ if (index >= 0 && index < branches.length - 1) {
+ this.setState({ selected: getBranchDisplayName(branches[index + 1]) });
+ }
+ };
+
+ handleSelect = (branch: Branch) => {
+ this.setState({ selected: getBranchDisplayName(branch) });
+ };
+
+ getSelected = () => {
+ const branches = this.getFilteredBranches();
+ return this.state.selected || (branches.length > 0 && getBranchDisplayName(branches[0]));
+ };
+
+ getProjectBranchUrl = (branch: Branch) => getProjectBranchUrl(this.props.project.key, branch);
+
+ isSelected = (branch: Branch) => getBranchDisplayName(branch) === this.getSelected();
+
+ renderSearch = () =>
+ <div className="search-box menu-search">
+ <button className="search-box-submit button-clean">
+ <i className="icon-search-new" />
+ </button>
+ <input
+ autoFocus={true}
+ className="search-box-input"
+ onChange={this.handleSearchChange}
+ onKeyDown={this.handleKeyDown}
+ placeholder={translate('search_verb')}
+ type="search"
+ value={this.state.query}
+ />
+ </div>;
+
+ renderBranchesList = () => {
+ const branches = this.getFilteredBranches();
+
+ const selected = this.getSelected();
+
+ return branches.length > 0
+ ? <ul className="menu">
+ {branches.map(branch =>
+ <ComponentNavBranchesMenuItem
+ branch={branch}
+ component={this.props.project}
+ key={getBranchDisplayName(branch)}
+ onSelect={this.handleSelect}
+ selected={getBranchDisplayName(branch) === selected}
+ />
+ )}
+ </ul>
+ : <div className="menu-message note">
+ {translate('no_results')}
+ </div>;
+ };
+
+ render() {
+ return (
+ <div className="dropdown-menu dropdown-menu-shadow" ref={node => (this.node = node)}>
+ {this.state.loading
+ ? <i className="spinner" />
+ : <div>
+ {this.renderSearch()}
+ {this.renderBranchesList()}
+ </div>}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import { Link } from 'react-router';
+import * as classNames from 'classnames';
+import BranchStatus from './BranchStatus';
+import { Branch, Component } from '../../../types';
+import BranchIcon from '../../../../components/icons-components/BranchIcon';
+import { isShortLivingBranch, getBranchDisplayName } from '../../../../helpers/branches';
+import { getProjectBranchUrl } from '../../../../helpers/urls';
+
+interface Props {
+ branch: Branch;
+ component: Component;
+ onSelect: (branch: Branch) => void;
+ selected: boolean;
+}
+
+export default function ComponentNavBranchesMenuItem({ branch, ...props }: Props) {
+ const displayName = getBranchDisplayName(branch);
+
+ const handleMouseEnter = () => {
+ props.onSelect(branch);
+ };
+
+ return (
+ <li key={displayName} onMouseEnter={handleMouseEnter}>
+ <Link
+ className={classNames('navbar-context-meta-branch-menu-item', {
+ active: props.selected
+ })}
+ to={getProjectBranchUrl(props.component.key, branch)}>
+ <div>
+ <BranchIcon
+ className={classNames('little-spacer-right', {
+ 'big-spacer-left': isShortLivingBranch(branch)
+ })}
+ />
+ {displayName}
+ </div>
+ <div className="big-spacer-left note">
+ <BranchStatus branch={branch} concise={true} />
+ </div>
+ </Link>
+ </li>
+ );
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import PropTypes from 'prop-types';
-import { Link } from 'react-router';
-import classNames from 'classnames';
-import NavBarTabs from '../../../../components/nav/NavBarTabs';
-import { translate } from '../../../../helpers/l10n';
-
-const SETTINGS_URLS = [
- '/project/admin',
- '/project/settings',
- '/project/quality_profiles',
- '/project/quality_gate',
- '/custom_measures',
- '/project/links',
- '/project_roles',
- '/project/history',
- 'background_tasks',
- '/project/key',
- '/project/deletion'
-];
-
-export default class ComponentNavMenu extends React.PureComponent {
- static propTypes = {
- component: PropTypes.object.isRequired,
- conf: PropTypes.object.isRequired
- };
-
- isProject() {
- return this.props.component.qualifier === 'TRK';
- }
-
- isDeveloper() {
- return this.props.component.qualifier === 'DEV';
- }
-
- isView() {
- const { qualifier } = this.props.component;
- return qualifier === 'VW' || qualifier === 'SVW';
- }
-
- isApplication() {
- return this.props.component.qualifier === 'APP';
- }
-
- renderDashboardLink() {
- const pathname = this.isView() ? '/portfolio' : '/dashboard';
- return (
- <li>
- <Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active">
- {translate('overview.page')}
- </Link>
- </li>
- );
- }
-
- renderCodeLink() {
- if (this.isDeveloper()) {
- return null;
- }
-
- return (
- <li>
- <Link
- to={{ pathname: '/code', query: { id: this.props.component.key } }}
- activeClassName="active">
- {this.isView() || this.isApplication()
- ? translate('view_projects.page')
- : translate('code.page')}
- </Link>
- </li>
- );
- }
-
- renderActivityLink() {
- if (!this.isProject() && !this.isApplication()) {
- return null;
- }
-
- return (
- <li>
- <Link
- to={{ pathname: '/project/activity', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('project_activity.page')}
- </Link>
- </li>
- );
- }
-
- renderIssuesLink() {
- return (
- <li>
- <Link
- to={{
- pathname: '/project/issues',
- query: { id: this.props.component.key, resolved: 'false' }
- }}
- activeClassName="active">
- {translate('issues.page')}
- </Link>
- </li>
- );
- }
-
- renderComponentMeasuresLink() {
- return (
- <li>
- <Link
- to={{ pathname: '/component_measures', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('layout.measures')}
- </Link>
- </li>
- );
- }
-
- renderAdministration() {
- const adminLinks = this.renderAdministrationLinks();
- if (!adminLinks.some(link => link != null)) {
- return null;
- }
-
- const isSettingsActive = SETTINGS_URLS.some(url => window.location.href.indexOf(url) !== -1);
- return (
- <li className="dropdown">
- <a
- className={classNames('dropdown-toggle', 'is-admin', { active: isSettingsActive })}
- id="component-navigation-admin"
- data-toggle="dropdown"
- href="#">
- {translate('layout.settings')}
- <i className="icon-dropdown" />
- </a>
- <ul className="dropdown-menu">
- {adminLinks}
- </ul>
- </li>
- );
- }
-
- renderAdministrationLinks() {
- return [
- this.renderSettingsLink(),
- this.renderProfilesLink(),
- this.renderQualityGateLink(),
- this.renderCustomMeasuresLink(),
- this.renderLinksLink(),
- this.renderPermissionsLink(),
- this.renderBackgroundTasksLink(),
- this.renderUpdateKeyLink(),
- ...this.renderAdminExtensions(),
- this.renderDeletionLink()
- ];
- }
-
- renderSettingsLink() {
- if (!this.props.conf.showSettings || this.isApplication() || this.isView()) {
- return null;
- }
- return (
- <li key="settings">
- <Link
- to={{ pathname: '/project/settings', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('project_settings.page')}
- </Link>
- </li>
- );
- }
-
- renderProfilesLink() {
- if (!this.props.conf.showQualityProfiles) {
- return null;
- }
- return (
- <li key="profiles">
- <Link
- to={{ pathname: '/project/quality_profiles', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('project_quality_profiles.page')}
- </Link>
- </li>
- );
- }
-
- renderQualityGateLink() {
- if (!this.props.conf.showQualityGates) {
- return null;
- }
- return (
- <li key="quality_gate">
- <Link
- to={{ pathname: '/project/quality_gate', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('project_quality_gate.page')}
- </Link>
- </li>
- );
- }
-
- renderCustomMeasuresLink() {
- if (!this.props.conf.showManualMeasures) {
- return null;
- }
- return (
- <li key="custom_measures">
- <Link
- to={{ pathname: '/custom_measures', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('custom_measures.page')}
- </Link>
- </li>
- );
- }
-
- renderLinksLink() {
- if (!this.props.conf.showLinks) {
- return null;
- }
- return (
- <li key="links">
- <Link
- to={{ pathname: '/project/links', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('project_links.page')}
- </Link>
- </li>
- );
- }
-
- renderPermissionsLink() {
- if (!this.props.conf.showPermissions) {
- return null;
- }
- return (
- <li key="permissions">
- <Link
- to={{ pathname: '/project_roles', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('permissions.page')}
- </Link>
- </li>
- );
- }
-
- renderBackgroundTasksLink() {
- if (!this.props.conf.showBackgroundTasks) {
- return null;
- }
- return (
- <li key="background_tasks">
- <Link
- to={{ pathname: '/project/background_tasks', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('background_tasks.page')}
- </Link>
- </li>
- );
- }
-
- renderUpdateKeyLink() {
- if (!this.props.conf.showUpdateKey) {
- return null;
- }
- return (
- <li key="update_key">
- <Link
- to={{ pathname: '/project/key', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('update_key.page')}
- </Link>
- </li>
- );
- }
-
- renderDeletionLink() {
- const { qualifier } = this.props.component;
-
- if (!this.props.conf.showSettings) {
- return null;
- }
-
- if (qualifier !== 'TRK' && qualifier !== 'VW' && qualifier !== 'APP') {
- return null;
- }
-
- return (
- <li key="project_delete">
- <Link
- to={{ pathname: '/project/deletion', query: { id: this.props.component.key } }}
- activeClassName="active">
- {translate('deletion.page')}
- </Link>
- </li>
- );
- }
-
- renderExtension = ({ key, name }, isAdmin) => {
- const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`;
- return (
- <li key={key}>
- <Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active">
- {name}
- </Link>
- </li>
- );
- };
-
- renderAdminExtensions() {
- const extensions = this.props.conf.extensions || [];
- return extensions.map(e => this.renderExtension(e, true));
- }
-
- renderExtensions() {
- const extensions = this.props.component.extensions || [];
- const withoutGovernance = extensions.filter(ext => ext.name !== 'Governance');
- if (!withoutGovernance.length) {
- return null;
- }
-
- return (
- <li className="dropdown">
- <a
- className="dropdown-toggle"
- id="component-navigation-more"
- data-toggle="dropdown"
- href="#">
- {translate('more')}
- <i className="icon-dropdown" />
- </a>
- <ul className="dropdown-menu">
- {withoutGovernance.map(e => this.renderExtension(e, false))}
- </ul>
- </li>
- );
- }
-
- render() {
- return (
- <NavBarTabs>
- {this.renderDashboardLink()}
- {this.renderIssuesLink()}
- {this.renderComponentMeasuresLink()}
- {this.renderCodeLink()}
- {this.renderActivityLink()}
- {this.renderAdministration()}
- {this.renderExtensions()}
- </NavBarTabs>
- );
- }
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import { Link } from 'react-router';
+import * as classNames from 'classnames';
+import { Branch, Component, ComponentExtension, ComponentConfiguration } from '../../../types';
+import NavBarTabs from '../../../../components/nav/NavBarTabs';
+import { isShortLivingBranch } from '../../../../helpers/branches';
+import { translate } from '../../../../helpers/l10n';
+
+const SETTINGS_URLS = [
+ '/project/admin',
+ '/project/settings',
+ '/project/quality_profiles',
+ '/project/quality_gate',
+ '/custom_measures',
+ '/project/links',
+ '/project_roles',
+ '/project/history',
+ 'background_tasks',
+ '/project/key',
+ '/project/deletion'
+];
+
+interface Props {
+ branch: Branch;
+ component: Component;
+ conf: ComponentConfiguration;
+}
+
+export default class ComponentNavMenu extends React.PureComponent<Props> {
+ isProject() {
+ return this.props.component.qualifier === 'TRK';
+ }
+
+ isDeveloper() {
+ return this.props.component.qualifier === 'DEV';
+ }
+
+ isView() {
+ const { qualifier } = this.props.component;
+ return qualifier === 'VW' || qualifier === 'SVW';
+ }
+
+ isApplication() {
+ return this.props.component.qualifier === 'APP';
+ }
+
+ renderDashboardLink() {
+ if (isShortLivingBranch(this.props.branch)) {
+ return null;
+ }
+
+ const pathname = this.isView() ? '/portfolio' : '/dashboard';
+ return (
+ <li>
+ <Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active">
+ {translate('overview.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderCodeLink() {
+ if (this.isDeveloper()) {
+ return null;
+ }
+
+ return (
+ <li>
+ <Link
+ to={{
+ pathname: '/code',
+ query: { branch: this.props.branch.name, id: this.props.component.key }
+ }}
+ activeClassName="active">
+ {this.isView() || this.isApplication()
+ ? translate('view_projects.page')
+ : translate('code.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderActivityLink() {
+ if (!this.isProject() && !this.isApplication()) {
+ return null;
+ }
+
+ if (isShortLivingBranch(this.props.branch)) {
+ return null;
+ }
+
+ return (
+ <li>
+ <Link
+ to={{ pathname: '/project/activity', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('project_activity.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderIssuesLink() {
+ return (
+ <li>
+ <Link
+ to={{
+ pathname: '/project/issues',
+ query: {
+ branch: this.props.branch.name,
+ id: this.props.component.key,
+ resolved: 'false'
+ }
+ }}
+ activeClassName="active">
+ {translate('issues.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderComponentMeasuresLink() {
+ if (isShortLivingBranch(this.props.branch)) {
+ return null;
+ }
+
+ return (
+ <li>
+ <Link
+ to={{ pathname: '/component_measures', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('layout.measures')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderAdministration() {
+ if (isShortLivingBranch(this.props.branch)) {
+ return null;
+ }
+
+ const adminLinks = this.renderAdministrationLinks();
+ if (!adminLinks.some(link => link != null)) {
+ return null;
+ }
+
+ const isSettingsActive = SETTINGS_URLS.some(url => window.location.href.indexOf(url) !== -1);
+ return (
+ <li className="dropdown">
+ <a
+ className={classNames('dropdown-toggle', 'is-admin', { active: isSettingsActive })}
+ id="component-navigation-admin"
+ data-toggle="dropdown"
+ href="#">
+ {translate('layout.settings')}
+ <i className="icon-dropdown" />
+ </a>
+ <ul className="dropdown-menu">
+ {adminLinks}
+ </ul>
+ </li>
+ );
+ }
+
+ renderAdministrationLinks() {
+ return [
+ this.renderSettingsLink(),
+ this.renderProfilesLink(),
+ this.renderQualityGateLink(),
+ this.renderCustomMeasuresLink(),
+ this.renderLinksLink(),
+ this.renderPermissionsLink(),
+ this.renderBackgroundTasksLink(),
+ this.renderUpdateKeyLink(),
+ ...this.renderAdminExtensions(),
+ this.renderDeletionLink()
+ ];
+ }
+
+ renderSettingsLink() {
+ if (!this.props.conf.showSettings || this.isApplication() || this.isView()) {
+ return null;
+ }
+ return (
+ <li key="settings">
+ <Link
+ to={{ pathname: '/project/settings', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('project_settings.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderProfilesLink() {
+ if (!this.props.conf.showQualityProfiles) {
+ return null;
+ }
+ return (
+ <li key="profiles">
+ <Link
+ to={{ pathname: '/project/quality_profiles', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('project_quality_profiles.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderQualityGateLink() {
+ if (!this.props.conf.showQualityGates) {
+ return null;
+ }
+ return (
+ <li key="quality_gate">
+ <Link
+ to={{ pathname: '/project/quality_gate', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('project_quality_gate.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderCustomMeasuresLink() {
+ if (!this.props.conf.showManualMeasures) {
+ return null;
+ }
+ return (
+ <li key="custom_measures">
+ <Link
+ to={{ pathname: '/custom_measures', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('custom_measures.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderLinksLink() {
+ if (!this.props.conf.showLinks) {
+ return null;
+ }
+ return (
+ <li key="links">
+ <Link
+ to={{ pathname: '/project/links', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('project_links.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderPermissionsLink() {
+ if (!this.props.conf.showPermissions) {
+ return null;
+ }
+ return (
+ <li key="permissions">
+ <Link
+ to={{ pathname: '/project_roles', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('permissions.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderBackgroundTasksLink() {
+ if (!this.props.conf.showBackgroundTasks) {
+ return null;
+ }
+ return (
+ <li key="background_tasks">
+ <Link
+ to={{ pathname: '/project/background_tasks', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('background_tasks.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderUpdateKeyLink() {
+ if (!this.props.conf.showUpdateKey) {
+ return null;
+ }
+ return (
+ <li key="update_key">
+ <Link
+ to={{ pathname: '/project/key', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('update_key.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderDeletionLink() {
+ const { qualifier } = this.props.component;
+
+ if (!this.props.conf.showSettings) {
+ return null;
+ }
+
+ if (qualifier !== 'TRK' && qualifier !== 'VW' && qualifier !== 'APP') {
+ return null;
+ }
+
+ return (
+ <li key="project_delete">
+ <Link
+ to={{ pathname: '/project/deletion', query: { id: this.props.component.key } }}
+ activeClassName="active">
+ {translate('deletion.page')}
+ </Link>
+ </li>
+ );
+ }
+
+ renderExtension = ({ key, name }: ComponentExtension, isAdmin: boolean) => {
+ const pathname = isAdmin ? `/project/admin/extension/${key}` : `/project/extension/${key}`;
+ return (
+ <li key={key}>
+ <Link to={{ pathname, query: { id: this.props.component.key } }} activeClassName="active">
+ {name}
+ </Link>
+ </li>
+ );
+ };
+
+ renderAdminExtensions() {
+ const extensions = this.props.conf.extensions || [];
+ return extensions.map(e => this.renderExtension(e, true));
+ }
+
+ renderExtensions() {
+ const extensions = this.props.component.extensions || [];
+ const withoutGovernance = extensions.filter(ext => ext.name !== 'Governance');
+ if (!withoutGovernance.length) {
+ return null;
+ }
+
+ return (
+ <li className="dropdown">
+ <a
+ className="dropdown-toggle"
+ id="component-navigation-more"
+ data-toggle="dropdown"
+ href="#">
+ {translate('more')}
+ <i className="icon-dropdown" />
+ </a>
+ <ul className="dropdown-menu">
+ {withoutGovernance.map(e => this.renderExtension(e, false))}
+ </ul>
+ </li>
+ );
+ }
+
+ render() {
+ return (
+ <NavBarTabs>
+ {this.renderDashboardLink()}
+ {this.renderIssuesLink()}
+ {this.renderComponentMeasuresLink()}
+ {this.renderCodeLink()}
+ {this.renderActivityLink()}
+ {this.renderAdministration()}
+ {this.renderExtensions()}
+ </NavBarTabs>
+ );
+ }
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter';
-import IncrementalBadge from './IncrementalBadge';
-import PendingIcon from '../../../../components/shared/pending-icon';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { translate, translateWithParameters } from '../../../../helpers/l10n';
-
-export default function ComponentNavMeta(props) {
- const metaList = [];
- const canSeeBackgroundTasks = props.conf.showBackgroundTasks;
- const backgroundTasksUrl =
- window.baseUrl + `/project/background_tasks?id=${encodeURIComponent(props.component.key)}`;
-
- if (props.isInProgress) {
- const tooltip = canSeeBackgroundTasks
- ? translateWithParameters('component_navigation.status.in_progress.admin', backgroundTasksUrl)
- : translate('component_navigation.status.in_progress');
- metaList.push(
- <Tooltip
- key="isInProgress"
- overlay={<div dangerouslySetInnerHTML={{ __html: tooltip }} />}
- mouseLeaveDelay={2}>
- <li>
- <i className="spinner" style={{ marginTop: '-1px' }} />{' '}
- <span className="text-info">{translate('background_task.status.IN_PROGRESS')}</span>
- </li>
- </Tooltip>
- );
- } else if (props.isPending) {
- const tooltip = canSeeBackgroundTasks
- ? translateWithParameters('component_navigation.status.pending.admin', backgroundTasksUrl)
- : translate('component_navigation.status.pending');
- metaList.push(
- <Tooltip
- key="isPending"
- overlay={<div dangerouslySetInnerHTML={{ __html: tooltip }} />}
- mouseLeaveDelay={2}>
- <li>
- <PendingIcon /> <span>{translate('background_task.status.PENDING')}</span>
- </li>
- </Tooltip>
- );
- } else if (props.isFailed) {
- const tooltip = canSeeBackgroundTasks
- ? translateWithParameters('component_navigation.status.failed.admin', backgroundTasksUrl)
- : translate('component_navigation.status.failed');
- metaList.push(
- <Tooltip
- key="isFailed"
- overlay={<div dangerouslySetInnerHTML={{ __html: tooltip }} />}
- mouseLeaveDelay={2}>
- <li>
- <span className="badge badge-danger">
- {translate('background_task.status.FAILED')}
- </span>
- </li>
- </Tooltip>
- );
- }
- if (props.analysisDate) {
- metaList.push(
- <li key="analysisDate">
- <DateTimeFormatter date={props.analysisDate} />
- </li>
- );
- }
-
- if (props.version) {
- metaList.push(
- <li key="version">
- Version {props.version}
- </li>
- );
- }
-
- if (props.incremental) {
- metaList.push(
- <li key="incremental">
- <IncrementalBadge />
- </li>
- );
- }
-
- return (
- <div className="navbar-context-meta">
- <ul className="list-inline">
- {metaList}
- </ul>
- </div>
- );
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import IncrementalBadge from './IncrementalBadge';
+import BranchStatus from './BranchStatus';
+import { Branch, Component, ComponentConfiguration } from '../../../types';
+import Tooltip from '../../../../components/controls/Tooltip';
+import PendingIcon from '../../../../components/icons-components/PendingIcon';
+import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter';
+import { translate, translateWithParameters } from '../../../../helpers/l10n';
+
+interface Props {
+ branch: Branch;
+ component: Component;
+ conf: ComponentConfiguration;
+ incremental?: boolean;
+ isInProgress?: boolean;
+ isFailed?: boolean;
+ isPending?: boolean;
+}
+
+export default function ComponentNavMeta(props: Props) {
+ const metaList = [];
+ const canSeeBackgroundTasks = props.conf.showBackgroundTasks;
+ const backgroundTasksUrl =
+ (window as any).baseUrl +
+ `/project/background_tasks?id=${encodeURIComponent(props.component.key)}`;
+
+ if (props.isInProgress) {
+ const tooltip = canSeeBackgroundTasks
+ ? translateWithParameters('component_navigation.status.in_progress.admin', backgroundTasksUrl)
+ : translate('component_navigation.status.in_progress');
+ metaList.push(
+ <Tooltip
+ key="isInProgress"
+ overlay={<div dangerouslySetInnerHTML={{ __html: tooltip }} />}
+ mouseLeaveDelay={2}>
+ <li>
+ <i className="spinner" style={{ marginTop: '-1px' }} />{' '}
+ <span className="text-info">{translate('background_task.status.IN_PROGRESS')}</span>
+ </li>
+ </Tooltip>
+ );
+ } else if (props.isPending) {
+ const tooltip = canSeeBackgroundTasks
+ ? translateWithParameters('component_navigation.status.pending.admin', backgroundTasksUrl)
+ : translate('component_navigation.status.pending');
+ metaList.push(
+ <Tooltip
+ key="isPending"
+ overlay={<div dangerouslySetInnerHTML={{ __html: tooltip }} />}
+ mouseLeaveDelay={2}>
+ <li>
+ <PendingIcon /> <span>{translate('background_task.status.PENDING')}</span>
+ </li>
+ </Tooltip>
+ );
+ } else if (props.isFailed) {
+ const tooltip = canSeeBackgroundTasks
+ ? translateWithParameters('component_navigation.status.failed.admin', backgroundTasksUrl)
+ : translate('component_navigation.status.failed');
+ metaList.push(
+ <Tooltip
+ key="isFailed"
+ overlay={<div dangerouslySetInnerHTML={{ __html: tooltip }} />}
+ mouseLeaveDelay={2}>
+ <li>
+ <span className="badge badge-danger">
+ {translate('background_task.status.FAILED')}
+ </span>
+ </li>
+ </Tooltip>
+ );
+ }
+
+ if (props.component.analysisDate && props.branch.isMain) {
+ metaList.push(
+ <li key="analysisDate">
+ <DateTimeFormatter date={props.component.analysisDate} />
+ </li>
+ );
+ }
+
+ if (props.component.version && props.branch.isMain) {
+ metaList.push(
+ <li key="version">
+ Version {props.component.version}
+ </li>
+ );
+ }
+
+ if (props.incremental) {
+ metaList.push(
+ <li key="incremental">
+ <IncrementalBadge />
+ </li>
+ );
+ }
+
+ if (!props.branch.isMain) {
+ metaList.push(
+ <li className="navbar-context-meta-branch" key="branch-status">
+ <BranchStatus branch={props.branch} />
+ </li>
+ );
+ }
+
+ return (
+ <div className="navbar-context-meta">
+ <ul className="list-inline">
+ {metaList}
+ </ul>
+ </div>
+ );
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { translate } from '../../../../helpers/l10n';
-
-export default function IncrementalBadge() {
- return (
- <Tooltip overlay={translate('incremental.project_tooltip')}>
- <div className="outline-badge">
- {translate('incremental')}
- </div>
- </Tooltip>
- );
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import Tooltip from '../../../../components/controls/Tooltip';
+import { translate } from '../../../../helpers/l10n';
+
+export default function IncrementalBadge() {
+ return (
+ <Tooltip overlay={translate('incremental.project_tooltip')}>
+ <div className="outline-badge">
+ {translate('incremental')}
+ </div>
+ </Tooltip>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import BranchStatus from '../BranchStatus';
+import { BranchType } from '../../../../types';
+
+it('renders', () => {
+ check(0, 0, 0);
+ check(0, 1, 0);
+ check(7, 3, 6);
+});
+
+function check(bugs: number, codeSmells: number, vulnerabilities: number) {
+ expect(
+ shallow(
+ <BranchStatus
+ branch={{
+ isMain: false,
+ name: 'foo',
+ status: { bugs, codeSmells, vulnerabilities },
+ type: BranchType.SHORT
+ }}
+ />
+ )
+ ).toMatchSnapshot();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ComponentNavBranch from '../ComponentNavBranch';
+import { BranchType, ShortLivingBranch, MainBranch, Component } from '../../../../types';
+import { click } from '../../../../../helpers/testUtils';
+
+it('renders main branch', () => {
+ const branch: MainBranch = { isMain: true, name: undefined, type: BranchType.LONG };
+ const component = {} as Component;
+ expect(shallow(<ComponentNavBranch branch={branch} project={component} />)).toMatchSnapshot();
+});
+
+it('renders short-living branch', () => {
+ const branch: ShortLivingBranch = {
+ isMain: false,
+ name: 'foo',
+ status: { bugs: 0, codeSmells: 0, vulnerabilities: 0 },
+ type: BranchType.SHORT
+ };
+ const component = {} as Component;
+ expect(shallow(<ComponentNavBranch branch={branch} project={component} />)).toMatchSnapshot();
+});
+
+it('opens menu', () => {
+ const branch: MainBranch = { isMain: true, name: undefined, type: BranchType.LONG };
+ const component = {} as Component;
+ const wrapper = shallow(<ComponentNavBranch branch={branch} project={component} />);
+ expect(wrapper.find('ComponentNavBranchesMenu')).toHaveLength(0);
+ click(wrapper.find('a'));
+ expect(wrapper.find('ComponentNavBranchesMenu')).toHaveLength(1);
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ComponentNavBranchesMenu from '../ComponentNavBranchesMenu';
+import {
+ BranchType,
+ MainBranch,
+ ShortLivingBranch,
+ LongLivingBranch,
+ Component
+} from '../../../../types';
+import { elementKeydown } from '../../../../../helpers/testUtils';
+
+it('renders list', () => {
+ const component = { key: 'component' } as Component;
+ const wrapper = shallow(
+ <ComponentNavBranchesMenu branch={mainBranch()} onClose={jest.fn()} project={component} />
+ );
+ wrapper.setState({
+ branches: [mainBranch(), shortBranch('foo'), longBranch('bar')],
+ loading: false
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('searches', () => {
+ const component = { key: 'component' } as Component;
+ const wrapper = shallow(
+ <ComponentNavBranchesMenu branch={mainBranch()} onClose={jest.fn()} project={component} />
+ );
+ wrapper.setState({
+ branches: [mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')],
+ loading: false,
+ query: 'bar'
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('selects next & previous', () => {
+ const component = { key: 'component' } as Component;
+ const wrapper = shallow(
+ <ComponentNavBranchesMenu branch={mainBranch()} onClose={jest.fn()} project={component} />
+ );
+ wrapper.setState({
+ branches: [mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')],
+ loading: false
+ });
+ elementKeydown(wrapper.find('input'), 40);
+ wrapper.update();
+ expect(wrapper.state().selected).toBe('foo');
+ elementKeydown(wrapper.find('input'), 40);
+ wrapper.update();
+ expect(wrapper.state().selected).toBe('foobar');
+ elementKeydown(wrapper.find('input'), 38);
+ wrapper.update();
+ expect(wrapper.state().selected).toBe('foo');
+});
+
+function mainBranch(): MainBranch {
+ return { isMain: true, name: undefined, type: BranchType.LONG };
+}
+
+function shortBranch(name: string): ShortLivingBranch {
+ return {
+ isMain: false,
+ name,
+ status: { bugs: 0, codeSmells: 0, vulnerabilities: 0 },
+ type: BranchType.SHORT
+ };
+}
+
+function longBranch(name: string): LongLivingBranch {
+ return { isMain: false, name, type: BranchType.LONG };
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ComponentNavBranchesMenuItem from '../ComponentNavBranchesMenuItem';
+import { BranchType, MainBranch, ShortLivingBranch, Component } from '../../../../types';
+
+it('renders main branch', () => {
+ const component = { key: 'component' } as Component;
+ const mainBranch: MainBranch = { isMain: true, name: undefined, type: BranchType.LONG };
+ expect(
+ shallow(
+ <ComponentNavBranchesMenuItem
+ branch={mainBranch}
+ component={component}
+ onSelect={jest.fn()}
+ selected={false}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('renders short-living branch', () => {
+ const component = { key: 'component' } as Component;
+ const shortBranch: ShortLivingBranch = {
+ isMain: false,
+ name: 'foo',
+ status: { bugs: 1, codeSmells: 2, vulnerabilities: 3 },
+ type: BranchType.SHORT
+ };
+ expect(
+ shallow(
+ <ComponentNavBranchesMenuItem
+ branch={shortBranch}
+ component={component}
+ onSelect={jest.fn()}
+ selected={false}
+ />
+ )
+ ).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * 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 React from 'react';
-import { shallow } from 'enzyme';
-import ComponentNavMenu from '../ComponentNavMenu';
-
-it('should work with extensions', () => {
- const component = {
- key: 'foo',
- qualifier: 'TRK',
- extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
- };
- const conf = {
- showSettings: true,
- extensions: [{ key: 'foo', name: 'Foo' }]
- };
- expect(shallow(<ComponentNavMenu component={component} conf={conf} />)).toMatchSnapshot();
-});
-
-it('should work with multiple extensions', () => {
- const component = {
- key: 'foo',
- qualifier: 'TRK',
- extensions: [
- { key: 'component-foo', name: 'ComponentFoo' },
- { key: 'component-bar', name: 'ComponentBar' }
- ]
- };
- const conf = {
- showSettings: true,
- extensions: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]
- };
- expect(shallow(<ComponentNavMenu component={component} conf={conf} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ComponentNavMenu from '../ComponentNavMenu';
+import { Branch, Component } from '../../../../types';
+
+it('should work with extensions', () => {
+ const component = {
+ key: 'foo',
+ qualifier: 'TRK',
+ extensions: [{ key: 'component-foo', name: 'ComponentFoo' }]
+ };
+ const conf = {
+ showSettings: true,
+ extensions: [{ key: 'foo', name: 'Foo' }]
+ };
+ expect(
+ shallow(
+ <ComponentNavMenu branch={{} as Branch} component={component as Component} conf={conf} />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should work with multiple extensions', () => {
+ const component = {
+ key: 'foo',
+ qualifier: 'TRK',
+ extensions: [
+ { key: 'component-foo', name: 'ComponentFoo' },
+ { key: 'component-bar', name: 'ComponentBar' }
+ ]
+ };
+ const conf = {
+ showSettings: true,
+ extensions: [{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]
+ };
+ expect(
+ shallow(
+ <ComponentNavMenu branch={{} as Branch} component={component as Component} conf={conf} />
+ )
+ ).toMatchSnapshot();
+});
import * as React from 'react';
import { shallow } from 'enzyme';
import ComponentNavMeta from '../ComponentNavMeta';
+import { Branch, Component } from '../../../../types';
it('renders incremental badge', () => {
check(true);
function check(incremental: boolean) {
expect(
shallow(
- <ComponentNavMeta component={{ key: 'foo' }} conf={{}} incremental={incremental} />
+ <ComponentNavMeta
+ branch={{} as Branch}
+ component={{ key: 'foo' } as Component}
+ conf={{}}
+ incremental={incremental}
+ />
).find('IncrementalBadge')
).toHaveLength(incremental ? 1 : 0);
}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<ul
+ className="list-inline branch-status"
+>
+ <li>
+ <i
+ className="branch-status-indicator is-passed"
+ />
+ </li>
+ <li>
+ 0
+ <BugIcon
+ className="little-spacer-left"
+ />
+ </li>
+ <li>
+ 0
+ <VulnerabilityIcon
+ className="little-spacer-left"
+ />
+ </li>
+ <li>
+ 0
+ <CodeSmellIcon
+ className="little-spacer-left"
+ />
+ </li>
+</ul>
+`;
+
+exports[`renders 2`] = `
+<ul
+ className="list-inline branch-status"
+>
+ <li>
+ <i
+ className="branch-status-indicator is-failed"
+ />
+ </li>
+ <li>
+ 0
+ <BugIcon
+ className="little-spacer-left"
+ />
+ </li>
+ <li>
+ 0
+ <VulnerabilityIcon
+ className="little-spacer-left"
+ />
+ </li>
+ <li>
+ 1
+ <CodeSmellIcon
+ className="little-spacer-left"
+ />
+ </li>
+</ul>
+`;
+
+exports[`renders 3`] = `
+<ul
+ className="list-inline branch-status"
+>
+ <li>
+ <i
+ className="branch-status-indicator is-failed"
+ />
+ </li>
+ <li>
+ 7
+ <BugIcon
+ className="little-spacer-left"
+ />
+ </li>
+ <li>
+ 6
+ <VulnerabilityIcon
+ className="little-spacer-left"
+ />
+ </li>
+ <li>
+ 3
+ <CodeSmellIcon
+ className="little-spacer-left"
+ />
+ </li>
+</ul>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders main branch 1`] = `
+<div
+ className="navbar-context-branches dropdown"
+>
+ <a
+ className="link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <BranchIcon
+ className="little-spacer-right"
+ />
+ master
+ <i
+ className="icon-dropdown little-spacer-left"
+ />
+ </a>
+</div>
+`;
+
+exports[`renders short-living branch 1`] = `
+<div
+ className="navbar-context-branches dropdown"
+>
+ <a
+ className="link-base-color link-no-underline"
+ href="#"
+ onClick={[Function]}
+ >
+ <BranchIcon
+ className="little-spacer-right"
+ />
+ foo
+ <i
+ className="icon-dropdown little-spacer-left"
+ />
+ </a>
+</div>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders list 1`] = `
+<div
+ className="dropdown-menu dropdown-menu-shadow"
+>
+ <div>
+ <div
+ className="search-box menu-search"
+ >
+ <button
+ className="search-box-submit button-clean"
+ >
+ <i
+ className="icon-search-new"
+ />
+ </button>
+ <input
+ autoFocus={true}
+ className="search-box-input"
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ placeholder="search_verb"
+ type="search"
+ value=""
+ />
+ </div>
+ <ul
+ className="menu"
+ >
+ <ComponentNavBranchesMenuItem
+ branch={
+ Object {
+ "isMain": true,
+ "name": undefined,
+ "type": "LONG",
+ }
+ }
+ component={
+ Object {
+ "key": "component",
+ }
+ }
+ onSelect={[Function]}
+ selected={true}
+ />
+ <ComponentNavBranchesMenuItem
+ branch={
+ Object {
+ "isMain": false,
+ "name": "foo",
+ "status": Object {
+ "bugs": 0,
+ "codeSmells": 0,
+ "vulnerabilities": 0,
+ },
+ "type": "SHORT",
+ }
+ }
+ component={
+ Object {
+ "key": "component",
+ }
+ }
+ onSelect={[Function]}
+ selected={false}
+ />
+ <ComponentNavBranchesMenuItem
+ branch={
+ Object {
+ "isMain": false,
+ "name": "bar",
+ "type": "LONG",
+ }
+ }
+ component={
+ Object {
+ "key": "component",
+ }
+ }
+ onSelect={[Function]}
+ selected={false}
+ />
+ </ul>
+ </div>
+</div>
+`;
+
+exports[`searches 1`] = `
+<div
+ className="dropdown-menu dropdown-menu-shadow"
+>
+ <div>
+ <div
+ className="search-box menu-search"
+ >
+ <button
+ className="search-box-submit button-clean"
+ >
+ <i
+ className="icon-search-new"
+ />
+ </button>
+ <input
+ autoFocus={true}
+ className="search-box-input"
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ placeholder="search_verb"
+ type="search"
+ value="bar"
+ />
+ </div>
+ <ul
+ className="menu"
+ >
+ <ComponentNavBranchesMenuItem
+ branch={
+ Object {
+ "isMain": false,
+ "name": "foobar",
+ "status": Object {
+ "bugs": 0,
+ "codeSmells": 0,
+ "vulnerabilities": 0,
+ },
+ "type": "SHORT",
+ }
+ }
+ component={
+ Object {
+ "key": "component",
+ }
+ }
+ onSelect={[Function]}
+ selected={true}
+ />
+ <ComponentNavBranchesMenuItem
+ branch={
+ Object {
+ "isMain": false,
+ "name": "bar",
+ "type": "LONG",
+ }
+ }
+ component={
+ Object {
+ "key": "component",
+ }
+ }
+ onSelect={[Function]}
+ selected={false}
+ />
+ </ul>
+ </div>
+</div>
+`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders main branch 1`] = `
+<li
+ onMouseEnter={[Function]}
+>
+ <Link
+ className="navbar-context-meta-branch-menu-item"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "component",
+ },
+ }
+ }
+ >
+ <div>
+ <BranchIcon
+ className="little-spacer-right"
+ />
+ master
+ </div>
+ <div
+ className="big-spacer-left note"
+ >
+ <BranchStatus
+ branch={
+ Object {
+ "isMain": true,
+ "name": undefined,
+ "type": "LONG",
+ }
+ }
+ concise={true}
+ />
+ </div>
+ </Link>
+</li>
+`;
+
+exports[`renders short-living branch 1`] = `
+<li
+ onMouseEnter={[Function]}
+>
+ <Link
+ className="navbar-context-meta-branch-menu-item"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "branch": "foo",
+ "id": "component",
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ <div>
+ <BranchIcon
+ className="little-spacer-right big-spacer-left"
+ />
+ foo
+ </div>
+ <div
+ className="big-spacer-left note"
+ >
+ <BranchStatus
+ branch={
+ Object {
+ "isMain": false,
+ "name": "foo",
+ "status": Object {
+ "bugs": 1,
+ "codeSmells": 2,
+ "vulnerabilities": 3,
+ },
+ "type": "SHORT",
+ }
+ }
+ concise={true}
+ />
+ </div>
+ </Link>
+</li>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should work with extensions 1`] = `
-<NavBarTabs>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- overview.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- layout.measures
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/code",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- code.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- project_activity.page
- </Link>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle is-admin"
- data-toggle="dropdown"
- href="#"
- id="component-navigation-admin"
- >
- layout.settings
- Â
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/settings",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- project_settings.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/admin/extension/foo",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- Foo
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- deletion.page
- </Link>
- </li>
- </ul>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle"
- data-toggle="dropdown"
- href="#"
- id="component-navigation-more"
- >
- more
- Â
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/extension/component-foo",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- ComponentFoo
- </Link>
- </li>
- </ul>
- </li>
-</NavBarTabs>
-`;
-
-exports[`should work with multiple extensions 1`] = `
-<NavBarTabs>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/dashboard",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- overview.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/issues",
- "query": Object {
- "id": "foo",
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/component_measures",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- layout.measures
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/code",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- code.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/activity",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- project_activity.page
- </Link>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle is-admin"
- data-toggle="dropdown"
- href="#"
- id="component-navigation-admin"
- >
- layout.settings
- Â
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/settings",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- project_settings.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/admin/extension/foo",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- Foo
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/admin/extension/bar",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- Bar
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/deletion",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- deletion.page
- </Link>
- </li>
- </ul>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle"
- data-toggle="dropdown"
- href="#"
- id="component-navigation-more"
- >
- more
- Â
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/extension/component-foo",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- ComponentFoo
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/project/extension/component-bar",
- "query": Object {
- "id": "foo",
- },
- }
- }
- >
- ComponentBar
- </Link>
- </li>
- </ul>
- </li>
-</NavBarTabs>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should work with extensions 1`] = `
+<NavBarTabs>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ overview.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "branch": undefined,
+ "id": "foo",
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/component_measures",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ layout.measures
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/code",
+ "query": Object {
+ "branch": undefined,
+ "id": "foo",
+ },
+ }
+ }
+ >
+ code.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/activity",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ project_activity.page
+ </Link>
+ </li>
+ <li
+ className="dropdown"
+ >
+ <a
+ className="dropdown-toggle is-admin"
+ data-toggle="dropdown"
+ href="#"
+ id="component-navigation-admin"
+ >
+ layout.settings
+ Â
+ <i
+ className="icon-dropdown"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/settings",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ project_settings.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/admin/extension/foo",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ Foo
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/deletion",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ deletion.page
+ </Link>
+ </li>
+ </ul>
+ </li>
+ <li
+ className="dropdown"
+ >
+ <a
+ className="dropdown-toggle"
+ data-toggle="dropdown"
+ href="#"
+ id="component-navigation-more"
+ >
+ more
+ Â
+ <i
+ className="icon-dropdown"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/extension/component-foo",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ ComponentFoo
+ </Link>
+ </li>
+ </ul>
+ </li>
+</NavBarTabs>
+`;
+
+exports[`should work with multiple extensions 1`] = `
+<NavBarTabs>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ overview.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/issues",
+ "query": Object {
+ "branch": undefined,
+ "id": "foo",
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/component_measures",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ layout.measures
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/code",
+ "query": Object {
+ "branch": undefined,
+ "id": "foo",
+ },
+ }
+ }
+ >
+ code.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/activity",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ project_activity.page
+ </Link>
+ </li>
+ <li
+ className="dropdown"
+ >
+ <a
+ className="dropdown-toggle is-admin"
+ data-toggle="dropdown"
+ href="#"
+ id="component-navigation-admin"
+ >
+ layout.settings
+ Â
+ <i
+ className="icon-dropdown"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/settings",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ project_settings.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/admin/extension/foo",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ Foo
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/admin/extension/bar",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ Bar
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/deletion",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ deletion.page
+ </Link>
+ </li>
+ </ul>
+ </li>
+ <li
+ className="dropdown"
+ >
+ <a
+ className="dropdown-toggle"
+ data-toggle="dropdown"
+ href="#"
+ id="component-navigation-more"
+ >
+ more
+ Â
+ <i
+ className="icon-dropdown"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/extension/component-foo",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ ComponentFoo
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/project/extension/component-bar",
+ "query": Object {
+ "id": "foo",
+ },
+ }
+ }
+ >
+ ComponentBar
+ </Link>
+ </li>
+ </ul>
+ </li>
+</NavBarTabs>
+`;
padding: 0;
overflow-y: auto;
overflow-x: hidden;
- box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
}
{this.state.open &&
Object.keys(this.state.results).length > 0 &&
<div
- className="dropdown-menu dropdown-menu-right global-navbar-search-dropdown"
+ className="dropdown-menu dropdown-menu-shadow dropdown-menu-right global-navbar-search-dropdown"
ref={node => (this.node = node)}>
<SearchResults
allowMore={this.state.query.length !== 1}
} 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';
}
}
-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);
/* @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'; */
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';
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,
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();
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)) {
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({
.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;
{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 &&
</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);
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} />;
}
}
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')}
/>
</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 });
}
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 (
};
componentWillMount() {
- this.handleSearch = debounce(this.handleSearch.bind(this), 250);
+ this.handleSearch = debounce(this.handleSearch, 250);
}
componentDidMount() {
this.context.router.push({
pathname: '/code',
query: {
+ branch: component.branch,
id: component.key,
selected: selected.key
}
}
}
- 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;
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)) {
}
});
}
- }
+ };
handleQueryChange(query) {
this.setState({ query });
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 }));
}
}
];
* @param {boolean} isPortfolio
* @returns {Promise}
*/
-function retrieveComponentBase(componentKey, isPortfolio) {
+function retrieveComponentBase(componentKey, isPortfolio, branch) {
const existing = getComponentFromBucket(componentKey);
if (existing) {
return Promise.resolve(existing);
const metrics = getMetrics(isPortfolio);
- return getComponent(componentKey, metrics).then(component => {
+ return getComponent(componentKey, metrics, branch).then(component => {
addComponent(component);
return component;
});
* @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({
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 => {
});
}
-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;
});
* @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],
});
}
-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 => {
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';
/*:: 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)
*/
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);
}
);
}
}
-
-const mapStateToProps = (state, ownProps) => ({
- component: getComponent(state, ownProps.location.query.id)
-});
-
-export default connect(mapStateToProps)(CustomMeasuresAppContainer);
{
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 })
);
}
}
/*::
export type Props = {
+ branch?: { name: string },
component?: Component,
currentUser: CurrentUser,
fetchIssues: (query: RawQuery) => Promise<*>,
const { query } = this.props.location;
const { query: prevQuery } = prevProps.location;
if (
+ prevProps.component !== this.props.component ||
!areQueriesEqual(prevQuery, query) ||
areMyIssuesSelected(prevQuery) !== areMyIssuesSelected(query)
) {
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
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
: undefined;
const parameters = {
+ branch: this.props.branch && this.props.branch.name,
+ componentKeys: component && component.key,
s: 'FILE_LINE',
...serializeQuery(query),
ps: '100',
...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' });
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
}
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
}
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
}
<div>
{openIssue
? <IssuesSourceViewer
+ branch={this.props.branch}
+ component={component}
openIssue={openIssue}
loadIssues={this.fetchIssuesForComponent}
onIssueChange={this.handleIssueChange}
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)
});
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,
<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}
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
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,
qualifier: string,
tags: Array<string>
},
+ onComponentChange: {} => void,
router: Object
};
*/
query: { id: this.props.component.key }
});
}
+ if (isShortLivingBranch(this.props.branch)) {
+ this.context.router.replace(getProjectBranchUrl(this.props.component.key, this.props.branch));
+ }
}
isPortfolio() {
}
render() {
- if (this.isPortfolio()) {
+ if (this.isPortfolio() || isShortLivingBranch(this.props.branch)) {
return null;
}
return <EmptyOverview component={component} />;
}
- return <OverviewApp component={component} />;
+ return <OverviewApp component={component} onComponentChange={this.props.onComponentChange} />;
}
}
+++ /dev/null
-/*
- * 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);
/*::
type Props = {
- component: Component
+ component: Component,
+ onComponentChange: {} => void
};
*/
</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>
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';
<MetaSize component={component} measures={measures} />
- {isProject && <MetaTags component={component} />}
+ {isProject && <MetaTags component={component} onComponentChange={onComponentChange} />}
{(isProject || isApplication) &&
<AnalysesList
*/
//@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 = {
configuration?: {
showSettings?: boolean
}
- }
+ },
+ onComponentChange: {} => void
};
*/
};
}
+ 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;
</button>
{popupOpen &&
<div ref={tagsSelector => (this.tagsSelector = tagsSelector)}>
- <ProjectTagsSelectorContainer
+ <MetaTagsSelector
position={popupPosition}
project={key}
selectedTags={tags}
+ setProjectTags={this.handleSetProjectTags}
/>
</div>}
</div>
--- /dev/null
+/*
+ * 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}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * 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']);
+});
/>
</button>
<div>
- <Connect(ProjectTagsSelectorContainer)
+ <MetaTagsSelector
position={
Object {
"right": 0,
"bar",
]
}
+ setProjectTags={[Function]}
/>
</div>
</div>
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 }));
}
}
];
* 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);
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,
}
const mapStateToProps = (state, ownProps) => ({
- component: getComponent(state, ownProps.location.query.id),
modules: getProjectAdminProjectModules(state, ownProps.location.query.id)
});
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
};
}
const mapStateToProps = (state, ownProps) => ({
- component: getComponent(state, ownProps.location.query.id),
links: getProjectAdminProjectLinks(state, ownProps.location.query.id)
});
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
};
}
const mapStateToProps = (state, ownProps) => ({
- component: getComponent(state, ownProps.location.query.id),
allGates: getProjectAdminAllGates(state),
gate: getProjectAdminProjectGate(state, ownProps.location.query.id)
});
import {
areThereCustomOrganizations,
getProjectAdminAllProfiles,
- getProjectAdminProjectProfiles,
- getComponent
+ getProjectAdminProjectProfiles
} from '../../../store/rootReducer';
import { translate } from '../../../helpers/l10n';
}
const mapStateToProps = (state, ownProps) => ({
- component: getComponent(state, ownProps.location.query.id),
customOrganizations: areThereCustomOrganizations(state),
allProfiles: getProjectAdminAllProfiles(state),
profiles: getProjectAdminProjectProfiles(state, ownProps.location.query.id)
*/
// @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';
/*::
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
}
};
*/
};
*/
-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 = {
if (isCustomGraph(newQuery.graph)) {
newQuery.customMetrics = getCustomGraph();
}
- this.props.router.replace({
+ this.context.router.replace({
pathname: props.location.pathname,
query: serializeUrlQuery(newQuery)
});
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,
...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
}
});
};
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));
{
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 })
);
}
}
+++ /dev/null
-/*
- * 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 { 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
-};
-*/
-
-/*::
-type State = {
- searchResult: Array<string>
-};
-*/
-
-const LIST_SIZE = 10;
-
-class ProjectTagsSelectorContainer 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.project, [...this.props.selectedTags, tag]);
- };
-
- onUnselect = (tag /*: string */) => {
- this.props.setProjectTags(this.props.project, 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}
- />
- );
- }
-}
-
-export default connect(null, { setProjectTags })(ProjectTagsSelectorContainer);
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)
});
/*::
type Props = {
aroundLine?: number,
+ branch?: string,
component: string,
displayAllIssues: boolean,
filterLine?: (line: SourceLine) => boolean,
highlightedLine?: number,
highlightedLocations?: Array<FlowLocation>,
highlightedLocationMessage?: { index: number, text: string },
- loadComponent: string => Promise<*>,
- loadIssues: (string, number, number) => Promise<*>,
- loadSources: (string, number, number) => Promise<*>,
+ loadComponent: (component: string, branch?: string) => Promise<*>,
+ loadIssues: (component: string, from: number, to: number, branch?: string) => Promise<*>,
+ loadSources: (component: string, from: number, to: number, branch?: string) => Promise<*>,
onLoaded?: (component: Object, sources: Array<*>, issues: Array<*>) => void,
onLocationSelect?: number => void,
onIssueChange?: Issue => void,
const LINES = 500;
-function loadComponent(key /*: string */) /*: Promise<*> */ {
- return getComponentForSourceViewer(key);
+function loadComponent(key /*: string */, branch /*: string | void */) /*: Promise<*> */ {
+ return getComponentForSourceViewer(key, branch);
}
function loadSources(
key /*: string */,
from /*: ?number */,
- to /*: ?number */
+ to /*: ?number */,
+ branch /*: string | void */
) /*: Promise<Array<*>> */ {
- return getSources(key, from, to);
+ return getSources(key, from, to, branch);
}
export default class SourceViewerBase extends React.PureComponent {
}
componentDidUpdate(prevProps /*: Props */) {
- if (prevProps.component !== this.props.component) {
+ if (prevProps.component !== this.props.component || prevProps.branch !== this.props.branch) {
this.fetchComponent();
} else if (
this.props.aroundLine != null &&
fetchComponent() {
this.setState({ loading: true });
const loadIssues = (component, sources) => {
- this.props.loadIssues(this.props.component, 1, LINES).then(issues => {
+ this.props.loadIssues(this.props.component, 1, LINES, this.props.branch).then(issues => {
if (this.mounted) {
const finalSources = sources.slice(0, LINES);
this.setState(
);
};
- this.props.loadComponent(this.props.component).then(onResolve, onFailLoadComponent);
+ this.props
+ .loadComponent(this.props.component, this.props.branch)
+ .then(onResolve, onFailLoadComponent);
}
fetchSources() {
to++;
return this.props
- .loadSources(this.props.component, from, to)
+ .loadSources(this.props.component, from, to, this.props.branch)
.then(sources => resolve(sources), onFailLoadSources);
});
}
const firstSourceLine = this.state.sources[0];
this.setState({ loadingSourcesBefore: true });
const from = Math.max(1, firstSourceLine.line - LINES);
- this.props.loadSources(this.props.component, from, firstSourceLine.line - 1).then(sources => {
- this.props.loadIssues(this.props.component, from, firstSourceLine.line - 1).then(issues => {
- if (this.mounted) {
- this.setState(prevState => {
- const nextIssues = uniqBy([...issues, ...prevState.issues], issue => issue.key);
- return {
- issues: nextIssues,
- issuesByLine: issuesByLine(nextIssues),
- issueLocationsByLine: locationsByLine(nextIssues),
- loadingSourcesBefore: false,
- sources: [...this.computeCoverageStatus(sources), ...prevState.sources],
- symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) }
- };
- });
- }
+ this.props
+ .loadSources(this.props.component, from, firstSourceLine.line - 1, this.props.branch)
+ .then(sources => {
+ this.props.loadIssues(this.props.component, from, firstSourceLine.line - 1).then(issues => {
+ if (this.mounted) {
+ this.setState(prevState => {
+ const nextIssues = uniqBy([...issues, ...prevState.issues], issue => issue.key);
+ return {
+ issues: nextIssues,
+ issuesByLine: issuesByLine(nextIssues),
+ issueLocationsByLine: locationsByLine(nextIssues),
+ loadingSourcesBefore: false,
+ sources: [...this.computeCoverageStatus(sources), ...prevState.sources],
+ symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) }
+ };
+ });
+ }
+ });
});
- });
};
loadSourcesAfter = () => {
const fromLine = lastSourceLine.line + 1;
// request one additional line to define `hasSourcesAfter`
const toLine = lastSourceLine.line + LINES + 1;
- this.props.loadSources(this.props.component, fromLine, toLine).then(sources => {
- this.props.loadIssues(this.props.component, fromLine, toLine).then(issues => {
- if (this.mounted) {
- this.setState(prevState => {
- const nextIssues = uniqBy([...prevState.issues, ...issues], issue => issue.key);
- return {
- issues: nextIssues,
- issuesByLine: issuesByLine(nextIssues),
- issueLocationsByLine: locationsByLine(nextIssues),
- hasSourcesAfter: sources.length > LINES,
- loadingSourcesAfter: false,
- sources: [
- ...prevState.sources,
- ...this.computeCoverageStatus(sources.slice(0, LINES))
- ],
- symbolsByLine: {
- ...prevState.symbolsByLine,
- ...symbolsByLine(sources.slice(0, LINES))
- }
- };
- });
- }
+ this.props
+ .loadSources(this.props.component, fromLine, toLine, this.props.branch)
+ .then(sources => {
+ this.props.loadIssues(this.props.component, fromLine, toLine).then(issues => {
+ if (this.mounted) {
+ this.setState(prevState => {
+ const nextIssues = uniqBy([...prevState.issues, ...issues], issue => issue.key);
+ return {
+ issues: nextIssues,
+ issuesByLine: issuesByLine(nextIssues),
+ issueLocationsByLine: locationsByLine(nextIssues),
+ hasSourcesAfter: sources.length > LINES,
+ loadingSourcesAfter: false,
+ sources: [
+ ...prevState.sources,
+ ...this.computeCoverageStatus(sources.slice(0, LINES))
+ ],
+ symbolsByLine: {
+ ...prevState.symbolsByLine,
+ ...symbolsByLine(sources.slice(0, LINES))
+ }
+ };
+ });
+ }
+ });
});
- });
};
loadDuplications = (line /*: SourceLine */) => {
import { parseIssueFromResponse } from '../../../helpers/issues';
/*::
-export type Query = { [string]: string };
+export type Query = { [string]: string | void };
*/
/*::
// maximum possible value
const PAGE_SIZE = 500;
-function buildQuery(component /*: string */) /*: Query */ {
+function buildQuery(component /*: string */, branch /*: string | void */) /*: Query */ {
return {
additionalFields: '_all',
resolved: 'false',
componentKeys: component,
+ branch,
s: 'FILE_LINE'
};
}
});
}
-function loadIssues(
+export default function loadIssues(
component /*: string */,
fromLine /*: number */,
- toLine /*: number */
+ toLine /*: number */,
+ branch /*: string | void */
) /*: Promise<Issues> */ {
- const query = buildQuery(component);
+ const query = buildQuery(component, branch);
return new Promise(resolve => {
loadPageAndNext(query, toLine, 1).then(issues => {
resolve(issues);
});
});
}
-
-export default loadIssues;
--- /dev/null
+/*
+ * 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 * as React from 'react';
+
+interface Props {
+ className?: string;
+ color?: string;
+ size?: number;
+}
+
+export default function BranchIcon({ className, color = '#4b9fd5', size = 14 }: Props) {
+ /* eslint-disable max-len */
+ return (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ className={className}
+ height={size}
+ width={size}
+ viewBox="0 0 16 16">
+ <g transform="matrix(0.0416667,0,0,0.0416667,2.98284,-1.32102)">
+ <path
+ d="M72,368C72,361.333 69.667,355.667 65,351C60.333,346.333 54.667,344 48,344C41.333,344 35.667,346.333 31,351C26.333,355.667 24,361.333 24,368C24,374.667 26.333,380.333 31,385C35.667,389.667 41.333,392 48,392C54.667,392 60.333,389.667 65,385C69.667,380.333 72,374.667 72,368ZM72,80C72,73.333 69.667,67.667 65,63C60.333,58.333 54.667,56 48,56C41.333,56 35.667,58.333 31,63C26.333,67.667 24,73.333 24,80C24,86.667 26.333,92.333 31,97C35.667,101.667 41.333,104 48,104C54.667,104 60.333,101.667 65,97C69.667,92.333 72,86.667 72,80ZM232,112C232,105.333 229.667,99.667 225,95C220.333,90.333 214.667,88 208,88C201.333,88 195.667,90.333 191,95C186.333,99.667 184,105.333 184,112C184,118.667 186.333,124.333 191,129C195.667,133.667 201.333,136 208,136C214.667,136 220.333,133.667 225,129C229.667,124.333 232,118.667 232,112ZM256,112C256,120.667 253.833,128.708 249.5,136.125C245.167,143.542 239.333,149.333 232,153.5C231.667,201.333 212.833,235.833 175.5,257C164.167,263.333 147.25,270.083 124.75,277.25C103.417,283.917 89.292,289.833 82.375,295C75.458,300.167 72,308.5 72,320L72,326.5C79.333,330.667 85.167,336.458 89.5,343.875C93.833,351.292 96,359.333 96,368C96,381.333 91.333,392.667 82,402C72.667,411.333 61.333,416 48,416C34.667,416 23.333,411.333 14,402C4.667,392.667 0,381.333 0,368C0,359.333 2.167,351.292 6.5,343.875C10.833,336.458 16.667,330.667 24,326.5L24,121.5C16.667,117.333 10.833,111.542 6.5,104.125C2.167,96.708 0,88.667 0,80C0,66.667 4.667,55.333 14,46C23.333,36.667 34.667,32 48,32C61.333,32 72.667,36.667 82,46C91.333,55.333 96,66.667 96,80C96,88.667 93.833,96.708 89.5,104.125C85.167,111.542 79.333,117.333 72,121.5L72,245.75C81,241.417 93.833,236.667 110.5,231.5C119.667,228.667 126.958,226.208 132.375,224.125C137.792,222.042 143.667,219.458 150,216.375C156.333,213.292 161.25,210 164.75,206.5C168.25,203 171.625,198.75 174.875,193.75C178.125,188.75 180.458,182.958 181.875,176.375C183.292,169.792 184,162.167 184,153.5C176.667,149.333 170.833,143.542 166.5,136.125C162.167,128.708 160,120.667 160,112C160,98.667 164.667,87.333 174,78C183.333,68.667 194.667,64 208,64C221.333,64 232.667,68.667 242,78C251.333,87.333 256,98.667 256,112Z"
+ style={{ fill: color, fillRule: 'nonzero' }}
+ />
+ </g>
+ </svg>
+ );
+}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+
+export default function PendingIcon() {
+ /* eslint max-len: 0 */
+ return (
+ <svg width="16" height="16" className="icon-pending">
+ <g transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)">
+ <path d="M224,136L224,248C224,250.333 223.25,252.25 221.75,253.75C220.25,255.25 218.333,256 216,256L136,256C133.667,256 131.75,255.25 130.25,253.75C128.75,252.25 128,250.333 128,248L128,232C128,229.667 128.75,227.75 130.25,226.25C131.75,224.75 133.667,224 136,224L192,224L192,136C192,133.667 192.75,131.75 194.25,130.25C195.75,128.75 197.667,128 200,128L216,128C218.333,128 220.25,128.75 221.75,130.25C223.25,131.75 224,133.667 224,136ZM328,224C328,199.333 321.917,176.583 309.75,155.75C297.583,134.917 281.083,118.417 260.25,106.25C239.417,94.083 216.667,88 192,88C167.333,88 144.583,94.083 123.75,106.25C102.917,118.417 86.417,134.917 74.25,155.75C62.083,176.583 56,199.333 56,224C56,248.667 62.083,271.417 74.25,292.25C86.417,313.083 102.917,329.583 123.75,341.75C144.583,353.917 167.333,360 192,360C216.667,360 239.417,353.917 260.25,341.75C281.083,329.583 297.583,313.083 309.75,292.25C321.917,271.417 328,248.667 328,224ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z" />
+ </g>
+ </svg>
+ );
+}
}
.navbar-context-header {
+ float: left;
line-height: 30px;
font-size: 15px;
}
+++ /dev/null
-/*
- * 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 NavBar from './NavBar';
-import './ContextNavBar.css';
-
-/*::
-type Props = {
- className?: string,
- height: number
-};
-*/
-
-export default function ContextNavBar({ className, ...other } /*: Props */) {
- return <NavBar className={classNames('navbar-context', className)} {...other} />;
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import * as classNames from 'classnames';
+import NavBar from './NavBar';
+import './ContextNavBar.css';
+
+interface Props {
+ className?: string;
+ height: number;
+ [attr: string]: any;
+}
+
+export default function ContextNavBar({ className, ...other }: Props) {
+ return <NavBar className={classNames('navbar-context', className)} {...other} />;
+}
+++ /dev/null
-/*
- * 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 './NavBar.css';
-
-/*::
-type Props = {
- children?: React.Element<*>,
- className?: string,
- height: number
-};
-*/
-
-export default function NavBar({ children, className, height, ...other } /*: Props */) {
- return (
- <nav {...other} className={classNames('navbar', className)} style={{ height }}>
- <div className="navbar-inner" style={{ height }}>
- <div className="navbar-limited clearfix">
- {children}
- </div>
- </div>
- </nav>
- );
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import * as classNames from 'classnames';
+import './NavBar.css';
+
+interface Props {
+ children?: any;
+ className?: string;
+ height: number;
+}
+
+export default function NavBar({ children, className, height, ...other }: Props) {
+ return (
+ <nav {...other} className={classNames('navbar', className)} style={{ height }}>
+ <div className="navbar-inner" style={{ height }}>
+ <div className="navbar-limited clearfix">
+ {children}
+ </div>
+ </div>
+ </nav>
+ );
+}
.navbar-tabs {
display: flex;
align-items: center;
+ clear: left;
}
.navbar-tabs > li + li {
+++ /dev/null
-/*
- * 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 './NavBarTabs.css';
-
-/*::
-type Props = {
- children?: React.Element<*>,
- className?: string
-};
-*/
-
-export default function NavBarTabs({ children, className, ...other } /*: Props */) {
- return (
- <ul {...other} className={classNames('navbar-tabs', className)}>
- {children}
- </ul>
- );
-}
--- /dev/null
+/*
+ * 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 * as React from 'react';
+import * as classNames from 'classnames';
+import './NavBarTabs.css';
+
+interface Props {
+ children?: any;
+ className?: string;
+ [attr: string]: any;
+}
+
+export default function NavBarTabs({ children, className, ...other }: Props) {
+ return (
+ <ul {...other} className={classNames('navbar-tabs', className)}>
+ {children}
+ </ul>
+ );
+}
+++ /dev/null
-/*
- * 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 React from 'react';
-
-export default class PendingIcon extends React.PureComponent {
- render() {
- /* eslint max-len: 0 */
- return (
- <svg width="16" height="16" className="icon-pending">
- <g transform="matrix(0.0364583,0,0,0.0364583,1,-0.166667)">
- <path d="M224,136L224,248C224,250.333 223.25,252.25 221.75,253.75C220.25,255.25 218.333,256 216,256L136,256C133.667,256 131.75,255.25 130.25,253.75C128.75,252.25 128,250.333 128,248L128,232C128,229.667 128.75,227.75 130.25,226.25C131.75,224.75 133.667,224 136,224L192,224L192,136C192,133.667 192.75,131.75 194.25,130.25C195.75,128.75 197.667,128 200,128L216,128C218.333,128 220.25,128.75 221.75,130.25C223.25,131.75 224,133.667 224,136ZM328,224C328,199.333 321.917,176.583 309.75,155.75C297.583,134.917 281.083,118.417 260.25,106.25C239.417,94.083 216.667,88 192,88C167.333,88 144.583,94.083 123.75,106.25C102.917,118.417 86.417,134.917 74.25,155.75C62.083,176.583 56,199.333 56,224C56,248.667 62.083,271.417 74.25,292.25C86.417,313.083 102.917,329.583 123.75,341.75C144.583,353.917 167.333,360 192,360C216.667,360 239.417,353.917 260.25,341.75C281.083,329.583 297.583,313.083 309.75,292.25C321.917,271.417 328,248.667 328,224ZM384,224C384,258.833 375.417,290.958 358.25,320.375C341.083,349.792 317.792,373.083 288.375,390.25C258.958,407.417 226.833,416 192,416C157.167,416 125.042,407.417 95.625,390.25C66.208,373.083 42.917,349.792 25.75,320.375C8.583,290.958 0,258.833 0,224C0,189.167 8.583,157.042 25.75,127.625C42.917,98.208 66.208,74.917 95.625,57.75C125.042,40.583 157.167,32 192,32C226.833,32 258.958,40.583 288.375,57.75C317.792,74.917 341.083,98.208 358.25,127.625C375.417,157.042 384,189.167 384,224Z" />
- </g>
- </svg>
- );
- }
-}
},
showViewer() {
- const { key, line } = this.model.toJSON();
+ const { branch, key, line } = this.model.toJSON();
const el = document.querySelector(this.viewerRegion.el);
render(
<WithStore>
<SourceViewer
+ branch={branch}
component={key}
fromWorkspace={true}
highlightedLine={line}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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 { Branch, BranchType, ShortLivingBranch } from '../app/types';
+
+export const MAIN_BRANCH: Branch = {
+ isMain: true,
+ name: undefined,
+ type: BranchType.LONG
+};
+
+const MAIN_BRANCH_DISPLAY_NAME = 'master';
+
+export function isShortLivingBranch(branch: Branch | null): branch is ShortLivingBranch {
+ return branch != null && branch.type === BranchType.SHORT;
+}
+
+export function getBranchDisplayName(branch: Branch): string {
+ return branch.isMain ? MAIN_BRANCH_DISPLAY_NAME : branch.name;
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { stringify } from 'querystring';
+import { isShortLivingBranch } from './branches';
import { getProfilePath } from '../apps/quality-profiles/utils';
+import { Branch } from '../app/types';
interface Query {
[x: string]: string;
return { pathname: '/dashboard', query: { id: key } };
}
+export function getProjectBranchUrl(key: string, branch: Branch) {
+ if (isShortLivingBranch(branch)) {
+ return {
+ pathname: '/project/issues',
+ query: { branch: branch.name, id: key, resolved: 'false' }
+ };
+ } else {
+ return { pathname: '/dashboard', query: { id: key } };
+ }
+}
+
/**
* Generate URL for a global issues page
*/
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getLanguages } from '../api/languages';
-import { getGlobalNavigation, getComponentNavigation } from '../api/nav';
-import { getComponentData } from '../api/components';
+import { getGlobalNavigation } from '../api/nav';
import * as auth from '../api/auth';
import { getOrganizations } from '../api/organizations';
import { getMetrics } from '../api/metrics';
import { receiveLanguages } from './languages/actions';
-import { receiveComponents } from './components/actions';
import { receiveMetrics } from './metrics/actions';
import { addGlobalErrorMessage } from './globalMessages/duck';
import { parseError } from '../apps/code/utils';
onFail(dispatch)
);
-const addQualifier = project => ({
- ...project,
- qualifier: project.breadcrumbs[project.breadcrumbs.length - 1].qualifier
-});
-
-export const fetchProject = key => dispatch =>
- Promise.all([
- getComponentNavigation(key),
- getComponentData(key)
- ]).then(([componentNav, componentData]) => {
- const component = { ...componentData, ...componentNav };
- dispatch(receiveComponents([addQualifier(component)]));
- if (component.organization != null) {
- dispatch(fetchOrganizations([component.organization]));
- }
- });
-
export const doLogin = (login, password) => dispatch =>
auth.login(login, password).then(
() => {
right: auto;
}
+.dropdown-menu-shadow {
+ box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+}
+
.dropdown-header {
display: block;
padding: 3px 8px 5px;
}
}
}
+
+.menu-message {
+ display: block;
+ padding: 4px 16px;
+ line-height: 16px;
+}