@@ -18,16 +18,28 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON, post } from '../helpers/request'; | |||
import { Branch, PullRequest } from '../app/types'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export function getBranches(project: string): Promise<any> { | |||
export function getBranches(project: string): Promise<Branch[]> { | |||
return getJSON('/api/project_branches/list', { project }).then(r => r.branches, throwGlobalError); | |||
} | |||
export function deleteBranch(project: string, branch: string): Promise<void | Response> { | |||
return post('/api/project_branches/delete', { project, branch }).catch(throwGlobalError); | |||
export function getPullRequests(project: string): Promise<PullRequest[]> { | |||
return getJSON('/api/project_pull_requests/list', { project }).then( | |||
r => r.pullRequests, | |||
throwGlobalError | |||
); | |||
} | |||
export function renameBranch(project: string, name: string): Promise<void | Response> { | |||
export function deleteBranch(data: { branch: string; project: string }) { | |||
return post('/api/project_branches/delete', data).catch(throwGlobalError); | |||
} | |||
export function deletePullRequest(data: { project: string; pullRequest: string }) { | |||
return post('/api/project_pull_requests/delete', data).catch(throwGlobalError); | |||
} | |||
export function renameBranch(project: string, name: string) { | |||
return post('/api/project_branches/rename', { project, name }).catch(throwGlobalError); | |||
} |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { getJSON, postJSON, post, RequestData } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { Paging, Visibility } from '../app/types'; | |||
import { Paging, Visibility, BranchParameters } from '../app/types'; | |||
export interface BaseSearchProjectsParameters { | |||
analyzedBefore?: string; | |||
@@ -118,11 +118,8 @@ export function getComponentLeaves( | |||
} | |||
export function getComponent( | |||
componentKey: string, | |||
metrics: string[] = [], | |||
branch?: string | |||
data: { componentKey: string; metricKeys: string } & BranchParameters | |||
): Promise<any> { | |||
const data = { branch, componentKey, metricKeys: metrics.join(',') }; | |||
return getJSON('/api/measures/component', data).then(r => r.component); | |||
} | |||
@@ -130,23 +127,23 @@ export function getTree(component: string, options: RequestData = {}): Promise<a | |||
return getJSON('/api/components/tree', { ...options, component }); | |||
} | |||
export function getComponentShow(component: string, branch?: string): Promise<any> { | |||
return getJSON('/api/components/show', { component, branch }); | |||
export function getComponentShow(data: { component: string } & BranchParameters): Promise<any> { | |||
return getJSON('/api/components/show', data); | |||
} | |||
export function getParents(component: string): Promise<any> { | |||
return getComponentShow(component).then(r => r.ancestors); | |||
return getComponentShow({ component }).then(r => r.ancestors); | |||
} | |||
export function getBreadcrumbs(component: string, branch?: string): Promise<any> { | |||
return getComponentShow(component, branch).then(r => { | |||
export function getBreadcrumbs(data: { component: string } & BranchParameters): Promise<any> { | |||
return getComponentShow(data).then(r => { | |||
const reversedAncestors = [...r.ancestors].reverse(); | |||
return [...reversedAncestors, r.component]; | |||
}); | |||
} | |||
export function getComponentData(component: string, branch?: string): Promise<any> { | |||
return getComponentShow(component, branch).then(r => r.component); | |||
export function getComponentData(data: { component: string } & BranchParameters): Promise<any> { | |||
return getComponentShow(data).then(r => r.component); | |||
} | |||
export function getMyProjects(data: RequestData): Promise<any> { | |||
@@ -246,31 +243,24 @@ export function getSuggestions( | |||
return getJSON('/api/components/suggestions', data); | |||
} | |||
export function getComponentForSourceViewer(component: string, branch?: string): Promise<any> { | |||
return getJSON('/api/components/app', { component, branch }); | |||
export function getComponentForSourceViewer( | |||
data: { component: string } & BranchParameters | |||
): Promise<any> { | |||
return getJSON('/api/components/app', data); | |||
} | |||
export function getSources( | |||
component: string, | |||
from?: number, | |||
to?: number, | |||
branch?: string | |||
data: { key: string; from?: number; to?: number } & BranchParameters | |||
): Promise<any> { | |||
const data: RequestData = { key: component, branch }; | |||
if (from) { | |||
Object.assign(data, { from }); | |||
} | |||
if (to) { | |||
Object.assign(data, { to }); | |||
} | |||
return getJSON('/api/sources/lines', data).then(r => r.sources); | |||
} | |||
export function getDuplications(component: string, branch?: string): Promise<any> { | |||
return getJSON('/api/duplications/show', { key: component, branch }); | |||
export function getDuplications(data: { key: string } & BranchParameters): Promise<any> { | |||
return getJSON('/api/duplications/show', data); | |||
} | |||
export function getTests(component: string, line: number | string, branch?: string): Promise<any> { | |||
const data = { sourceFileKey: component, sourceFileLineNumber: line, branch }; | |||
export function getTests( | |||
data: { sourceFileKey: string; sourceFileLineNumber: number | string } & BranchParameters | |||
): Promise<any> { | |||
return getJSON('/api/tests/list', data).then(r => r.tests); | |||
} |
@@ -20,17 +20,13 @@ | |||
import { getJSON, RequestData, postJSON, post } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { Measure, MeasurePeriod } from '../helpers/measures'; | |||
import { Metric, CustomMeasure, Paging } from '../app/types'; | |||
import { Metric, CustomMeasure, Paging, BranchParameters } from '../app/types'; | |||
import { Period } from '../helpers/periods'; | |||
export function getMeasures( | |||
componentKey: string, | |||
metrics: string[], | |||
branch?: string | |||
data: { componentKey: string; metricKeys: string } & BranchParameters | |||
): Promise<{ metric: string; value?: string }[]> { | |||
const url = '/api/measures/component'; | |||
const data = { componentKey, metricKeys: metrics.join(','), branch }; | |||
return getJSON(url, data).then(r => r.component.measures, throwGlobalError); | |||
return getJSON('/api/measures/component', data).then(r => r.component.measures, throwGlobalError); | |||
} | |||
interface MeasureComponent { |
@@ -18,14 +18,17 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON, parseJSON, request } from '../helpers/request'; | |||
import { BranchParameters } from '../app/types'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export function getGlobalNavigation(): Promise<any> { | |||
return getJSON('/api/navigation/global'); | |||
} | |||
export function getComponentNavigation(componentKey: string, branch?: string): Promise<any> { | |||
return getJSON('/api/navigation/component', { componentKey, branch }).catch(throwGlobalError); | |||
export function getComponentNavigation( | |||
data: { componentKey: string } & BranchParameters | |||
): Promise<any> { | |||
return getJSON('/api/navigation/component', data).catch(throwGlobalError); | |||
} | |||
export function getSettingsNavigation(): Promise<any> { |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import { getJSON, postJSON, post, RequestData } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { Paging } from '../app/types'; | |||
import { Paging, BranchParameters } from '../app/types'; | |||
export interface Event { | |||
key: string; | |||
@@ -34,13 +34,9 @@ export interface Analysis { | |||
events: Event[]; | |||
} | |||
export function getProjectActivity(data: { | |||
branch?: string; | |||
project: string; | |||
category?: string; | |||
p?: number; | |||
ps?: number; | |||
}): Promise<{ analyses: Analysis[]; paging: Paging }> { | |||
export function getProjectActivity( | |||
data: { project: string; category?: string; p?: number; ps?: number } & BranchParameters | |||
): Promise<{ analyses: Analysis[]; paging: Paging }> { | |||
return getJSON('/api/project_analyses/search', data).catch(throwGlobalError); | |||
} | |||
@@ -20,10 +20,11 @@ | |||
import { omitBy } from 'lodash'; | |||
import { getJSON, RequestData, post, postJSON } from '../helpers/request'; | |||
import { TYPE_PROPERTY_SET } from '../apps/settings/constants'; | |||
import { BranchParameters } from '../app/types'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export function getDefinitions(component: string | null, branch?: string): Promise<any> { | |||
return getJSON('/api/settings/list_definitions', { branch, component }).then(r => r.definitions); | |||
export function getDefinitions(component?: string): Promise<any> { | |||
return getJSON('/api/settings/list_definitions', { component }).then(r => r.definitions); | |||
} | |||
export interface SettingValue { | |||
@@ -36,21 +37,14 @@ export interface SettingValue { | |||
} | |||
export function getValues( | |||
keys: string, | |||
component?: string, | |||
branch?: string | |||
data: { keys: string; component?: string } & BranchParameters | |||
): Promise<SettingValue[]> { | |||
return getJSON('/api/settings/values', { keys, component, branch }).then(r => r.settings); | |||
return getJSON('/api/settings/values', data).then(r => r.settings); | |||
} | |||
export function setSettingValue( | |||
definition: any, | |||
value: any, | |||
component?: string, | |||
branch?: string | |||
): Promise<void> { | |||
export function setSettingValue(definition: any, value: any, component?: string): Promise<void> { | |||
const { key } = definition; | |||
const data: RequestData = { key, component, branch }; | |||
const data: RequestData = { key, component }; | |||
if (definition.multiValues) { | |||
data.values = value; | |||
@@ -65,17 +59,16 @@ export function setSettingValue( | |||
return post('/api/settings/set', data); | |||
} | |||
export function setSimpleSettingValue(parameters: { | |||
branch?: string; | |||
component?: string; | |||
value: string; | |||
key: string; | |||
}): Promise<void | Response> { | |||
return post('/api/settings/set', parameters).catch(throwGlobalError); | |||
export function setSimpleSettingValue( | |||
data: { component?: string; value: string; key: string } & BranchParameters | |||
): Promise<void | Response> { | |||
return post('/api/settings/set', data).catch(throwGlobalError); | |||
} | |||
export function resetSettingValue(key: string, component?: string, branch?: string): Promise<void> { | |||
return post('/api/settings/reset', { keys: key, component, branch }); | |||
export function resetSettingValue( | |||
data: { keys: string; component?: string } & BranchParameters | |||
): Promise<void> { | |||
return post('/api/settings/reset', data); | |||
} | |||
export function sendTestEmail(to: string, subject: string, message: string): Promise<void> { |
@@ -18,18 +18,19 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { Paging, TestCase, CoveredFile } from '../app/types'; | |||
import { Paging, TestCase, CoveredFile, BranchParameters } from '../app/types'; | |||
import { getJSON } from '../helpers/request'; | |||
export function getTests(parameters: { | |||
branch?: string; | |||
p?: number; | |||
ps?: number; | |||
sourceFileKey?: string; | |||
sourceFileLineNumber?: number; | |||
testFileKey: string; | |||
testId?: string; | |||
}): Promise<{ paging: Paging; tests: TestCase[] }> { | |||
export function getTests( | |||
parameters: { | |||
p?: number; | |||
ps?: number; | |||
sourceFileKey?: string; | |||
sourceFileLineNumber?: number; | |||
testFileKey: string; | |||
testId?: string; | |||
} & BranchParameters | |||
): Promise<{ paging: Paging; tests: TestCase[] }> { | |||
return getJSON('/api/tests/list', parameters).catch(throwGlobalError); | |||
} | |||
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON } from '../helpers/request'; | |||
import { Paging } from '../app/types'; | |||
import { Paging, BranchParameters } from '../app/types'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export interface HistoryItem { | |||
@@ -39,25 +39,29 @@ interface TimeMachineResponse { | |||
} | |||
export function getTimeMachineData( | |||
component: string, | |||
metrics: string[], | |||
other?: { branch?: string; p?: number; ps?: number; from?: string; to?: string } | |||
data: { | |||
component: string; | |||
from?: string; | |||
metrics: string; | |||
p?: number; | |||
ps?: number; | |||
to?: string; | |||
} & BranchParameters | |||
): Promise<TimeMachineResponse> { | |||
return getJSON('/api/measures/search_history', { | |||
component, | |||
metrics: metrics.join(), | |||
ps: 1000, | |||
...other | |||
}).catch(throwGlobalError); | |||
return getJSON('/api/measures/search_history', data).catch(throwGlobalError); | |||
} | |||
export function getAllTimeMachineData( | |||
component: string, | |||
metrics: Array<string>, | |||
other?: { branch?: string; p?: number; from?: string; to?: string }, | |||
data: { | |||
component: string; | |||
metrics: string; | |||
from?: string; | |||
p?: number; | |||
to?: string; | |||
} & BranchParameters, | |||
prev?: TimeMachineResponse | |||
): Promise<TimeMachineResponse> { | |||
return getTimeMachineData(component, metrics, { ...other, ps: 1000 }).then(r => { | |||
return getTimeMachineData({ ...data, ps: 1000 }).then(r => { | |||
const result = prev | |||
? { | |||
measures: prev.measures.map((measure, idx) => ({ | |||
@@ -71,11 +75,6 @@ export function getAllTimeMachineData( | |||
if (result.paging.pageIndex * result.paging.pageSize >= result.paging.total) { | |||
return result; | |||
} | |||
return getAllTimeMachineData( | |||
component, | |||
metrics, | |||
{ ...other, p: result.paging.pageIndex + 1 }, | |||
result | |||
); | |||
return getAllTimeMachineData({ ...data, p: result.paging.pageIndex + 1 }, result); | |||
}); | |||
} |
@@ -22,25 +22,26 @@ import * as PropTypes from 'prop-types'; | |||
import { connect } from 'react-redux'; | |||
import ComponentContainerNotFound from './ComponentContainerNotFound'; | |||
import ComponentNav from './nav/component/ComponentNav'; | |||
import { Branch, Component } from '../types'; | |||
import { Component, BranchLike } from '../types'; | |||
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization'; | |||
import { getBranches } from '../../api/branches'; | |||
import { getBranches, getPullRequests } from '../../api/branches'; | |||
import { Task, getTasksForComponent } from '../../api/ce'; | |||
import { getComponentData } from '../../api/components'; | |||
import { getComponentNavigation } from '../../api/nav'; | |||
import { fetchOrganizations } from '../../store/rootActions'; | |||
import { STATUSES } from '../../apps/background-tasks/constants'; | |||
import { isPullRequest, isBranch } from '../../helpers/branches'; | |||
interface Props { | |||
children: any; | |||
fetchOrganizations: (organizations: string[]) => void; | |||
location: { | |||
query: { branch?: string; id: string }; | |||
query: { branch?: string; id: string; pullRequest?: string }; | |||
}; | |||
} | |||
interface State { | |||
branches: Branch[]; | |||
branchLikes: BranchLike[]; | |||
loading: boolean; | |||
component?: Component; | |||
currentTask?: Task; | |||
@@ -57,7 +58,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { branches: [], loading: true }; | |||
this.state = { branchLikes: [], loading: true }; | |||
} | |||
componentDidMount() { | |||
@@ -68,7 +69,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
componentWillReceiveProps(nextProps: Props) { | |||
if ( | |||
nextProps.location.query.id !== this.props.location.query.id || | |||
nextProps.location.query.branch !== this.props.location.query.branch | |||
nextProps.location.query.branch !== this.props.location.query.branch || | |||
nextProps.location.query.pullRequest !== this.props.location.query.pullRequest | |||
) { | |||
this.fetchComponent(nextProps); | |||
} | |||
@@ -84,7 +86,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
}); | |||
fetchComponent(props: Props) { | |||
const { branch, id } = props.location.query; | |||
const { branch, id: key, pullRequest } = props.location.query; | |||
this.setState({ loading: true }); | |||
const onError = (error: any) => { | |||
@@ -97,29 +99,33 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
} | |||
}; | |||
Promise.all([getComponentNavigation(id, branch), getComponentData(id, branch)]).then( | |||
([nav, data]) => { | |||
const component = this.addQualifier({ ...nav, ...data }); | |||
Promise.all([ | |||
getComponentNavigation({ componentKey: key, branch, pullRequest }), | |||
getComponentData({ component: key, branch, pullRequest }) | |||
]).then(([nav, data]) => { | |||
const component = this.addQualifier({ ...nav, ...data }); | |||
if (this.context.organizationsEnabled) { | |||
this.props.fetchOrganizations([component.organization]); | |||
} | |||
if (this.context.organizationsEnabled) { | |||
this.props.fetchOrganizations([component.organization]); | |||
} | |||
this.fetchBranches(component).then(branches => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, branches, component }); | |||
} | |||
}, onError); | |||
this.fetchBranches(component).then(branchLikes => { | |||
if (this.mounted) { | |||
this.setState({ loading: false, branchLikes, component }); | |||
} | |||
}, onError); | |||
this.fetchStatus(component); | |||
}, | |||
onError | |||
); | |||
this.fetchStatus(component); | |||
}, onError); | |||
} | |||
fetchBranches = (component: Component) => { | |||
fetchBranches = (component: Component): Promise<BranchLike[]> => { | |||
const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK'); | |||
return project ? getBranches(project.key) : Promise.resolve([]); | |||
return project | |||
? Promise.all([getBranches(project.key), getPullRequests(project.key)]).then( | |||
([branches, pullRequests]) => [...branches, ...pullRequests] | |||
) | |||
: Promise.resolve([]); | |||
}; | |||
fetchStatus = (component: Component) => { | |||
@@ -146,9 +152,9 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
handleBranchesChange = () => { | |||
if (this.mounted && this.state.component) { | |||
this.fetchBranches(this.state.component).then( | |||
branches => { | |||
branchLikes => { | |||
if (this.mounted) { | |||
this.setState({ branches }); | |||
this.setState({ branchLikes }); | |||
} | |||
}, | |||
() => {} | |||
@@ -158,22 +164,24 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
render() { | |||
const { query } = this.props.location; | |||
const { branches, component, loading } = this.state; | |||
const { branchLikes, component, loading } = this.state; | |||
if (!loading && !component) { | |||
return <ComponentContainerNotFound />; | |||
} | |||
const branch = branches.find(b => (query.branch ? b.name === query.branch : b.isMain)); | |||
const branchLike = query.pullRequest | |||
? branchLikes.find(b => isPullRequest(b) && b.key === query.pullRequest) | |||
: branchLikes.find(b => isBranch(b) && (query.branch ? b.name === query.branch : b.isMain)); | |||
return ( | |||
<div> | |||
{component && | |||
!['FIL', 'UTS'].includes(component.qualifier) && ( | |||
<ComponentNav | |||
branches={branches} | |||
currentBranch={branch} | |||
branchLikes={branchLikes} | |||
component={component} | |||
currentBranchLike={branchLike} | |||
currentTask={this.state.currentTask} | |||
isInProgress={this.state.isInProgress} | |||
isPending={this.state.isPending} | |||
@@ -186,8 +194,8 @@ export class ComponentContainer extends React.PureComponent<Props, State> { | |||
</div> | |||
) : ( | |||
React.cloneElement(this.props.children, { | |||
branch, | |||
branches, | |||
branchLike, | |||
branchLikes, | |||
component, | |||
isInProgress: this.state.isInProgress, | |||
isPending: this.state.isPending, |
@@ -19,12 +19,12 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization'; | |||
import { Component, Branch } from '../types'; | |||
import { BranchLike, Component } from '../types'; | |||
interface Props { | |||
children: JSX.Element; | |||
branch?: Branch; | |||
branches: Branch[]; | |||
branchLike?: BranchLike; | |||
branchLikes: BranchLike[]; | |||
component: Component; | |||
isInProgress?: boolean; | |||
isPending?: boolean; |
@@ -20,18 +20,24 @@ | |||
import * as React from 'react'; | |||
import { shallow, mount } from 'enzyme'; | |||
import { ComponentContainer } from '../ComponentContainer'; | |||
import { getBranches } from '../../../api/branches'; | |||
import { getBranches, getPullRequests } from '../../../api/branches'; | |||
import { getTasksForComponent } from '../../../api/ce'; | |||
import { getComponentData } from '../../../api/components'; | |||
import { getComponentNavigation } from '../../../api/nav'; | |||
jest.mock('../../../api/branches', () => ({ getBranches: jest.fn(() => Promise.resolve([])) })); | |||
jest.mock('../../../api/branches', () => ({ | |||
getBranches: jest.fn(() => Promise.resolve([])), | |||
getPullRequests: jest.fn(() => Promise.resolve([])) | |||
})); | |||
jest.mock('../../../api/ce', () => ({ | |||
getTasksForComponent: jest.fn(() => Promise.resolve({ queue: [] })) | |||
})); | |||
jest.mock('../../../api/components', () => ({ | |||
getComponentData: jest.fn(() => Promise.resolve({})) | |||
})); | |||
jest.mock('../../../api/nav', () => ({ | |||
getComponentNavigation: jest.fn(() => | |||
Promise.resolve({ | |||
@@ -49,10 +55,11 @@ jest.mock('../nav/component/ComponentNav', () => ({ | |||
const Inner = () => <div />; | |||
beforeEach(() => { | |||
(getBranches as jest.Mock<any>).mockClear(); | |||
(getComponentData as jest.Mock<any>).mockClear(); | |||
(getComponentNavigation as jest.Mock<any>).mockClear(); | |||
(getTasksForComponent as jest.Mock<any>).mockClear(); | |||
(getBranches as jest.Mock).mockClear(); | |||
(getPullRequests as jest.Mock).mockClear(); | |||
(getComponentData as jest.Mock).mockClear(); | |||
(getComponentNavigation as jest.Mock).mockClear(); | |||
(getTasksForComponent as jest.Mock).mockClear(); | |||
}); | |||
it('changes component', () => { | |||
@@ -90,8 +97,9 @@ it("loads branches for module's project", async () => { | |||
await new Promise(setImmediate); | |||
expect(getBranches).toBeCalledWith('projectKey'); | |||
expect(getComponentData).toBeCalledWith('moduleKey', undefined); | |||
expect(getComponentNavigation).toBeCalledWith('moduleKey', undefined); | |||
expect(getPullRequests).toBeCalledWith('projectKey'); | |||
expect(getComponentData).toBeCalledWith({ component: 'moduleKey', branch: undefined }); | |||
expect(getComponentNavigation).toBeCalledWith({ componentKey: 'moduleKey', branch: undefined }); | |||
}); | |||
it("doesn't load branches portfolio", async () => { | |||
@@ -103,8 +111,12 @@ it("doesn't load branches portfolio", async () => { | |||
await new Promise(setImmediate); | |||
expect(getBranches).not.toBeCalled(); | |||
expect(getComponentData).toBeCalledWith('portfolioKey', undefined); | |||
expect(getComponentNavigation).toBeCalledWith('portfolioKey', undefined); | |||
expect(getPullRequests).not.toBeCalled(); | |||
expect(getComponentData).toBeCalledWith({ component: 'portfolioKey', branch: undefined }); | |||
expect(getComponentNavigation).toBeCalledWith({ | |||
componentKey: 'portfolioKey', | |||
branch: undefined | |||
}); | |||
wrapper.update(); | |||
expect(wrapper.find(Inner).exists()).toBeTruthy(); | |||
}); | |||
@@ -123,6 +135,7 @@ it('updates branches on change', () => { | |||
}); | |||
(wrapper.find(Inner).prop('onBranchesChange') as Function)(); | |||
expect(getBranches).toBeCalledWith('projectKey'); | |||
expect(getPullRequests).toBeCalledWith('projectKey'); | |||
}); | |||
it('loads organization', async () => { |
@@ -25,6 +25,10 @@ | |||
font-size: var(--baseFontSize); | |||
} | |||
.navbar-context-meta-branch-menu-title { | |||
padding-left: calc(3 * var(--gridSize)); | |||
} | |||
.navbar-context-meta-branch-menu-item { | |||
display: flex !important; | |||
justify-content: space-between; |
@@ -24,15 +24,15 @@ import ComponentNavMenu from './ComponentNavMenu'; | |||
import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif'; | |||
import RecentHistory from '../../RecentHistory'; | |||
import * as theme from '../../../theme'; | |||
import { Branch, Component } from '../../../types'; | |||
import { BranchLike, Component } from '../../../types'; | |||
import ContextNavBar from '../../../../components/nav/ContextNavBar'; | |||
import { Task } from '../../../../api/ce'; | |||
import { STATUSES } from '../../../../apps/background-tasks/constants'; | |||
import './ComponentNav.css'; | |||
interface Props { | |||
branches: Branch[]; | |||
currentBranch?: Branch; | |||
branchLikes: BranchLike[]; | |||
currentBranchLike: BranchLike | undefined; | |||
component: Component; | |||
currentTask?: Task; | |||
isInProgress?: boolean; | |||
@@ -85,15 +85,18 @@ export default class ComponentNav extends React.PureComponent<Props> { | |||
height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw} | |||
notif={notifComponent}> | |||
<ComponentNavHeader | |||
branches={this.props.branches} | |||
branchLikes={this.props.branchLikes} | |||
component={this.props.component} | |||
currentBranch={this.props.currentBranch} | |||
currentBranchLike={this.props.currentBranchLike} | |||
// to close dropdown on any location change | |||
location={this.props.location} | |||
/> | |||
<ComponentNavMeta branch={this.props.currentBranch} component={this.props.component} /> | |||
<ComponentNavMeta | |||
branchLike={this.props.currentBranchLike} | |||
component={this.props.component} | |||
/> | |||
<ComponentNavMenu | |||
branch={this.props.currentBranch} | |||
branchLike={this.props.currentBranchLike} | |||
component={this.props.component} | |||
// to re-render selected menu item | |||
location={this.props.location} |
@@ -20,22 +20,28 @@ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import * as PropTypes from 'prop-types'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import ComponentNavBranchesMenu from './ComponentNavBranchesMenu'; | |||
import SingleBranchHelperPopup from './SingleBranchHelperPopup'; | |||
import NoBranchSupportPopup from './NoBranchSupportPopup'; | |||
import { Branch, Component } from '../../../types'; | |||
import { BranchLike, Component } from '../../../types'; | |||
import * as theme from '../../../theme'; | |||
import BranchIcon from '../../../../components/icons-components/BranchIcon'; | |||
import { isShortLivingBranch } from '../../../../helpers/branches'; | |||
import { | |||
isShortLivingBranch, | |||
isSameBranchLike, | |||
getBranchLikeDisplayName, | |||
isPullRequest | |||
} from '../../../../helpers/branches'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import HelpIcon from '../../../../components/icons-components/HelpIcon'; | |||
import BubblePopupHelper from '../../../../components/common/BubblePopupHelper'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
interface Props { | |||
branches: Branch[]; | |||
branchLikes: BranchLike[]; | |||
component: Component; | |||
currentBranch: Branch; | |||
currentBranchLike: BranchLike; | |||
location?: any; | |||
} | |||
@@ -69,7 +75,7 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
componentWillReceiveProps(nextProps: Props) { | |||
if ( | |||
nextProps.component !== this.props.component || | |||
this.differentBranches(nextProps.currentBranch, this.props.currentBranch) || | |||
!isSameBranchLike(nextProps.currentBranchLike, this.props.currentBranchLike) || | |||
nextProps.location !== this.props.location | |||
) { | |||
this.setState({ dropdownOpen: false, singleBranchPopupOpen: false }); | |||
@@ -80,11 +86,6 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
this.mounted = false; | |||
} | |||
differentBranches(a: Branch, b: Branch) { | |||
// if main branch changes name, we should not close the dropdown | |||
return a.isMain && b.isMain ? false : a.name !== b.name; | |||
} | |||
handleClick = (event: React.SyntheticEvent<HTMLElement>) => { | |||
event.preventDefault(); | |||
event.stopPropagation(); | |||
@@ -130,32 +131,46 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
const { configuration } = this.props.component; | |||
return this.state.dropdownOpen ? ( | |||
<ComponentNavBranchesMenu | |||
branches={this.props.branches} | |||
branchLikes={this.props.branchLikes} | |||
canAdmin={configuration && configuration.showSettings} | |||
component={this.props.component} | |||
currentBranch={this.props.currentBranch} | |||
currentBranchLike={this.props.currentBranchLike} | |||
onClose={this.closeDropdown} | |||
/> | |||
) : null; | |||
}; | |||
renderMergeBranch = () => { | |||
const { currentBranch } = this.props; | |||
if (!isShortLivingBranch(currentBranch)) { | |||
const { currentBranchLike } = this.props; | |||
if (isShortLivingBranch(currentBranchLike)) { | |||
return currentBranchLike.isOrphan ? ( | |||
<span className="note big-spacer-left text-lowercase"> | |||
{translate('branches.orphan_branch')} | |||
<Tooltip overlay={translate('branches.orphan_branches.tooltip')}> | |||
<i className="icon-help spacer-left" /> | |||
</Tooltip> | |||
</span> | |||
) : ( | |||
<span className="note big-spacer-left text-lowercase"> | |||
{translate('from')} <strong>{currentBranchLike.mergeBranch}</strong> | |||
</span> | |||
); | |||
} else if (isPullRequest(currentBranchLike)) { | |||
return ( | |||
<span className="note big-spacer-left text-lowercase"> | |||
<FormattedMessage | |||
defaultMessage={translate('branches.pull_request.for_merge_into_x_from_y')} | |||
id="branches.pull_request.for_merge_into_x_from_y" | |||
values={{ | |||
base: <strong>{currentBranchLike.base}</strong>, | |||
branch: <strong>{currentBranchLike.branch}</strong> | |||
}} | |||
/> | |||
</span> | |||
); | |||
} else { | |||
return null; | |||
} | |||
return currentBranch.isOrphan ? ( | |||
<span className="note big-spacer-left text-lowercase"> | |||
{translate('branches.orphan_branch')} | |||
<Tooltip overlay={translate('branches.orphan_branches.tooltip')}> | |||
<i className="icon-help spacer-left" /> | |||
</Tooltip> | |||
</span> | |||
) : ( | |||
<span className="note big-spacer-left text-lowercase"> | |||
{translate('from')} <strong>{currentBranch.mergeBranch}</strong> | |||
</span> | |||
); | |||
}; | |||
renderSingleBranchPopup = () => ( | |||
@@ -187,27 +202,33 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
); | |||
render() { | |||
const { branches, currentBranch } = this.props; | |||
const { branchLikes, currentBranchLike } = this.props; | |||
if (this.context.onSonarCloud && !this.context.branchesEnabled) { | |||
return null; | |||
} | |||
const displayName = getBranchLikeDisplayName(currentBranchLike); | |||
if (!this.context.branchesEnabled) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon branch={currentBranch} className="little-spacer-right" fill={theme.gray80} /> | |||
<span className="note">{currentBranch.name}</span> | |||
<BranchIcon | |||
branchLike={currentBranchLike} | |||
className="little-spacer-right" | |||
fill={theme.gray80} | |||
/> | |||
<span className="note">{displayName}</span> | |||
{this.renderNoBranchSupportPopup()} | |||
</div> | |||
); | |||
} | |||
if (branches.length < 2) { | |||
if (branchLikes.length < 2) { | |||
return ( | |||
<div className="navbar-context-branches"> | |||
<BranchIcon branch={currentBranch} className="little-spacer-right" /> | |||
<span className="note">{currentBranch.name}</span> | |||
<BranchIcon branchLike={currentBranchLike} className="little-spacer-right" /> | |||
<span className="note">{displayName}</span> | |||
{this.renderSingleBranchPopup()} | |||
</div> | |||
); | |||
@@ -219,9 +240,9 @@ export default class ComponentNavBranch extends React.PureComponent<Props, State | |||
open: this.state.dropdownOpen | |||
})}> | |||
<a className="link-base-color link-no-underline" href="#" onClick={this.handleClick}> | |||
<BranchIcon branch={currentBranch} className="little-spacer-right" /> | |||
<Tooltip overlay={currentBranch.name} mouseEnterDelay={1}> | |||
<span className="text-limited text-top">{currentBranch.name}</span> | |||
<BranchIcon branchLike={currentBranchLike} className="little-spacer-right" /> | |||
<Tooltip overlay={displayName} mouseEnterDelay={1}> | |||
<span className="text-limited text-top">{displayName}</span> | |||
</Tooltip> | |||
<i className="icon-dropdown little-spacer-left" /> | |||
</a> |
@@ -21,28 +21,32 @@ import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { Link } from 'react-router'; | |||
import ComponentNavBranchesMenuItem from './ComponentNavBranchesMenuItem'; | |||
import { Branch, Component } from '../../../types'; | |||
import { BranchLike, Component } from '../../../types'; | |||
import { | |||
sortBranchesAsTree, | |||
isLongLivingBranch, | |||
isShortLivingBranch | |||
isShortLivingBranch, | |||
isSameBranchLike, | |||
getBranchLikeKey, | |||
isPullRequest, | |||
isBranch | |||
} from '../../../../helpers/branches'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getProjectBranchUrl } from '../../../../helpers/urls'; | |||
import { getBranchLikeUrl } from '../../../../helpers/urls'; | |||
import SearchBox from '../../../../components/controls/SearchBox'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
interface Props { | |||
branches: Branch[]; | |||
branchLikes: BranchLike[]; | |||
canAdmin?: boolean; | |||
component: Component; | |||
currentBranch: Branch; | |||
currentBranchLike: BranchLike; | |||
onClose: () => void; | |||
} | |||
interface State { | |||
query: string; | |||
selected: string | null; | |||
selected: BranchLike | undefined; | |||
} | |||
export default class ComponentNavBranchesMenu extends React.PureComponent<Props, State> { | |||
@@ -54,7 +58,7 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { query: '', selected: null }; | |||
this.state = { query: '', selected: undefined }; | |||
} | |||
componentDidMount() { | |||
@@ -65,10 +69,16 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
window.removeEventListener('click', this.handleClickOutside); | |||
} | |||
getFilteredBranches = () => | |||
sortBranchesAsTree(this.props.branches).filter(branch => | |||
branch.name.toLowerCase().includes(this.state.query.toLowerCase()) | |||
); | |||
getFilteredBranchLikes = () => { | |||
const query = this.state.query.toLowerCase(); | |||
return sortBranchesAsTree(this.props.branchLikes).filter(branchLike => { | |||
const matchBranchName = isBranch(branchLike) && branchLike.name.toLowerCase().includes(query); | |||
const matchPullRequestTitleOrId = | |||
isPullRequest(branchLike) && | |||
(branchLike.title.includes(query) || branchLike.key.includes(query)); | |||
return matchBranchName || matchPullRequestTitleOrId; | |||
}); | |||
}; | |||
handleClickOutside = (event: Event) => { | |||
if (!this.node || !this.node.contains(event.target as HTMLElement)) { | |||
@@ -76,7 +86,7 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
} | |||
}; | |||
handleSearchChange = (query: string) => this.setState({ query, selected: null }); | |||
handleSearchChange = (query: string) => this.setState({ query, selected: undefined }); | |||
handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { | |||
switch (event.keyCode) { | |||
@@ -99,32 +109,31 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
openSelected = () => { | |||
const selected = this.getSelected(); | |||
const branch = this.getFilteredBranches().find(branch => branch.name === selected); | |||
if (branch) { | |||
this.context.router.push(this.getProjectBranchUrl(branch)); | |||
if (selected) { | |||
this.context.router.push(this.getProjectBranchUrl(selected)); | |||
} | |||
}; | |||
selectPrevious = () => { | |||
const selected = this.getSelected(); | |||
const branches = this.getFilteredBranches(); | |||
const index = branches.findIndex(branch => branch.name === selected); | |||
const branchLikes = this.getFilteredBranchLikes(); | |||
const index = branchLikes.findIndex(b => isSameBranchLike(b, selected)); | |||
if (index > 0) { | |||
this.setState({ selected: branches[index - 1].name }); | |||
this.setState({ selected: branchLikes[index - 1] }); | |||
} | |||
}; | |||
selectNext = () => { | |||
const selected = this.getSelected(); | |||
const branches = this.getFilteredBranches(); | |||
const index = branches.findIndex(branch => branch.name === selected); | |||
const branches = this.getFilteredBranchLikes(); | |||
const index = branches.findIndex(b => isSameBranchLike(b, selected)); | |||
if (index >= 0 && index < branches.length - 1) { | |||
this.setState({ selected: branches[index + 1].name }); | |||
this.setState({ selected: branches[index + 1] }); | |||
} | |||
}; | |||
handleSelect = (branch: Branch) => { | |||
this.setState({ selected: branch.name }); | |||
handleSelect = (branchLike: BranchLike) => { | |||
this.setState({ selected: branchLike }); | |||
}; | |||
getSelected = () => { | |||
@@ -132,21 +141,24 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
return this.state.selected; | |||
} | |||
const branches = this.getFilteredBranches(); | |||
if (branches.find(b => b.name === this.props.currentBranch.name)) { | |||
return this.props.currentBranch.name; | |||
const branchLikes = this.getFilteredBranchLikes(); | |||
if (branchLikes.find(b => isSameBranchLike(b, this.props.currentBranchLike))) { | |||
return this.props.currentBranchLike; | |||
} | |||
if (branches.length > 0) { | |||
return branches[0].name; | |||
if (branchLikes.length > 0) { | |||
return branchLikes[0]; | |||
} | |||
return undefined; | |||
}; | |||
getProjectBranchUrl = (branch: Branch) => getProjectBranchUrl(this.props.component.key, branch); | |||
getProjectBranchUrl = (branchLike: BranchLike) => | |||
getBranchLikeUrl(this.props.component.key, branchLike); | |||
isSelected = (branch: Branch) => branch.name === this.getSelected(); | |||
isOrphan = (branchLike: BranchLike) => { | |||
return (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && branchLike.isOrphan; | |||
}; | |||
renderSearch = () => ( | |||
<div className="menu-search"> | |||
@@ -161,21 +173,26 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
); | |||
renderBranchesList = () => { | |||
const branches = this.getFilteredBranches(); | |||
const branchLikes = this.getFilteredBranchLikes(); | |||
const selected = this.getSelected(); | |||
if (branches.length === 0) { | |||
if (branchLikes.length === 0) { | |||
return <div className="menu-message note">{translate('no_results')}</div>; | |||
} | |||
const items = branches.map((branch, index) => { | |||
const isOrphan = isShortLivingBranch(branch) && branch.isOrphan; | |||
const previous = index > 0 ? branches[index - 1] : undefined; | |||
const isPreviousOrphan = isShortLivingBranch(previous) ? previous.isOrphan : false; | |||
const showDivider = isLongLivingBranch(branch) || (isOrphan && !isPreviousOrphan); | |||
const items = branchLikes.map((branchLike, index) => { | |||
const isOrphan = this.isOrphan(branchLike); | |||
const previous = index > 0 ? branchLikes[index - 1] : undefined; | |||
const isPreviousOrphan = previous !== undefined && this.isOrphan(previous); | |||
const showDivider = isLongLivingBranch(branchLike) || (isOrphan && !isPreviousOrphan); | |||
const showOrphanHeader = isOrphan && !isPreviousOrphan; | |||
const showPullRequestHeader = | |||
!showOrphanHeader && isPullRequest(branchLike) && !isPullRequest(previous); | |||
const showShortLivingBranchHeader = | |||
!showOrphanHeader && isShortLivingBranch(branchLike) && !isShortLivingBranch(previous); | |||
return ( | |||
<React.Fragment key={branch.name}> | |||
<React.Fragment key={getBranchLikeKey(branchLike)}> | |||
{showDivider && <li className="divider" />} | |||
{showOrphanHeader && ( | |||
<li className="dropdown-header"> | |||
@@ -185,12 +202,22 @@ export default class ComponentNavBranchesMenu extends React.PureComponent<Props, | |||
</Tooltip> | |||
</li> | |||
)} | |||
{showPullRequestHeader && ( | |||
<li className="dropdown-header navbar-context-meta-branch-menu-title"> | |||
{translate('branches.pull_requests')} | |||
</li> | |||
)} | |||
{showShortLivingBranchHeader && ( | |||
<li className="dropdown-header navbar-context-meta-branch-menu-title"> | |||
{translate('branches.short_lived_branches')} | |||
</li> | |||
)} | |||
<ComponentNavBranchesMenuItem | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={this.props.component} | |||
key={branch.name} | |||
key={getBranchLikeKey(branchLike)} | |||
onSelect={this.handleSelect} | |||
selected={branch.name === selected} | |||
selected={isSameBranchLike(branchLike, selected)} | |||
/> | |||
</React.Fragment> | |||
); |
@@ -21,47 +21,55 @@ import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import * as classNames from 'classnames'; | |||
import BranchStatus from '../../../../components/common/BranchStatus'; | |||
import { Branch, Component } from '../../../types'; | |||
import { BranchLike, Component } from '../../../types'; | |||
import BranchIcon from '../../../../components/icons-components/BranchIcon'; | |||
import { isShortLivingBranch } from '../../../../helpers/branches'; | |||
import { | |||
isShortLivingBranch, | |||
getBranchLikeDisplayName, | |||
getBranchLikeKey, | |||
isMainBranch, | |||
isPullRequest | |||
} from '../../../../helpers/branches'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getProjectBranchUrl } from '../../../../helpers/urls'; | |||
import { getBranchLikeUrl } from '../../../../helpers/urls'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
export interface Props { | |||
branch: Branch; | |||
branchLike: BranchLike; | |||
component: Component; | |||
onSelect: (branch: Branch) => void; | |||
onSelect: (branchLike: BranchLike) => void; | |||
selected: boolean; | |||
} | |||
export default function ComponentNavBranchesMenuItem({ branch, ...props }: Props) { | |||
export default function ComponentNavBranchesMenuItem({ branchLike, ...props }: Props) { | |||
const handleMouseEnter = () => { | |||
props.onSelect(branch); | |||
props.onSelect(branchLike); | |||
}; | |||
const displayName = getBranchLikeDisplayName(branchLike); | |||
const shouldBeIndented = | |||
(isShortLivingBranch(branchLike) && !branchLike.isOrphan) || isPullRequest(branchLike); | |||
return ( | |||
<li key={branch.name} onMouseEnter={handleMouseEnter}> | |||
<Tooltip mouseEnterDelay={0.5} overlay={branch.name} placement="right"> | |||
<li key={getBranchLikeKey(branchLike)} onMouseEnter={handleMouseEnter}> | |||
<Tooltip mouseEnterDelay={0.5} overlay={displayName} placement="right"> | |||
<Link | |||
className={classNames('navbar-context-meta-branch-menu-item', { | |||
active: props.selected | |||
})} | |||
to={getProjectBranchUrl(props.component.key, branch)}> | |||
to={getBranchLikeUrl(props.component.key, branchLike)}> | |||
<div className="navbar-context-meta-branch-menu-item-name text-ellipsis"> | |||
<BranchIcon | |||
branch={branch} | |||
className={classNames('little-spacer-right', { | |||
'big-spacer-left': isShortLivingBranch(branch) && !branch.isOrphan | |||
})} | |||
branchLike={branchLike} | |||
className={classNames('little-spacer-right', { 'big-spacer-left': shouldBeIndented })} | |||
/> | |||
{branch.name} | |||
{branch.isMain && ( | |||
{displayName} | |||
{isMainBranch(branchLike) && ( | |||
<div className="outline-badge spacer-left">{translate('branches.main_branch')}</div> | |||
)} | |||
</div> | |||
<div className="big-spacer-left note"> | |||
<BranchStatus branch={branch} concise={true} /> | |||
<BranchStatus branchLike={branchLike} concise={true} /> | |||
</div> | |||
</Link> | |||
</Tooltip> |
@@ -21,7 +21,7 @@ import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { Link } from 'react-router'; | |||
import ComponentNavBranch from './ComponentNavBranch'; | |||
import { Component, Organization, Branch, Breadcrumb } from '../../../types'; | |||
import { Component, Organization, BranchLike, Breadcrumb } from '../../../types'; | |||
import QualifierIcon from '../../../../components/shared/QualifierIcon'; | |||
import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer'; | |||
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar'; | |||
@@ -37,9 +37,9 @@ interface StateProps { | |||
} | |||
interface OwnProps { | |||
branches: Branch[]; | |||
branchLikes: BranchLike[]; | |||
component: Component; | |||
currentBranch?: Branch; | |||
currentBranchLike: BranchLike | undefined; | |||
location?: any; | |||
} | |||
@@ -70,11 +70,11 @@ export function ComponentNavHeader(props: Props) { | |||
{component.visibility === 'private' && ( | |||
<PrivateBadge className="spacer-left" qualifier={component.qualifier} /> | |||
)} | |||
{props.currentBranch && ( | |||
{props.currentBranchLike && ( | |||
<ComponentNavBranch | |||
branches={props.branches} | |||
branchLikes={props.branchLikes} | |||
component={component} | |||
currentBranch={props.currentBranch} | |||
currentBranchLike={props.currentBranchLike} | |||
// to close dropdown on any location change | |||
location={props.location} | |||
/> |
@@ -21,12 +21,13 @@ import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import * as classNames from 'classnames'; | |||
import * as PropTypes from 'prop-types'; | |||
import { Branch, Component, Extension } from '../../../types'; | |||
import { BranchLike, Component, Extension } from '../../../types'; | |||
import NavBarTabs from '../../../../components/nav/NavBarTabs'; | |||
import { | |||
isShortLivingBranch, | |||
getBranchName, | |||
isLongLivingBranch | |||
isPullRequest, | |||
isMainBranch, | |||
getBranchLikeQuery | |||
} from '../../../../helpers/branches'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
@@ -47,7 +48,7 @@ const SETTINGS_URLS = [ | |||
]; | |||
interface Props { | |||
branch?: Branch; | |||
branchLike: BranchLike | undefined; | |||
component: Component; | |||
location?: any; | |||
} | |||
@@ -78,23 +79,21 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
return this.props.component.configuration || {}; | |||
} | |||
getQuery = () => { | |||
return { id: this.props.component.key, ...getBranchLikeQuery(this.props.branchLike) }; | |||
}; | |||
renderDashboardLink() { | |||
if (isShortLivingBranch(this.props.branch)) { | |||
const { branchLike } = this.props; | |||
if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) { | |||
return null; | |||
} | |||
const pathname = this.isPortfolio() ? '/portfolio' : '/dashboard'; | |||
return ( | |||
<li> | |||
<Link | |||
to={{ | |||
pathname, | |||
query: { | |||
branch: getBranchName(this.props.branch), | |||
id: this.props.component.key | |||
} | |||
}} | |||
activeClassName="active"> | |||
<Link activeClassName="active" to={{ pathname, query: this.getQuery() }}> | |||
{translate('overview.page')} | |||
</Link> | |||
</li> | |||
@@ -108,15 +107,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
return ( | |||
<li> | |||
<Link | |||
to={{ | |||
pathname: '/code', | |||
query: { | |||
branch: getBranchName(this.props.branch), | |||
id: this.props.component.key | |||
} | |||
}} | |||
activeClassName="active"> | |||
<Link to={{ pathname: '/code', query: this.getQuery() }} activeClassName="active"> | |||
{this.isPortfolio() || this.isApplication() | |||
? translate('view_projects.page') | |||
: translate('code.page')} | |||
@@ -126,20 +117,16 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
} | |||
renderActivityLink() { | |||
if (isShortLivingBranch(this.props.branch)) { | |||
const { branchLike } = this.props; | |||
if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) { | |||
return null; | |||
} | |||
return ( | |||
<li> | |||
<Link | |||
to={{ | |||
pathname: '/project/activity', | |||
query: { | |||
branch: getBranchName(this.props.branch), | |||
id: this.props.component.key | |||
} | |||
}} | |||
to={{ pathname: '/project/activity', query: this.getQuery() }} | |||
activeClassName="active"> | |||
{translate('project_activity.page')} | |||
</Link> | |||
@@ -153,16 +140,9 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
return ( | |||
<li> | |||
<Link | |||
to={{ | |||
pathname: '/project/issues', | |||
query: { | |||
branch: getBranchName(this.props.branch), | |||
id: this.props.component.key, | |||
resolved: 'false' | |||
} | |||
}} | |||
activeClassName="active" | |||
className={classNames({ active: isIssuesActive })} | |||
activeClassName="active"> | |||
to={{ pathname: '/project/issues', query: { ...this.getQuery(), resolved: 'false' } }}> | |||
{translate('issues.page')} | |||
</Link> | |||
</li> | |||
@@ -170,20 +150,16 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
} | |||
renderComponentMeasuresLink() { | |||
if (isShortLivingBranch(this.props.branch)) { | |||
const { branchLike } = this.props; | |||
if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) { | |||
return null; | |||
} | |||
return ( | |||
<li> | |||
<Link | |||
to={{ | |||
pathname: '/component_measures', | |||
query: { | |||
branch: getBranchName(this.props.branch), | |||
id: this.props.component.key | |||
} | |||
}} | |||
to={{ pathname: '/component_measures', query: this.getQuery() }} | |||
activeClassName="active"> | |||
{translate('layout.measures')} | |||
</Link> | |||
@@ -192,30 +168,14 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
} | |||
renderAdministration() { | |||
const { branch } = this.props; | |||
const { branchLike } = this.props; | |||
if (!this.getConfiguration().showSettings || (branch && !branch.isMain)) { | |||
if (!this.getConfiguration().showSettings || (branchLike && !isMainBranch(branchLike))) { | |||
return null; | |||
} | |||
const isSettingsActive = SETTINGS_URLS.some(url => window.location.href.indexOf(url) !== -1); | |||
if (isLongLivingBranch(branch)) { | |||
return ( | |||
<li> | |||
<Link | |||
className={classNames({ active: isSettingsActive })} | |||
id="component-navigation-admin" | |||
to={{ | |||
pathname: '/project/settings', | |||
query: { branch: getBranchName(branch), id: this.props.component.key } | |||
}}> | |||
{translate('branches.branch_settings')} | |||
</Link> | |||
</li> | |||
); | |||
} | |||
const adminLinks = this.renderAdministrationLinks(); | |||
if (!adminLinks.some(link => link != null)) { | |||
return null; | |||
@@ -260,13 +220,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
return ( | |||
<li key="settings"> | |||
<Link | |||
to={{ | |||
pathname: '/project/settings', | |||
query: { | |||
branch: getBranchName(this.props.branch), | |||
id: this.props.component.key | |||
} | |||
}} | |||
to={{ pathname: '/project/settings', query: this.getQuery() }} | |||
activeClassName="active"> | |||
{translate('project_settings.page')} | |||
</Link> | |||
@@ -448,7 +402,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
}; | |||
renderAdminExtensions() { | |||
if (this.props.branch && !this.props.branch.isMain) { | |||
if (this.props.branchLike && !isMainBranch(this.props.branchLike)) { | |||
return []; | |||
} | |||
const extensions = this.getConfiguration().extensions || []; | |||
@@ -457,7 +411,7 @@ export default class ComponentNavMenu extends React.PureComponent<Props> { | |||
renderExtensions() { | |||
const extensions = this.props.component.extensions || []; | |||
if (!extensions.length || (this.props.branch && !this.props.branch.isMain)) { | |||
if (!extensions.length || (this.props.branchLike && !isMainBranch(this.props.branchLike))) { | |||
return null; | |||
} | |||
@@ -19,7 +19,14 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { Branch, Component, CurrentUser, isLoggedIn, HomePageType, HomePage } from '../../../types'; | |||
import { | |||
BranchLike, | |||
Component, | |||
CurrentUser, | |||
isLoggedIn, | |||
HomePageType, | |||
HomePage | |||
} from '../../../types'; | |||
import BranchStatus from '../../../../components/common/BranchStatus'; | |||
import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter'; | |||
import Favorite from '../../../../components/controls/Favorite'; | |||
@@ -28,7 +35,8 @@ import Tooltip from '../../../../components/controls/Tooltip'; | |||
import { | |||
isShortLivingBranch, | |||
isLongLivingBranch, | |||
getBranchName | |||
isMainBranch, | |||
isPullRequest | |||
} from '../../../../helpers/branches'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getCurrentUser } from '../../../../store/rootReducer'; | |||
@@ -38,27 +46,15 @@ interface StateProps { | |||
} | |||
interface Props extends StateProps { | |||
branch?: Branch; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
} | |||
export function ComponentNavMeta({ branch, component, currentUser }: Props) { | |||
const shortBranch = isShortLivingBranch(branch); | |||
const mainBranch = !branch || branch.isMain; | |||
const longBranch = isLongLivingBranch(branch); | |||
let currentPage: HomePage | undefined; | |||
if (component.qualifier === 'VW' || component.qualifier === 'SVW') { | |||
currentPage = { type: HomePageType.Portfolio, component: component.key }; | |||
} else if (component.qualifier === 'APP') { | |||
currentPage = { type: HomePageType.Application, component: component.key }; | |||
} else if (component.qualifier === 'TRK') { | |||
currentPage = { | |||
type: HomePageType.Project, | |||
component: component.key, | |||
branch: getBranchName(branch) | |||
}; | |||
} | |||
export function ComponentNavMeta({ branchLike, component, currentUser }: Props) { | |||
const mainBranch = !branchLike || isMainBranch(branchLike); | |||
const longBranch = isLongLivingBranch(branchLike); | |||
const currentPage = getCurrentPage(component, branchLike); | |||
const displayVersion = component.version !== undefined && (mainBranch || longBranch); | |||
return ( | |||
<div className="navbar-context-meta"> | |||
@@ -67,14 +63,13 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) { | |||
<DateTimeFormatter date={component.analysisDate} /> | |||
</div> | |||
)} | |||
{component.version && | |||
!shortBranch && ( | |||
<Tooltip mouseEnterDelay={0.5} overlay={`${translate('version')} ${component.version}`}> | |||
<div className="spacer-left text-limited"> | |||
{translate('version')} {component.version} | |||
</div> | |||
</Tooltip> | |||
)} | |||
{displayVersion && ( | |||
<Tooltip mouseEnterDelay={0.5} overlay={`${translate('version')} ${component.version}`}> | |||
<div className="spacer-left text-limited"> | |||
{translate('version')} {component.version} | |||
</div> | |||
</Tooltip> | |||
)} | |||
{isLoggedIn(currentUser) && ( | |||
<div className="navbar-context-meta-secondary"> | |||
{mainBranch && ( | |||
@@ -90,15 +85,36 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) { | |||
)} | |||
</div> | |||
)} | |||
{shortBranch && ( | |||
{(isShortLivingBranch(branchLike) || isPullRequest(branchLike)) && ( | |||
<div className="navbar-context-meta-secondary"> | |||
<BranchStatus branch={branch!} /> | |||
{isPullRequest(branchLike) && | |||
branchLike.url !== undefined && ( | |||
<a className="big-spacer-right" href={branchLike.url} rel="nofollow" target="_blank"> | |||
{translate('branches.see_the_pr')} | |||
<i className="icon-detach little-spacer-left" /> | |||
</a> | |||
)} | |||
<BranchStatus branchLike={branchLike} /> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
function getCurrentPage(component: Component, branchLike: BranchLike | undefined) { | |||
let currentPage: HomePage | undefined; | |||
if (component.qualifier === 'VW' || component.qualifier === 'SVW') { | |||
currentPage = { type: HomePageType.Portfolio, component: component.key }; | |||
} else if (component.qualifier === 'APP') { | |||
currentPage = { type: HomePageType.Application, component: component.key }; | |||
} else if (component.qualifier === 'TRK') { | |||
const branch = | |||
isMainBranch(branchLike) || isLongLivingBranch(branchLike) ? branchLike.name : undefined; | |||
currentPage = { type: HomePageType.Project, component: component.key, branch }; | |||
} | |||
return currentPage; | |||
} | |||
const mapStateToProps = (state: any): StateProps => ({ | |||
currentUser: getCurrentUser(state) | |||
}); |
@@ -30,7 +30,14 @@ const component = { | |||
}; | |||
it('renders', () => { | |||
const wrapper = shallow(<ComponentNav branches={[]} component={component} location={{}} />); | |||
const wrapper = shallow( | |||
<ComponentNav | |||
branchLikes={[]} | |||
component={component} | |||
currentBranchLike={undefined} | |||
location={{}} | |||
/> | |||
); | |||
wrapper.setState({ isInProgress: true, isPending: true }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); |
@@ -25,21 +25,22 @@ import { | |||
ShortLivingBranch, | |||
MainBranch, | |||
Component, | |||
LongLivingBranch | |||
LongLivingBranch, | |||
PullRequest | |||
} from '../../../../types'; | |||
import { click } from '../../../../../helpers/testUtils'; | |||
const mainBranch: MainBranch = { isMain: true, name: 'master' }; | |||
const fooBranch: LongLivingBranch = { isMain: false, name: 'foo', type: BranchType.LONG }; | |||
it('renders main branch', () => { | |||
const branch: MainBranch = { isMain: true, name: 'master' }; | |||
const component = {} as Component; | |||
expect( | |||
shallow( | |||
<ComponentNavBranch | |||
branches={[branch, fooBranch]} | |||
branchLikes={[mainBranch, fooBranch]} | |||
component={component} | |||
currentBranch={branch} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
) | |||
@@ -58,9 +59,30 @@ it('renders short-living branch', () => { | |||
expect( | |||
shallow( | |||
<ComponentNavBranch | |||
branches={[branch, fooBranch]} | |||
branchLikes={[branch, fooBranch]} | |||
component={component} | |||
currentBranch={branch} | |||
currentBranchLike={branch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders pull request', () => { | |||
const pullRequest: PullRequest = { | |||
base: 'master', | |||
branch: 'feature', | |||
key: '1234', | |||
title: 'Feature PR', | |||
url: 'https://example.com/pull/1234' | |||
}; | |||
const component = {} as Component; | |||
expect( | |||
shallow( | |||
<ComponentNavBranch | |||
branchLikes={[pullRequest, fooBranch]} | |||
component={component} | |||
currentBranchLike={pullRequest} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
) | |||
@@ -68,13 +90,12 @@ it('renders short-living branch', () => { | |||
}); | |||
it('opens menu', () => { | |||
const branch: MainBranch = { isMain: true, name: 'master' }; | |||
const component = {} as Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch | |||
branches={[branch, fooBranch]} | |||
branchLikes={[mainBranch, fooBranch]} | |||
component={component} | |||
currentBranch={branch} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
); | |||
@@ -84,10 +105,13 @@ it('opens menu', () => { | |||
}); | |||
it('renders single branch popup', () => { | |||
const branch: MainBranch = { isMain: true, name: 'master' }; | |||
const component = {} as Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch branches={[branch]} component={component} currentBranch={branch} />, | |||
<ComponentNavBranch | |||
branchLikes={[mainBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: true } } | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
@@ -97,13 +121,12 @@ it('renders single branch popup', () => { | |||
}); | |||
it('renders no branch support popup', () => { | |||
const branch: MainBranch = { isMain: true, name: 'master' }; | |||
const component = {} as Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch | |||
branches={[branch, fooBranch]} | |||
branchLikes={[mainBranch, fooBranch]} | |||
component={component} | |||
currentBranch={branch} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: false } } | |||
); | |||
@@ -114,10 +137,13 @@ it('renders no branch support popup', () => { | |||
}); | |||
it('renders nothing on SonarCloud without branch support', () => { | |||
const branch: MainBranch = { isMain: true, name: 'master' }; | |||
const component = {} as Component; | |||
const wrapper = shallow( | |||
<ComponentNavBranch branches={[branch]} component={component} currentBranch={branch} />, | |||
<ComponentNavBranch | |||
branchLikes={[mainBranch]} | |||
component={component} | |||
currentBranchLike={mainBranch} | |||
/>, | |||
{ context: { branchesEnabled: false, onSonarCloud: true } } | |||
); | |||
expect(wrapper.type()).toBeNull(); |
@@ -25,7 +25,8 @@ import { | |||
MainBranch, | |||
ShortLivingBranch, | |||
LongLivingBranch, | |||
Component | |||
Component, | |||
PullRequest | |||
} from '../../../../types'; | |||
import { elementKeydown } from '../../../../../helpers/testUtils'; | |||
@@ -35,9 +36,15 @@ it('renders list', () => { | |||
expect( | |||
shallow( | |||
<ComponentNavBranchesMenu | |||
branches={[mainBranch(), shortBranch('foo'), longBranch('bar'), shortBranch('baz', true)]} | |||
branchLikes={[ | |||
mainBranch(), | |||
shortBranch('foo'), | |||
longBranch('bar'), | |||
shortBranch('baz', true), | |||
pullRequest('qux') | |||
]} | |||
component={component} | |||
currentBranch={mainBranch()} | |||
currentBranchLike={mainBranch()} | |||
onClose={jest.fn()} | |||
/> | |||
) | |||
@@ -47,9 +54,9 @@ it('renders list', () => { | |||
it('searches', () => { | |||
const wrapper = shallow( | |||
<ComponentNavBranchesMenu | |||
branches={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]} | |||
branchLikes={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]} | |||
component={component} | |||
currentBranch={mainBranch()} | |||
currentBranchLike={mainBranch()} | |||
onClose={jest.fn()} | |||
/> | |||
); | |||
@@ -60,21 +67,21 @@ it('searches', () => { | |||
it('selects next & previous', () => { | |||
const wrapper = shallow( | |||
<ComponentNavBranchesMenu | |||
branches={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]} | |||
branchLikes={[mainBranch(), shortBranch('foo'), shortBranch('foobar'), longBranch('bar')]} | |||
component={component} | |||
currentBranch={mainBranch()} | |||
currentBranchLike={mainBranch()} | |||
onClose={jest.fn()} | |||
/> | |||
); | |||
elementKeydown(wrapper.find('SearchBox'), 40); | |||
wrapper.update(); | |||
expect(wrapper.state().selected).toBe('foo'); | |||
expect(wrapper.state().selected).toEqual(shortBranch('foo')); | |||
elementKeydown(wrapper.find('SearchBox'), 40); | |||
wrapper.update(); | |||
expect(wrapper.state().selected).toBe('foobar'); | |||
expect(wrapper.state().selected).toEqual(shortBranch('foobar')); | |||
elementKeydown(wrapper.find('SearchBox'), 38); | |||
wrapper.update(); | |||
expect(wrapper.state().selected).toBe('foo'); | |||
expect(wrapper.state().selected).toEqual(shortBranch('foo')); | |||
}); | |||
function mainBranch(): MainBranch { | |||
@@ -95,3 +102,13 @@ function shortBranch(name: string, isOrphan?: true): ShortLivingBranch { | |||
function longBranch(name: string): LongLivingBranch { | |||
return { isMain: false, name, type: BranchType.LONG }; | |||
} | |||
function pullRequest(title: string): PullRequest { | |||
return { | |||
base: 'master', | |||
branch: 'feature', | |||
key: '1234', | |||
status: { bugs: 0, codeSmells: 0, vulnerabilities: 0 }, | |||
title | |||
}; | |||
} |
@@ -35,7 +35,7 @@ const shortBranch: ShortLivingBranch = { | |||
const mainBranch: MainBranch = { isMain: true, name: 'master' }; | |||
it('renders main branch', () => { | |||
expect(shallowRender({ branch: mainBranch })).toMatchSnapshot(); | |||
expect(shallowRender({ branchLike: mainBranch })).toMatchSnapshot(); | |||
}); | |||
it('renders short-living branch', () => { | |||
@@ -43,13 +43,14 @@ it('renders short-living branch', () => { | |||
}); | |||
it('renders short-living orhpan branch', () => { | |||
expect(shallowRender({ branch: { ...shortBranch, isOrphan: true } })).toMatchSnapshot(); | |||
const orhpan: ShortLivingBranch = { ...shortBranch, isOrphan: true }; | |||
expect(shallowRender({ branchLike: orhpan })).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) { | |||
return shallow( | |||
<ComponentNavBranchesMenuItem | |||
branch={shortBranch} | |||
branchLike={shortBranch} | |||
component={component} | |||
onSelect={jest.fn()} | |||
selected={false} |
@@ -32,7 +32,12 @@ it('should not render breadcrumbs with one element', () => { | |||
visibility: 'public' | |||
}; | |||
const result = shallow( | |||
<ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} /> | |||
<ComponentNavHeader | |||
branchLikes={[]} | |||
component={component} | |||
currentBranchLike={undefined} | |||
shouldOrganizationBeDisplayed={false} | |||
/> | |||
); | |||
expect(result).toMatchSnapshot(); | |||
}); | |||
@@ -53,8 +58,9 @@ it('should render organization', () => { | |||
}; | |||
const result = shallow( | |||
<ComponentNavHeader | |||
branches={[]} | |||
branchLikes={[]} | |||
component={component} | |||
currentBranchLike={undefined} | |||
organization={organization} | |||
shouldOrganizationBeDisplayed={true} | |||
/> | |||
@@ -72,7 +78,12 @@ it('renders private badge', () => { | |||
visibility: 'private' | |||
}; | |||
const result = shallow( | |||
<ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} /> | |||
<ComponentNavHeader | |||
branchLikes={[]} | |||
component={component} | |||
currentBranchLike={undefined} | |||
shouldOrganizationBeDisplayed={false} | |||
/> | |||
); | |||
expect(result.find('PrivateBadge')).toHaveLength(1); | |||
}); |
@@ -39,7 +39,7 @@ it('should work with extensions', () => { | |||
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }] | |||
}; | |||
expect( | |||
shallow(<ComponentNavMenu branch={mainBranch} component={component} />, { | |||
shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, { | |||
context: { branchesEnabled: true } | |||
}) | |||
).toMatchSnapshot(); | |||
@@ -58,7 +58,7 @@ it('should work with multiple extensions', () => { | |||
] | |||
}; | |||
expect( | |||
shallow(<ComponentNavMenu branch={mainBranch} component={component} />, { | |||
shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, { | |||
context: { branchesEnabled: true } | |||
}) | |||
).toMatchSnapshot(); | |||
@@ -77,7 +77,7 @@ it('should work for short-living branches', () => { | |||
extensions: [{ key: 'component-foo', name: 'ComponentFoo' }] | |||
}; | |||
expect( | |||
shallow(<ComponentNavMenu branch={branch} component={component} />, { | |||
shallow(<ComponentNavMenu branchLike={branch} component={component} />, { | |||
context: { branchesEnabled: true } | |||
}) | |||
).toMatchSnapshot(); | |||
@@ -89,7 +89,7 @@ it('should work for long-living branches', () => { | |||
expect( | |||
shallow( | |||
<ComponentNavMenu | |||
branch={branch} | |||
branchLike={branch} | |||
component={{ | |||
...baseComponent, | |||
configuration: { showSettings }, | |||
@@ -109,7 +109,7 @@ it('should work for all qualifiers', () => { | |||
function checkWithQualifier(qualifier: string) { | |||
const component = { ...baseComponent, configuration: { showSettings: true }, qualifier }; | |||
expect( | |||
shallow(<ComponentNavMenu branch={mainBranch} component={component} />, { | |||
shallow(<ComponentNavMenu branchLike={mainBranch} component={component} />, { | |||
context: { branchesEnabled: true } | |||
}) | |||
).toMatchSnapshot(); |
@@ -20,7 +20,7 @@ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { ComponentNavMeta } from '../ComponentNavMeta'; | |||
import { BranchType, ShortLivingBranch, LongLivingBranch } from '../../../../types'; | |||
import { BranchType, ShortLivingBranch, LongLivingBranch, PullRequest } from '../../../../types'; | |||
const component = { | |||
analysisDate: '2017-01-02T00:00:00.000Z', | |||
@@ -42,7 +42,11 @@ it('renders status of short-living branch', () => { | |||
}; | |||
expect( | |||
shallow( | |||
<ComponentNavMeta branch={branch} component={component} currentUser={{ isLoggedIn: false }} /> | |||
<ComponentNavMeta | |||
branchLike={branch} | |||
component={component} | |||
currentUser={{ isLoggedIn: false }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
@@ -56,7 +60,31 @@ it('renders meta for long-living branch', () => { | |||
}; | |||
expect( | |||
shallow( | |||
<ComponentNavMeta branch={branch} component={component} currentUser={{ isLoggedIn: false }} /> | |||
<ComponentNavMeta | |||
branchLike={branch} | |||
component={component} | |||
currentUser={{ isLoggedIn: false }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('renders meta for pull request', () => { | |||
const pullRequest: PullRequest = { | |||
base: 'master', | |||
branch: 'feature', | |||
key: '1234', | |||
status: { bugs: 0, codeSmells: 2, vulnerabilities: 3 }, | |||
title: 'Feature PR', | |||
url: 'https://example.com/pull/1234' | |||
}; | |||
expect( | |||
shallow( | |||
<ComponentNavMeta | |||
branchLike={pullRequest} | |||
component={component} | |||
currentUser={{ isLoggedIn: false }} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -6,7 +6,7 @@ exports[`renders 1`] = ` | |||
id="context-navigation" | |||
> | |||
<Connect(ComponentNavHeader) | |||
branches={Array []} | |||
branchLikes={Array []} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [ |
@@ -10,7 +10,7 @@ exports[`renders main branch 1`] = ` | |||
onClick={[Function]} | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
@@ -41,7 +41,7 @@ exports[`renders no branch support popup 1`] = ` | |||
className="navbar-context-branches" | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
@@ -77,6 +77,63 @@ exports[`renders no branch support popup 1`] = ` | |||
</div> | |||
`; | |||
exports[`renders pull request 1`] = ` | |||
<div | |||
className="navbar-context-branches dropdown" | |||
> | |||
<a | |||
className="link-base-color link-no-underline" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
<BranchIcon | |||
branchLike={ | |||
Object { | |||
"base": "master", | |||
"branch": "feature", | |||
"key": "1234", | |||
"title": "Feature PR", | |||
"url": "https://example.com/pull/1234", | |||
} | |||
} | |||
className="little-spacer-right" | |||
/> | |||
<Tooltip | |||
mouseEnterDelay={1} | |||
overlay="1234 – Feature PR" | |||
placement="bottom" | |||
> | |||
<span | |||
className="text-limited text-top" | |||
> | |||
1234 – Feature PR | |||
</span> | |||
</Tooltip> | |||
<i | |||
className="icon-dropdown little-spacer-left" | |||
/> | |||
</a> | |||
<span | |||
className="note big-spacer-left text-lowercase" | |||
> | |||
<FormattedMessage | |||
defaultMessage="branches.pull_request.for_merge_into_x_from_y" | |||
id="branches.pull_request.for_merge_into_x_from_y" | |||
values={ | |||
Object { | |||
"base": <strong> | |||
master | |||
</strong>, | |||
"branch": <strong> | |||
feature | |||
</strong>, | |||
} | |||
} | |||
/> | |||
</span> | |||
</div> | |||
`; | |||
exports[`renders short-living branch 1`] = ` | |||
<div | |||
className="navbar-context-branches dropdown" | |||
@@ -87,7 +144,7 @@ exports[`renders short-living branch 1`] = ` | |||
onClick={[Function]} | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
@@ -134,7 +191,7 @@ exports[`renders single branch popup 1`] = ` | |||
className="navbar-context-branches" | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", |
@@ -19,10 +19,10 @@ exports[`renders list 1`] = ` | |||
className="menu menu-vertically-limited" | |||
> | |||
<React.Fragment | |||
key="master" | |||
key="branch-master" | |||
> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
@@ -33,13 +33,45 @@ exports[`renders list 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="master" | |||
key="branch-master" | |||
onSelect={[Function]} | |||
selected={true} | |||
/> | |||
</React.Fragment> | |||
<React.Fragment | |||
key="baz" | |||
key="pull-request-1234" | |||
> | |||
<li | |||
className="dropdown-header navbar-context-meta-branch-menu-title" | |||
> | |||
branches.pull_requests | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branchLike={ | |||
Object { | |||
"base": "master", | |||
"branch": "feature", | |||
"key": "1234", | |||
"status": Object { | |||
"bugs": 0, | |||
"codeSmells": 0, | |||
"vulnerabilities": 0, | |||
}, | |||
"title": "qux", | |||
} | |||
} | |||
component={ | |||
Object { | |||
"key": "component", | |||
} | |||
} | |||
key="pull-request-1234" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
</React.Fragment> | |||
<React.Fragment | |||
key="branch-baz" | |||
> | |||
<li | |||
className="divider" | |||
@@ -58,7 +90,7 @@ exports[`renders list 1`] = ` | |||
</Tooltip> | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": true, | |||
@@ -77,16 +109,16 @@ exports[`renders list 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="baz" | |||
key="branch-baz" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
</React.Fragment> | |||
<React.Fragment | |||
key="foo" | |||
key="branch-foo" | |||
> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": undefined, | |||
@@ -105,19 +137,19 @@ exports[`renders list 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="foo" | |||
key="branch-foo" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
</React.Fragment> | |||
<React.Fragment | |||
key="bar" | |||
key="branch-bar" | |||
> | |||
<li | |||
className="divider" | |||
/> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"name": "bar", | |||
@@ -129,13 +161,13 @@ exports[`renders list 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="bar" | |||
key="branch-bar" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
</React.Fragment> | |||
<React.Fragment | |||
key="baz" | |||
key="branch-baz" | |||
> | |||
<li | |||
className="divider" | |||
@@ -154,7 +186,7 @@ exports[`renders list 1`] = ` | |||
</Tooltip> | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": true, | |||
@@ -173,7 +205,7 @@ exports[`renders list 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="baz" | |||
key="branch-baz" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> | |||
@@ -201,10 +233,15 @@ exports[`searches 1`] = ` | |||
className="menu menu-vertically-limited" | |||
> | |||
<React.Fragment | |||
key="foobar" | |||
key="branch-foobar" | |||
> | |||
<li | |||
className="dropdown-header navbar-context-meta-branch-menu-title" | |||
> | |||
branches.short_lived_branches | |||
</li> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": undefined, | |||
@@ -223,19 +260,19 @@ exports[`searches 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="foobar" | |||
key="branch-foobar" | |||
onSelect={[Function]} | |||
selected={true} | |||
/> | |||
</React.Fragment> | |||
<React.Fragment | |||
key="bar" | |||
key="branch-bar" | |||
> | |||
<li | |||
className="divider" | |||
/> | |||
<ComponentNavBranchesMenuItem | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"name": "bar", | |||
@@ -247,7 +284,7 @@ exports[`searches 1`] = ` | |||
"key": "component", | |||
} | |||
} | |||
key="bar" | |||
key="branch-bar" | |||
onSelect={[Function]} | |||
selected={false} | |||
/> |
@@ -2,7 +2,7 @@ | |||
exports[`renders main branch 1`] = ` | |||
<li | |||
key="master" | |||
key="branch-master" | |||
onMouseEnter={[Function]} | |||
> | |||
<Tooltip | |||
@@ -27,7 +27,7 @@ exports[`renders main branch 1`] = ` | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
@@ -46,7 +46,7 @@ exports[`renders main branch 1`] = ` | |||
className="big-spacer-left note" | |||
> | |||
<BranchStatus | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": true, | |||
"name": "master", | |||
@@ -62,7 +62,7 @@ exports[`renders main branch 1`] = ` | |||
exports[`renders short-living branch 1`] = ` | |||
<li | |||
key="foo" | |||
key="branch-foo" | |||
onMouseEnter={[Function]} | |||
> | |||
<Tooltip | |||
@@ -89,7 +89,7 @@ exports[`renders short-living branch 1`] = ` | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
@@ -110,7 +110,7 @@ exports[`renders short-living branch 1`] = ` | |||
className="big-spacer-left note" | |||
> | |||
<BranchStatus | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "master", | |||
@@ -133,7 +133,7 @@ exports[`renders short-living branch 1`] = ` | |||
exports[`renders short-living orhpan branch 1`] = ` | |||
<li | |||
key="foo" | |||
key="branch-foo" | |||
onMouseEnter={[Function]} | |||
> | |||
<Tooltip | |||
@@ -160,7 +160,7 @@ exports[`renders short-living orhpan branch 1`] = ` | |||
className="navbar-context-meta-branch-menu-item-name text-ellipsis" | |||
> | |||
<BranchIcon | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": true, | |||
@@ -182,7 +182,7 @@ exports[`renders short-living orhpan branch 1`] = ` | |||
className="big-spacer-left note" | |||
> | |||
<BranchStatus | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"isOrphan": true, |
@@ -23,7 +23,6 @@ exports[`should not render breadcrumbs with one element 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "my-project", | |||
}, | |||
} | |||
@@ -91,7 +90,6 @@ exports[`should render organization 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "my-project", | |||
}, | |||
} |
@@ -11,7 +11,6 @@ exports[`should work for all qualifiers 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -30,7 +29,6 @@ exports[`should work for all qualifiers 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -49,7 +47,6 @@ exports[`should work for all qualifiers 1`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -67,7 +64,6 @@ exports[`should work for all qualifiers 1`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -85,7 +81,6 @@ exports[`should work for all qualifiers 1`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -123,7 +118,6 @@ exports[`should work for all qualifiers 1`] = ` | |||
Object { | |||
"pathname": "/project/settings", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -205,7 +199,6 @@ exports[`should work for all qualifiers 2`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -224,7 +217,6 @@ exports[`should work for all qualifiers 2`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -243,7 +235,6 @@ exports[`should work for all qualifiers 2`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -261,7 +252,6 @@ exports[`should work for all qualifiers 2`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -279,7 +269,6 @@ exports[`should work for all qualifiers 2`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -317,7 +306,6 @@ exports[`should work for all qualifiers 2`] = ` | |||
Object { | |||
"pathname": "/project/settings", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -342,7 +330,6 @@ exports[`should work for all qualifiers 3`] = ` | |||
Object { | |||
"pathname": "/portfolio", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -361,7 +348,6 @@ exports[`should work for all qualifiers 3`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -380,7 +366,6 @@ exports[`should work for all qualifiers 3`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -398,7 +383,6 @@ exports[`should work for all qualifiers 3`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -416,7 +400,6 @@ exports[`should work for all qualifiers 3`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -478,7 +461,6 @@ exports[`should work for all qualifiers 4`] = ` | |||
Object { | |||
"pathname": "/portfolio", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -497,7 +479,6 @@ exports[`should work for all qualifiers 4`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -516,7 +497,6 @@ exports[`should work for all qualifiers 4`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -534,7 +514,6 @@ exports[`should work for all qualifiers 4`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -552,7 +531,6 @@ exports[`should work for all qualifiers 4`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -575,7 +553,6 @@ exports[`should work for all qualifiers 5`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -594,7 +571,6 @@ exports[`should work for all qualifiers 5`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -613,7 +589,6 @@ exports[`should work for all qualifiers 5`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -631,7 +606,6 @@ exports[`should work for all qualifiers 5`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -649,7 +623,6 @@ exports[`should work for all qualifiers 5`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -948,7 +921,6 @@ exports[`should work with extensions 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -967,7 +939,6 @@ exports[`should work with extensions 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -986,7 +957,6 @@ exports[`should work with extensions 1`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1004,7 +974,6 @@ exports[`should work with extensions 1`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1022,7 +991,6 @@ exports[`should work with extensions 1`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1060,7 +1028,6 @@ exports[`should work with extensions 1`] = ` | |||
Object { | |||
"pathname": "/project/settings", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1200,7 +1167,6 @@ exports[`should work with multiple extensions 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1219,7 +1185,6 @@ exports[`should work with multiple extensions 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
"resolved": "false", | |||
}, | |||
@@ -1238,7 +1203,6 @@ exports[`should work with multiple extensions 1`] = ` | |||
Object { | |||
"pathname": "/component_measures", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1256,7 +1220,6 @@ exports[`should work with multiple extensions 1`] = ` | |||
Object { | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1274,7 +1237,6 @@ exports[`should work with multiple extensions 1`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -1312,7 +1274,6 @@ exports[`should work with multiple extensions 1`] = ` | |||
Object { | |||
"pathname": "/project/settings", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -27,6 +27,51 @@ exports[`renders meta for long-living branch 1`] = ` | |||
</div> | |||
`; | |||
exports[`renders meta for pull request 1`] = ` | |||
<div | |||
className="navbar-context-meta" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<DateTimeFormatter | |||
date="2017-01-02T00:00:00.000Z" | |||
/> | |||
</div> | |||
<div | |||
className="navbar-context-meta-secondary" | |||
> | |||
<a | |||
className="big-spacer-right" | |||
href="https://example.com/pull/1234" | |||
rel="nofollow" | |||
target="_blank" | |||
> | |||
branches.see_the_pr | |||
<i | |||
className="icon-detach little-spacer-left" | |||
/> | |||
</a> | |||
<BranchStatus | |||
branchLike={ | |||
Object { | |||
"base": "master", | |||
"branch": "feature", | |||
"key": "1234", | |||
"status": Object { | |||
"bugs": 0, | |||
"codeSmells": 2, | |||
"vulnerabilities": 3, | |||
}, | |||
"title": "Feature PR", | |||
"url": "https://example.com/pull/1234", | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
`; | |||
exports[`renders status of short-living branch 1`] = ` | |||
<div | |||
className="navbar-context-meta" | |||
@@ -42,7 +87,7 @@ exports[`renders status of short-living branch 1`] = ` | |||
className="navbar-context-meta-secondary" | |||
> | |||
<BranchStatus | |||
branch={ | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "master", |
@@ -21,7 +21,6 @@ exports[`renders favorite 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -70,7 +69,6 @@ exports[`renders match 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -118,7 +116,6 @@ exports[`renders organizations 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -171,7 +168,6 @@ exports[`renders organizations 2`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -219,7 +215,6 @@ exports[`renders projects 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "qwe", | |||
}, | |||
} | |||
@@ -272,7 +267,6 @@ exports[`renders recently browsed 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -320,7 +314,6 @@ exports[`renders selected 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -366,7 +359,6 @@ exports[`renders selected 2`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -36,7 +36,15 @@ export interface AppState { | |||
qualifiers: string[]; | |||
} | |||
export type Branch = MainBranch | LongLivingBranch | ShortLivingBranch; | |||
export interface Branch { | |||
analysisDate?: string; | |||
isMain: boolean; | |||
name: string; | |||
} | |||
export type BranchLike = Branch | PullRequest; | |||
export type BranchParameters = { branch?: string } | { pullRequest?: string }; | |||
export enum BranchType { | |||
LONG = 'LONG', | |||
@@ -273,23 +281,14 @@ export interface LoggedInUser extends CurrentUser { | |||
name: string; | |||
} | |||
export interface LongLivingBranch { | |||
analysisDate?: string; | |||
export interface LongLivingBranch extends Branch { | |||
isMain: false; | |||
name: string; | |||
status?: { | |||
qualityGateStatus: string; | |||
}; | |||
status?: { qualityGateStatus: string }; | |||
type: BranchType.LONG; | |||
} | |||
export interface MainBranch { | |||
analysisDate?: string; | |||
export interface MainBranch extends Branch { | |||
isMain: true; | |||
name: string; | |||
status?: { | |||
qualityGateStatus: string; | |||
}; | |||
} | |||
export interface Metric { | |||
@@ -352,6 +351,21 @@ export interface ProjectLink { | |||
url: string; | |||
} | |||
export interface PullRequest { | |||
analysisDate?: string; | |||
base: string; | |||
branch: string; | |||
key: string; | |||
isOrphan?: true; | |||
status?: { | |||
bugs: number; | |||
codeSmells: number; | |||
vulnerabilities: number; | |||
}; | |||
title: string; | |||
url?: string; | |||
} | |||
export interface Rule { | |||
isTemplate?: boolean; | |||
key: string; | |||
@@ -419,12 +433,10 @@ export enum RuleScope { | |||
All = 'ALL' | |||
} | |||
export interface ShortLivingBranch { | |||
analysisDate?: string; | |||
export interface ShortLivingBranch extends Branch { | |||
isMain: false; | |||
isOrphan?: true; | |||
mergeBranch: string; | |||
name: string; | |||
status?: { | |||
bugs: number; | |||
codeSmells: number; |
@@ -23,7 +23,7 @@ import { receiveValues } from '../settings/store/values/actions'; | |||
export const fetchAboutPageSettings = () => dispatch => { | |||
const keys = ['sonar.lf.aboutText']; | |||
return getValues(keys.join()).then(values => { | |||
return getValues({ keys: keys.join() }).then(values => { | |||
dispatch(receiveValues(values)); | |||
}); | |||
}; |
@@ -23,7 +23,6 @@ exports[`should match snapshot 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -30,7 +30,7 @@ export const fetchMyOrganizations = () => (dispatch: Dispatch<any>) => { | |||
}; | |||
export const fetchIfAnyoneCanCreateOrganizations = () => (dispatch: Dispatch<any>) => { | |||
return getValues('sonar.organizations.anyoneCanCreate').then(values => { | |||
return getValues({ keys: 'sonar.organizations.anyoneCanCreate' }).then(values => { | |||
dispatch(receiveValues(values, undefined)); | |||
}); | |||
}; |
@@ -23,9 +23,16 @@ import TaskType from './TaskType'; | |||
import { Task } from '../types'; | |||
import QualifierIcon from '../../../components/shared/QualifierIcon'; | |||
import Organization from '../../../components/shared/Organization'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { | |||
getProjectUrl, | |||
getShortLivingBranchUrl, | |||
getLongLivingBranchUrl, | |||
getPullRequestUrl | |||
} from '../../../helpers/urls'; | |||
import ShortLivingBranchIcon from '../../../components/icons-components/ShortLivingBranchIcon'; | |||
import LongLivingBranchIcon from '../../../components/icons-components/LongLivingBranchIcon'; | |||
import PullRequestIcon from '../../../components/icons-components/PullRequestIcon'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
interface Props { | |||
task: Task; | |||
@@ -45,8 +52,10 @@ export default function TaskComponent({ task }: Props) { | |||
<td> | |||
{task.branchType === 'SHORT' && <ShortLivingBranchIcon className="little-spacer-right" />} | |||
{task.branchType === 'LONG' && <LongLivingBranchIcon className="little-spacer-right" />} | |||
{task.pullRequest !== undefined && <PullRequestIcon className="little-spacer-right" />} | |||
{!task.branchType && | |||
!task.pullRequest && | |||
task.componentQualifier && ( | |||
<span className="little-spacer-right"> | |||
<QualifierIcon qualifier={task.componentQualifier} /> | |||
@@ -56,7 +65,7 @@ export default function TaskComponent({ task }: Props) { | |||
{task.organization && <Organization organizationKey={task.organization} />} | |||
{task.componentName && ( | |||
<Link className="spacer-right" to={getProjectUrl(task.componentKey, task.branch)}> | |||
<Link className="spacer-right" to={getTaskComponentUrl(task.componentKey, task)}> | |||
{task.componentName} | |||
{task.branch && ( | |||
@@ -65,6 +74,15 @@ export default function TaskComponent({ task }: Props) { | |||
{task.branch} | |||
</span> | |||
)} | |||
{task.pullRequest && ( | |||
<Tooltip overlay={task.pullRequestTitle}> | |||
<span className="text-limited text-text-top"> | |||
<span style={{ marginLeft: 5, marginRight: 5 }}>/</span> | |||
{task.pullRequest} | |||
</span> | |||
</Tooltip> | |||
)} | |||
</Link> | |||
)} | |||
@@ -72,3 +90,15 @@ export default function TaskComponent({ task }: Props) { | |||
</td> | |||
); | |||
} | |||
function getTaskComponentUrl(componentKey: string, task: Task) { | |||
if (task.branch && task.branchType === 'SHORT') { | |||
return getShortLivingBranchUrl(componentKey, task.branchType); | |||
} else if (task.branchType && task.branchType === 'LONG') { | |||
return getLongLivingBranchUrl(componentKey, task.branchType); | |||
} else if (task.pullRequest) { | |||
return getPullRequestUrl(componentKey, task.pullRequest); | |||
} else { | |||
return getProjectUrl(componentKey); | |||
} | |||
} |
@@ -20,7 +20,6 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} | |||
@@ -67,7 +66,6 @@ exports[`renders 3`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": "feature", | |||
"id": "foo", | |||
}, | |||
} |
@@ -29,6 +29,8 @@ export interface Task { | |||
hasScannerContext?: boolean; | |||
id: string; | |||
organization?: string; | |||
pullRequest?: string; | |||
pullRequestTitle?: string; | |||
startedAt?: string; | |||
status: string; | |||
submittedAt: string; |
@@ -26,16 +26,16 @@ import Search from './Search'; | |||
import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket'; | |||
import { Component as CodeComponent } from '../types'; | |||
import { retrieveComponentChildren, retrieveComponent, loadMoreChildren } from '../utils'; | |||
import { Component, BranchLike } from '../../../app/types'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { getBranchName } from '../../../helpers/branches'; | |||
import { isSameBranchLike } from '../../../helpers/branches'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Component, Branch } from '../../../app/types'; | |||
import { parseError } from '../../../helpers/request'; | |||
import '../code.css'; | |||
interface Props { | |||
branch?: Branch; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
location: { query: { [x: string]: string } }; | |||
} | |||
@@ -67,7 +67,10 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.component !== this.props.component || prevProps.branch !== this.props.branch) { | |||
if ( | |||
prevProps.component !== this.props.component || | |||
!isSameBranchLike(prevProps.branchLike, this.props.branchLike) | |||
) { | |||
this.handleComponentChange(); | |||
} else if (prevProps.location !== this.props.location) { | |||
this.handleUpdate(); | |||
@@ -80,14 +83,14 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
handleComponentChange() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
// we already know component's breadcrumbs, | |||
addComponentBreadcrumbs(component.key, component.breadcrumbs); | |||
this.setState({ loading: true }); | |||
const isPortfolio = ['VW', 'SVW'].includes(component.qualifier); | |||
retrieveComponentChildren(component.key, isPortfolio, getBranchName(branch)) | |||
retrieveComponentChildren(component.key, isPortfolio, branchLike) | |||
.then(() => { | |||
addComponent(component); | |||
if (this.mounted) { | |||
@@ -106,7 +109,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
this.setState({ loading: true }); | |||
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier); | |||
retrieveComponent(componentKey, isPortfolio, getBranchName(this.props.branch)) | |||
retrieveComponent(componentKey, isPortfolio, this.props.branchLike) | |||
.then(r => { | |||
if (this.mounted) { | |||
if (['FIL', 'UTS'].includes(r.component.qualifier)) { | |||
@@ -152,7 +155,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
return; | |||
} | |||
const isPortfolio = ['VW', 'SVW'].includes(this.props.component.qualifier); | |||
loadMoreChildren(baseComponent.key, page + 1, isPortfolio, getBranchName(this.props.branch)) | |||
loadMoreChildren(baseComponent.key, page + 1, isPortfolio, this.props.branchLike) | |||
.then(r => { | |||
if (this.mounted) { | |||
this.setState({ | |||
@@ -177,7 +180,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const { branch, component, location } = this.props; | |||
const { branchLike, component, location } = this.props; | |||
const { | |||
loading, | |||
error, | |||
@@ -187,8 +190,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
total, | |||
sourceViewer | |||
} = this.state; | |||
const branchName = getBranchName(branch); | |||
const shouldShowBreadcrumbs = breadcrumbs.length > 1; | |||
const componentsClassName = classNames('boxed-group', 'boxed-group-inner', 'spacer-top', { | |||
@@ -202,7 +203,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
{error && <div className="alert alert-danger">{error}</div>} | |||
<Search | |||
branch={branchName} | |||
branchLike={branchLike} | |||
component={component} | |||
location={location} | |||
onError={this.handleError} | |||
@@ -210,7 +211,11 @@ export default class App extends React.PureComponent<Props, State> { | |||
<div className="code-components"> | |||
{shouldShowBreadcrumbs && ( | |||
<Breadcrumbs branch={branchName} breadcrumbs={breadcrumbs} rootComponent={component} /> | |||
<Breadcrumbs | |||
branchLike={branchLike} | |||
breadcrumbs={breadcrumbs} | |||
rootComponent={component} | |||
/> | |||
)} | |||
{sourceViewer === undefined && | |||
@@ -218,7 +223,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
<div className={componentsClassName}> | |||
<Components | |||
baseComponent={baseComponent} | |||
branch={branchName} | |||
branchLike={branchLike} | |||
components={components} | |||
rootComponent={component} | |||
/> | |||
@@ -232,7 +237,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
{sourceViewer !== undefined && ( | |||
<div className="spacer-top"> | |||
<SourceViewer branch={branchName} component={sourceViewer.key} /> | |||
<SourceViewer branchLike={branchLike} component={sourceViewer.key} /> | |||
</div> | |||
)} | |||
</div> |
@@ -20,20 +20,21 @@ | |||
import * as React from 'react'; | |||
import ComponentName from './ComponentName'; | |||
import { Component } from '../types'; | |||
import { BranchLike } from '../../../app/types'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
breadcrumbs: Component[]; | |||
rootComponent: Component; | |||
} | |||
export default function Breadcrumbs({ branch, breadcrumbs, rootComponent }: Props) { | |||
export default function Breadcrumbs({ branchLike, breadcrumbs, rootComponent }: Props) { | |||
return ( | |||
<ul className="code-breadcrumbs"> | |||
{breadcrumbs.map((component, index) => ( | |||
<li key={component.key}> | |||
<ComponentName | |||
branch={branch} | |||
branchLike={branchLike} | |||
canBrowse={index < breadcrumbs.length - 1} | |||
component={component} | |||
rootComponent={rootComponent} |
@@ -24,12 +24,13 @@ import ComponentMeasure from './ComponentMeasure'; | |||
import ComponentLink from './ComponentLink'; | |||
import ComponentPin from './ComponentPin'; | |||
import { Component as IComponent } from '../types'; | |||
import { BranchLike } from '../../../app/types'; | |||
const TOP_OFFSET = 200; | |||
const BOTTOM_OFFSET = 10; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
canBrowse?: boolean; | |||
component: IComponent; | |||
previous?: IComponent; | |||
@@ -73,7 +74,7 @@ export default class Component extends React.PureComponent<Props> { | |||
render() { | |||
const { | |||
branch, | |||
branchLike, | |||
component, | |||
rootComponent, | |||
selected = false, | |||
@@ -89,10 +90,10 @@ export default class Component extends React.PureComponent<Props> { | |||
switch (component.qualifier) { | |||
case 'FIL': | |||
case 'UTS': | |||
componentAction = <ComponentPin branch={branch} component={component} />; | |||
componentAction = <ComponentPin branchLike={branchLike} component={component} />; | |||
break; | |||
default: | |||
componentAction = <ComponentLink branch={branch} component={component} />; | |||
componentAction = <ComponentLink branchLike={branchLike} component={component} />; | |||
} | |||
} | |||
@@ -121,7 +122,7 @@ export default class Component extends React.PureComponent<Props> { | |||
</td> | |||
<td className="code-name-cell"> | |||
<ComponentName | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component} | |||
rootComponent={rootComponent} | |||
previous={previous} |
@@ -20,21 +20,22 @@ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { Component } from '../types'; | |||
import { BranchLike } from '../../../app/types'; | |||
import LinkIcon from '../../../components/icons-components/LinkIcon'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { getBranchLikeUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
} | |||
export default function ComponentLink({ component, branch }: Props) { | |||
export default function ComponentLink({ component, branchLike }: Props) { | |||
return ( | |||
<Link | |||
className="link-no-underline" | |||
title={translate('code.open_component_page')} | |||
to={getProjectUrl(component.refKey || component.key, branch)}> | |||
to={getBranchLikeUrl(component.refKey || component.key, branchLike)}> | |||
<LinkIcon /> | |||
</Link> | |||
); |
@@ -20,9 +20,11 @@ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Truncated from './Truncated'; | |||
import { Component } from '../types'; | |||
import * as theme from '../../../app/theme'; | |||
import { BranchLike } from '../../../app/types'; | |||
import QualifierIcon from '../../../components/shared/QualifierIcon'; | |||
import { Component } from '../types'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
function getTooltip(component: Component) { | |||
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS'; | |||
@@ -49,7 +51,7 @@ function mostCommitPrefix(strings: string[]) { | |||
} | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
canBrowse?: boolean; | |||
component: Component; | |||
previous?: Component; | |||
@@ -57,7 +59,7 @@ interface Props { | |||
} | |||
export default function ComponentName(props: Props) { | |||
const { branch, component, rootComponent, previous, canBrowse = false } = props; | |||
const { branchLike, component, rootComponent, previous, canBrowse = false } = props; | |||
const areBothDirs = component.qualifier === 'DIR' && previous && previous.qualifier === 'DIR'; | |||
const prefix = | |||
areBothDirs && previous !== undefined | |||
@@ -83,7 +85,7 @@ export default function ComponentName(props: Props) { | |||
</Link> | |||
); | |||
} else if (canBrowse) { | |||
const query = { id: rootComponent.key, branch }; | |||
const query = { id: rootComponent.key, ...getBranchLikeQuery(branchLike) }; | |||
if (component.key !== rootComponent.key) { | |||
Object.assign(query, { selected: component.key }); | |||
} |
@@ -18,20 +18,21 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import Workspace from '../../../components/workspace/main'; | |||
import { Component } from '../types'; | |||
import { BranchLike } from '../../../app/types'; | |||
import PinIcon from '../../../components/shared/pin-icon'; | |||
import Workspace from '../../../components/workspace/main'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Component } from '../types'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
} | |||
export default function ComponentPin({ branch, component }: Props) { | |||
export default function ComponentPin({ branchLike, component }: Props) { | |||
const handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
Workspace.openComponent({ branch, key: component.key }); | |||
Workspace.openComponent({ branchLike, key: component.key }); | |||
}; | |||
return ( |
@@ -22,24 +22,25 @@ import Component from './Component'; | |||
import ComponentsEmpty from './ComponentsEmpty'; | |||
import ComponentsHeader from './ComponentsHeader'; | |||
import { Component as IComponent } from '../types'; | |||
import { BranchLike } from '../../../app/types'; | |||
interface Props { | |||
baseComponent?: IComponent; | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
components: IComponent[]; | |||
rootComponent: IComponent; | |||
selected?: IComponent; | |||
} | |||
export default function Components(props: Props) { | |||
const { baseComponent, branch, components, rootComponent, selected } = props; | |||
const { baseComponent, branchLike, components, rootComponent, selected } = props; | |||
return ( | |||
<table className="data zebra"> | |||
<ComponentsHeader baseComponent={baseComponent} rootComponent={rootComponent} /> | |||
{baseComponent && ( | |||
<tbody> | |||
<Component | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={baseComponent} | |||
key={baseComponent.key} | |||
rootComponent={rootComponent} | |||
@@ -53,7 +54,7 @@ export default function Components(props: Props) { | |||
{components.length ? ( | |||
components.map((component, index, list) => ( | |||
<Component | |||
branch={branch} | |||
branchLike={branchLike} | |||
canBrowse={true} | |||
component={component} | |||
key={component.key} |
@@ -21,15 +21,17 @@ import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import * as classNames from 'classnames'; | |||
import Components from './Components'; | |||
import { getTree } from '../../../api/components'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { Component } from '../types'; | |||
import { getTree } from '../../../api/components'; | |||
import { BranchLike } from '../../../app/types'; | |||
import SearchBox from '../../../components/controls/SearchBox'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { parseError } from '../../../helpers/request'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
location: {}; | |||
onError: (error: string) => void; | |||
@@ -89,7 +91,7 @@ export default class Search extends React.PureComponent<Props, State> { | |||
} | |||
handleSelectCurrent() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
const { results, selectedIndex } = this.state; | |||
if (results != null && selectedIndex != null) { | |||
const selected = results[selectedIndex]; | |||
@@ -99,7 +101,7 @@ export default class Search extends React.PureComponent<Props, State> { | |||
} else { | |||
this.context.router.push({ | |||
pathname: '/code', | |||
query: { branch, id: component.key, selected: selected.key } | |||
query: { id: component.key, selected: selected.key, ...getBranchLikeQuery(branchLike) } | |||
}); | |||
} | |||
} | |||
@@ -125,13 +127,18 @@ export default class Search extends React.PureComponent<Props, State> { | |||
handleSearch = (query: string) => { | |||
if (this.mounted) { | |||
const { branch, component, onError } = this.props; | |||
const { branchLike, component, onError } = this.props; | |||
this.setState({ loading: true }); | |||
const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier); | |||
const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL'; | |||
getTree(component.key, { branch, q: query, s: 'qualifier,name', qualifiers }) | |||
getTree(component.key, { | |||
q: query, | |||
s: 'qualifier,name', | |||
qualifiers, | |||
...getBranchLikeQuery(branchLike) | |||
}) | |||
.then(r => { | |||
if (this.mounted) { | |||
this.setState({ | |||
@@ -184,7 +191,7 @@ export default class Search extends React.PureComponent<Props, State> { | |||
{results != null && ( | |||
<div className="boxed-group boxed-group-inner spacer-top"> | |||
<Components | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
components={results} | |||
rootComponent={component} | |||
selected={selected} |
@@ -28,6 +28,8 @@ import { | |||
} from './bucket'; | |||
import { Breadcrumb, Component } from './types'; | |||
import { getChildren, getComponent, getBreadcrumbs } from '../../api/components'; | |||
import { BranchLike } from '../../app/types'; | |||
import { getBranchLikeQuery } from '../../helpers/branches'; | |||
const METRICS = [ | |||
'ncloc', | |||
@@ -54,11 +56,15 @@ function requestChildren( | |||
componentKey: string, | |||
metrics: string[], | |||
page: number, | |||
branch?: string | |||
branchLike?: BranchLike | |||
): Promise<Component[]> { | |||
return getChildren(componentKey, metrics, { branch, p: page, ps: PAGE_SIZE }).then(r => { | |||
return getChildren(componentKey, metrics, { | |||
p: page, | |||
ps: PAGE_SIZE, | |||
...getBranchLikeQuery(branchLike) | |||
}).then(r => { | |||
if (r.paging.total > r.paging.pageSize * r.paging.pageIndex) { | |||
return requestChildren(componentKey, metrics, page + 1, branch).then(moreComponents => { | |||
return requestChildren(componentKey, metrics, page + 1, branchLike).then(moreComponents => { | |||
return [...r.components, ...moreComponents]; | |||
}); | |||
} | |||
@@ -69,9 +75,9 @@ function requestChildren( | |||
function requestAllChildren( | |||
componentKey: string, | |||
metrics: string[], | |||
branch?: string | |||
branchLike?: BranchLike | |||
): Promise<Component[]> { | |||
return requestChildren(componentKey, metrics, 1, branch); | |||
return requestChildren(componentKey, metrics, 1, branchLike); | |||
} | |||
interface Children { | |||
@@ -84,13 +90,13 @@ interface ExpandRootDirFunc { | |||
(children: Children): Promise<Children>; | |||
} | |||
function expandRootDir(metrics: string[], branch?: string): ExpandRootDirFunc { | |||
function expandRootDir(metrics: string[], branchLike?: BranchLike): ExpandRootDirFunc { | |||
return function({ components, total, ...other }) { | |||
const rootDir = components.find( | |||
(component: Component) => component.qualifier === 'DIR' && component.name === '/' | |||
); | |||
if (rootDir) { | |||
return requestAllChildren(rootDir.key, metrics, branch).then(rootDirComponents => { | |||
return requestAllChildren(rootDir.key, metrics, branchLike).then(rootDirComponents => { | |||
const nextComponents = without([...rootDirComponents, ...components], rootDir); | |||
const nextTotal = total + rootDirComponents.length - /* root dir */ 1; | |||
return { components: nextComponents, total: nextTotal, ...other }; | |||
@@ -133,7 +139,11 @@ function getMetrics(isPortfolio: boolean) { | |||
return isPortfolio ? PORTFOLIO_METRICS : METRICS; | |||
} | |||
function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branch?: string) { | |||
function retrieveComponentBase( | |||
componentKey: string, | |||
isPortfolio: boolean, | |||
branchLike?: BranchLike | |||
) { | |||
const existing = getComponentFromBucket(componentKey); | |||
if (existing) { | |||
return Promise.resolve(existing); | |||
@@ -141,7 +151,11 @@ function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branc | |||
const metrics = getMetrics(isPortfolio); | |||
return getComponent(componentKey, metrics, branch).then(component => { | |||
return getComponent({ | |||
componentKey, | |||
metricKeys: metrics.join(), | |||
...getBranchLikeQuery(branchLike) | |||
}).then(component => { | |||
addComponent(component); | |||
return component; | |||
}); | |||
@@ -150,7 +164,7 @@ function retrieveComponentBase(componentKey: string, isPortfolio: boolean, branc | |||
export function retrieveComponentChildren( | |||
componentKey: string, | |||
isPortfolio: boolean, | |||
branch?: string | |||
branchLike?: BranchLike | |||
): Promise<{ components: Component[]; page: number; total: number }> { | |||
const existing = getComponentChildren(componentKey); | |||
if (existing) { | |||
@@ -163,9 +177,13 @@ export function retrieveComponentChildren( | |||
const metrics = getMetrics(isPortfolio); | |||
return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, s: 'qualifier,name' }) | |||
return getChildren(componentKey, metrics, { | |||
ps: PAGE_SIZE, | |||
s: 'qualifier,name', | |||
...getBranchLikeQuery(branchLike) | |||
}) | |||
.then(prepareChildren) | |||
.then(expandRootDir(metrics, branch)) | |||
.then(expandRootDir(metrics, branchLike)) | |||
.then(r => { | |||
addComponentChildren(componentKey, r.components, r.total, r.page); | |||
storeChildrenBase(r.components); | |||
@@ -175,18 +193,18 @@ export function retrieveComponentChildren( | |||
} | |||
function retrieveComponentBreadcrumbs( | |||
componentKey: string, | |||
branch?: string | |||
component: string, | |||
branchLike?: BranchLike | |||
): Promise<Breadcrumb[]> { | |||
const existing = getComponentBreadcrumbs(componentKey); | |||
const existing = getComponentBreadcrumbs(component); | |||
if (existing) { | |||
return Promise.resolve(existing); | |||
} | |||
return getBreadcrumbs(componentKey, branch) | |||
return getBreadcrumbs({ component, ...getBranchLikeQuery(branchLike) }) | |||
.then(skipRootDir) | |||
.then(breadcrumbs => { | |||
addComponentBreadcrumbs(componentKey, breadcrumbs); | |||
addComponentBreadcrumbs(component, breadcrumbs); | |||
return breadcrumbs; | |||
}); | |||
} | |||
@@ -194,7 +212,7 @@ function retrieveComponentBreadcrumbs( | |||
export function retrieveComponent( | |||
componentKey: string, | |||
isPortfolio: boolean, | |||
branch?: string | |||
branchLike?: BranchLike | |||
): Promise<{ | |||
breadcrumbs: Component[]; | |||
component: Component; | |||
@@ -203,9 +221,9 @@ export function retrieveComponent( | |||
total: number; | |||
}> { | |||
return Promise.all([ | |||
retrieveComponentBase(componentKey, isPortfolio, branch), | |||
retrieveComponentChildren(componentKey, isPortfolio, branch), | |||
retrieveComponentBreadcrumbs(componentKey, branch) | |||
retrieveComponentBase(componentKey, isPortfolio, branchLike), | |||
retrieveComponentChildren(componentKey, isPortfolio, branchLike), | |||
retrieveComponentBreadcrumbs(componentKey, branchLike) | |||
]).then(r => { | |||
return { | |||
component: r[0], | |||
@@ -221,13 +239,17 @@ export function loadMoreChildren( | |||
componentKey: string, | |||
page: number, | |||
isPortfolio: boolean, | |||
branch?: string | |||
branchLike?: BranchLike | |||
): Promise<Children> { | |||
const metrics = getMetrics(isPortfolio); | |||
return getChildren(componentKey, metrics, { branch, ps: PAGE_SIZE, p: page }) | |||
return getChildren(componentKey, metrics, { | |||
ps: PAGE_SIZE, | |||
p: page, | |||
...getBranchLikeQuery(branchLike) | |||
}) | |||
.then(prepareChildren) | |||
.then(expandRootDir(metrics, branch)) | |||
.then(expandRootDir(metrics, branchLike)) | |||
.then(r => { | |||
addComponentChildren(componentKey, r.components, r.total, r.page); | |||
storeChildrenBase(r.components); |
@@ -26,7 +26,7 @@ import MeasureOverviewContainer from './MeasureOverviewContainer'; | |||
import Sidebar from '../sidebar/Sidebar'; | |||
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; | |||
import { hasBubbleChart, parseQuery, serializeQuery } from '../utils'; | |||
import { getBranchName } from '../../../helpers/branches'; | |||
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getDisplayMetrics } from '../../../helpers/measures'; | |||
/*:: import type { Component, Query, Period } from '../types'; */ | |||
@@ -36,14 +36,14 @@ import { getDisplayMetrics } from '../../../helpers/measures'; | |||
import '../style.css'; | |||
/*:: type Props = {| | |||
branch?: {}, | |||
branchLike?: { id?: string; name: string }, | |||
component: Component, | |||
currentUser: { isLoggedIn: boolean }, | |||
location: { pathname: string, query: RawQuery }, | |||
fetchMeasures: ( | |||
component: string, | |||
metricsKey: Array<string>, | |||
branch?: string | |||
branchLike?: { id?: string; name: string } | |||
) => Promise<{ component: Component, measures: Array<MeasureEnhanced>, leakPeriod: ?Period }>, | |||
fetchMetrics: () => void, | |||
metrics: { [string]: Metric }, | |||
@@ -88,7 +88,7 @@ export default class App extends React.PureComponent { | |||
componentWillReceiveProps(nextProps /*: Props */) { | |||
if ( | |||
nextProps.branch !== this.props.branch || | |||
!isSameBranchLike(nextProps.branchLike, this.props.branchLike) || | |||
nextProps.component.key !== this.props.component.key || | |||
nextProps.metrics !== this.props.metrics | |||
) { | |||
@@ -107,10 +107,10 @@ export default class App extends React.PureComponent { | |||
} | |||
} | |||
fetchMeasures = ({ branch, component, fetchMeasures, metrics } /*: Props */) => { | |||
fetchMeasures = ({ branchLike, component, fetchMeasures, metrics } /*: Props */) => { | |||
this.setState({ loading: true }); | |||
const filteredKeys = getDisplayMetrics(Object.values(metrics)).map(metric => metric.key); | |||
fetchMeasures(component.key, filteredKeys, getBranchName(branch)).then( | |||
fetchMeasures(component.key, filteredKeys, branchLike).then( | |||
({ measures, leakPeriod }) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
@@ -137,7 +137,7 @@ export default class App extends React.PureComponent { | |||
pathname: this.props.location.pathname, | |||
query: { | |||
...query, | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
id: this.props.component.key | |||
} | |||
}); | |||
@@ -148,7 +148,7 @@ export default class App extends React.PureComponent { | |||
if (isLoading) { | |||
return <i className="spinner spinner-margin" />; | |||
} | |||
const { branch, component, fetchMeasures, metrics } = this.props; | |||
const { branchLike, component, fetchMeasures, metrics } = this.props; | |||
const { leakPeriod } = this.state; | |||
const query = parseQuery(this.props.location.query); | |||
const metric = metrics[query.metric]; | |||
@@ -174,7 +174,7 @@ export default class App extends React.PureComponent { | |||
{metric != null && ( | |||
<MeasureContentContainer | |||
branch={getBranchName(branch)} | |||
branchLike={branchLike} | |||
className="layout-page-main" | |||
currentUser={this.props.currentUser} | |||
rootComponent={component} | |||
@@ -191,7 +191,7 @@ export default class App extends React.PureComponent { | |||
{metric == null && | |||
hasBubbleChart(query.metric) && ( | |||
<MeasureOverviewContainer | |||
branch={getBranchName(branch)} | |||
branchLike={branchLike} | |||
className="layout-page-main" | |||
rootComponent={component} | |||
currentUser={this.props.currentUser} |
@@ -27,6 +27,7 @@ import { fetchMetrics } from '../../../store/rootActions'; | |||
import { getMeasuresAndMeta } from '../../../api/measures'; | |||
import { getLeakPeriod } from '../../../helpers/periods'; | |||
import { enhanceMeasure } from '../../../components/measure/utils'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
/*:: import type { Component, Period } from '../types'; */ | |||
/*:: import type { Measure, MeasureEnhanced } from '../../../components/measure/types'; */ | |||
@@ -50,7 +51,7 @@ function banQualityGate(component /*: Component */) /*: Array<Measure> */ { | |||
const fetchMeasures = ( | |||
component /*: string */, | |||
metricsKey /*: Array<string> */, | |||
branch /*: string | void */ | |||
branchLike /*: { id?: string; name: string } | void */ | |||
) => (dispatch, getState) => { | |||
if (metricsKey.length <= 0) { | |||
return Promise.resolve({ component: {}, measures: [], leakPeriod: null }); | |||
@@ -58,7 +59,7 @@ const fetchMeasures = ( | |||
return getMeasuresAndMeta(component, metricsKey, { | |||
additionalFields: 'periods', | |||
branch | |||
...getBranchLikeQuery(branchLike) | |||
}).then(r => { | |||
const measures = banQualityGate(r.component).map(measure => | |||
enhanceMeasure(measure, getMetrics(getState())) |
@@ -22,11 +22,12 @@ import React from 'react'; | |||
import key from 'keymaster'; | |||
import Breadcrumb from './Breadcrumb'; | |||
import { getBreadcrumbs } from '../../../api/components'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
/*:: import type { Component } from '../types'; */ | |||
/*:: type Props = {| | |||
backToFirst: boolean, | |||
branch?: string, | |||
branchLike?: { id?: string, name: string }, | |||
className?: string, | |||
component: Component, | |||
handleSelect: string => void, | |||
@@ -76,7 +77,7 @@ export default class Breadcrumbs extends React.PureComponent { | |||
key.unbind('left', 'measures-files'); | |||
} | |||
fetchBreadcrumbs = ({ branch, component, rootComponent } /*: Props */) => { | |||
fetchBreadcrumbs = ({ branchLike, component, rootComponent } /*: Props */) => { | |||
const isRoot = component.key === rootComponent.key; | |||
if (isRoot) { | |||
if (this.mounted) { | |||
@@ -84,11 +85,13 @@ export default class Breadcrumbs extends React.PureComponent { | |||
} | |||
return; | |||
} | |||
getBreadcrumbs(component.key, branch).then(breadcrumbs => { | |||
if (this.mounted) { | |||
this.setState({ breadcrumbs }); | |||
getBreadcrumbs({ component: component.key, ...getBranchLikeQuery(branchLike) }).then( | |||
breadcrumbs => { | |||
if (this.mounted) { | |||
this.setState({ breadcrumbs }); | |||
} | |||
} | |||
}); | |||
); | |||
}; | |||
render() { |
@@ -34,6 +34,7 @@ import { complementary } from '../config/complementary'; | |||
import { enhanceComponent, isFileType, isViewType } from '../utils'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { isDiffMetric } from '../../../helpers/measures'; | |||
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; | |||
/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */ | |||
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
@@ -42,7 +43,7 @@ import { isDiffMetric } from '../../../helpers/measures'; | |||
// https://github.com/facebook/flow/issues/3147 | |||
// router: { push: ({ pathname: string, query?: RawQuery }) => void } | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
className?: string, | |||
component: Component, | |||
currentUser: { isLoggedIn: boolean }, | |||
@@ -87,7 +88,7 @@ export default class MeasureContent extends React.PureComponent { | |||
componentWillReceiveProps(nextProps /*: Props */) { | |||
if ( | |||
nextProps.branch !== this.props.branch || | |||
!isSameBranchLike(nextProps.branchLike, this.props.branchLike) || | |||
nextProps.component !== this.props.component || | |||
nextProps.metric !== this.props.metric | |||
) { | |||
@@ -115,7 +116,7 @@ export default class MeasureContent extends React.PureComponent { | |||
const strategy = view === 'list' ? 'leaves' : 'children'; | |||
const metricKeys = [metric.key]; | |||
const opts /*: Object */ = { | |||
branch: this.props.branch, | |||
...getBranchLikeQuery(this.props.branchLike), | |||
metricSortFilter: 'withMeasuresOnly' | |||
}; | |||
const isDiff = isDiffMetric(metric.key); | |||
@@ -225,7 +226,7 @@ export default class MeasureContent extends React.PureComponent { | |||
return ( | |||
<div className="measure-details-viewer"> | |||
<CodeView | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
component={this.props.component} | |||
components={this.state.components} | |||
leakPeriod={this.props.leakPeriod} | |||
@@ -244,7 +245,7 @@ export default class MeasureContent extends React.PureComponent { | |||
const selectedIdx = this.getSelectedIndex(); | |||
return ( | |||
<FilesView | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
components={this.state.components} | |||
fetchMore={this.fetchMoreComponents} | |||
handleOpen={this.onOpenComponent} | |||
@@ -261,7 +262,7 @@ export default class MeasureContent extends React.PureComponent { | |||
if (view === 'treemap') { | |||
return ( | |||
<TreeMapView | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
components={this.state.components} | |||
handleSelect={this.onOpenComponent} | |||
metric={metric} | |||
@@ -274,7 +275,7 @@ export default class MeasureContent extends React.PureComponent { | |||
} | |||
render() { | |||
const { branch, component, currentUser, measure, metric, rootComponent, view } = this.props; | |||
const { branchLike, component, currentUser, measure, metric, rootComponent, view } = this.props; | |||
const isLoggedIn = currentUser && currentUser.isLoggedIn; | |||
const isFile = isFileType(component); | |||
const selectedIdx = this.getSelectedIndex(); | |||
@@ -288,7 +289,7 @@ export default class MeasureContent extends React.PureComponent { | |||
<div className="layout-page-main-inner"> | |||
<Breadcrumbs | |||
backToFirst={view === 'list'} | |||
branch={branch} | |||
branchLike={branchLike} | |||
className="measure-breadcrumbs spacer-right text-ellipsis" | |||
component={component} | |||
handleSelect={this.onOpenComponent} | |||
@@ -327,7 +328,7 @@ export default class MeasureContent extends React.PureComponent { | |||
measure != null && ( | |||
<div className="layout-page-main-inner measure-details-content"> | |||
<MeasureHeader | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component} | |||
components={this.state.components} | |||
leakPeriod={this.props.leakPeriod} |
@@ -26,14 +26,14 @@ import MeasureContent from './MeasureContent'; | |||
/*:: import type { RawQuery } from '../../../helpers/query'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
className?: string, | |||
currentUser: { isLoggedIn: boolean }, | |||
rootComponent: Component, | |||
fetchMeasures: ( | |||
component: string, | |||
metricsKey: Array<string>, | |||
branch?: string | |||
branchLike?: { id?: string; name: string } | |||
) => Promise<{ component: Component, measures: Array<MeasureEnhanced> }>, | |||
leakPeriod?: Period, | |||
metric: Metric, | |||
@@ -89,7 +89,7 @@ export default class MeasureContentContainer extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
fetchMeasure = ({ branch, rootComponent, fetchMeasures, metric, selected } /*: Props */) => { | |||
fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected } /*: Props */) => { | |||
this.updateLoading({ measure: true }); | |||
const metricKeys = [metric.key]; | |||
@@ -101,7 +101,7 @@ export default class MeasureContentContainer extends React.PureComponent { | |||
metricKeys.push('file_complexity_distribution'); | |||
} | |||
fetchMeasures(selected || rootComponent.key, metricKeys, branch).then( | |||
fetchMeasures(selected || rootComponent.key, metricKeys, branchLike).then( | |||
({ component, measures }) => { | |||
if (this.mounted) { | |||
const measure = measures.find(measure => measure.metric.key === metric.key); | |||
@@ -134,7 +134,7 @@ export default class MeasureContentContainer extends React.PureComponent { | |||
return ( | |||
<MeasureContent | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
className={this.props.className} | |||
component={this.state.component} | |||
currentUser={this.props.currentUser} |
@@ -57,7 +57,7 @@ class MeasureFavoriteContainer extends React.PureComponent { | |||
} | |||
fetchComponentFavorite({ component, onReceiveComponent } /*: Props */) { | |||
getComponentForSourceViewer(component).then(component => { | |||
getComponentForSourceViewer({ component }).then(component => { | |||
this.setState({ component }); | |||
onReceiveComponent(component); | |||
}); |
@@ -34,7 +34,7 @@ import { isDiffMetric } from '../../../helpers/measures'; | |||
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
component: Component, | |||
components: Array<Component>, | |||
leakPeriod?: Period, | |||
@@ -43,7 +43,7 @@ import { isDiffMetric } from '../../../helpers/measures'; | |||
|}; */ | |||
export default function MeasureHeader(props /*: Props*/) { | |||
const { branch, component, leakPeriod, measure, secondaryMeasure } = props; | |||
const { branchLike, component, leakPeriod, measure, secondaryMeasure } = props; | |||
const { metric } = measure; | |||
const isDiff = isDiffMetric(metric.key); | |||
return ( | |||
@@ -72,7 +72,7 @@ export default function MeasureHeader(props /*: Props*/) { | |||
overlay={translate('component_measures.show_metric_history')}> | |||
<Link | |||
className="js-show-history spacer-left button button-small" | |||
to={getMeasureHistoryUrl(component.key, metric.key, branch)}> | |||
to={getMeasureHistoryUrl(component.key, metric.key, branchLike)}> | |||
<HistoryIcon /> | |||
</Link> | |||
</Tooltip> |
@@ -27,11 +27,12 @@ import BubbleChart from '../drilldown/BubbleChart'; | |||
import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | |||
import { getComponentLeaves } from '../../../api/components'; | |||
import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
/*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */ | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
className?: string, | |||
component: Component, | |||
currentUser: { isLoggedIn: boolean }, | |||
@@ -79,9 +80,10 @@ export default class MeasureOverview extends React.PureComponent { | |||
} | |||
fetchComponents = (props /*: Props */) => { | |||
const { branch, component, domain, metrics } = props; | |||
const { branchLike, component, domain, metrics } = props; | |||
if (isFileType(component)) { | |||
return this.setState({ components: [], paging: null }); | |||
this.setState({ components: [], paging: null }); | |||
return; | |||
} | |||
const { x, y, size, colors } = getBubbleMetrics(domain, metrics); | |||
const metricsKey = [x.key, y.key, size.key]; | |||
@@ -89,7 +91,7 @@ export default class MeasureOverview extends React.PureComponent { | |||
metricsKey.push(colors.map(metric => metric.key)); | |||
} | |||
const options = { | |||
branch, | |||
...getBranchLikeQuery(branchLike), | |||
s: 'metric', | |||
metricSort: size.key, | |||
asc: false, | |||
@@ -114,11 +116,11 @@ export default class MeasureOverview extends React.PureComponent { | |||
}; | |||
renderContent() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
if (isFileType(component)) { | |||
return ( | |||
<div className="measure-details-viewer"> | |||
<SourceViewer branch={branch} component={component.key} /> | |||
<SourceViewer branchLike={branchLike} component={component.key} /> | |||
</div> | |||
); | |||
} | |||
@@ -135,7 +137,7 @@ export default class MeasureOverview extends React.PureComponent { | |||
} | |||
render() { | |||
const { branch, component, currentUser, leakPeriod, rootComponent } = this.props; | |||
const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props; | |||
const isLoggedIn = currentUser && currentUser.isLoggedIn; | |||
const isFile = isFileType(component); | |||
return ( | |||
@@ -145,7 +147,7 @@ export default class MeasureOverview extends React.PureComponent { | |||
<div className="layout-page-main-inner"> | |||
<Breadcrumbs | |||
backToFirst={true} | |||
branch={branch} | |||
branchLike={branchLike} | |||
className="measure-breadcrumbs spacer-right text-ellipsis" | |||
component={component} | |||
handleSelect={this.props.updateSelected} |
@@ -23,12 +23,13 @@ import MeasureOverview from './MeasureOverview'; | |||
import { getComponentShow } from '../../../api/components'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { isViewType } from '../utils'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
/*:: import type { Component, Period, Query } from '../types'; */ | |||
/*:: import type { RawQuery } from '../../../helpers/query'; */ | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
className?: string, | |||
rootComponent: Component, | |||
currentUser: { isLoggedIn: boolean }, | |||
@@ -81,14 +82,14 @@ export default class MeasureOverviewContainer extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
fetchComponent = ({ branch, rootComponent, selected } /*: Props */) => { | |||
fetchComponent = ({ branchLike, rootComponent, selected } /*: Props */) => { | |||
if (!selected || rootComponent.key === selected) { | |||
this.setState({ component: rootComponent }); | |||
this.updateLoading({ component: false }); | |||
return; | |||
} | |||
this.updateLoading({ component: true }); | |||
getComponentShow(selected, branch).then( | |||
getComponentShow({ component: selected, ...getBranchLikeQuery(branchLike) }).then( | |||
({ component }) => { | |||
if (this.mounted) { | |||
this.setState({ component }); | |||
@@ -122,7 +123,7 @@ export default class MeasureOverviewContainer extends React.PureComponent { | |||
return ( | |||
<MeasureOverview | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
className={this.props.className} | |||
component={this.state.component} | |||
currentUser={this.props.currentUser} |
@@ -77,7 +77,10 @@ it('should render correctly for leak', () => { | |||
}); | |||
it('should render with branch', () => { | |||
expect(shallow(<MeasureHeader branch="feature" {...PROPS} />).find('Link')).toMatchSnapshot(); | |||
const shortBranch = { isMain: false, name: 'feature', mergeBranch: '', type: 'SHORT' }; | |||
expect( | |||
shallow(<MeasureHeader branchLike={shortBranch} {...PROPS} />).find('Link') | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display secondary measure too', () => { |
@@ -86,7 +86,6 @@ exports[`should render correctly 1`] = ` | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"branch": undefined, | |||
"custom_metrics": "reliability_rating", | |||
"graph": "custom", | |||
"id": "foo", |
@@ -25,7 +25,7 @@ import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
component: ComponentEnhanced, | |||
components: Array<ComponentEnhanced>, | |||
leakPeriod?: Period, | |||
@@ -81,7 +81,7 @@ export default class CodeView extends React.PureComponent { | |||
}; | |||
render() { | |||
const { branch, component } = this.props; | |||
return <SourceViewer branch={branch} component={component.key} />; | |||
const { branchLike, component } = this.props; | |||
return <SourceViewer branchLike={branchLike} component={component.key} />; | |||
} | |||
} |
@@ -23,11 +23,11 @@ import { Link } from 'react-router'; | |||
import LinkIcon from '../../../components/icons-components/LinkIcon'; | |||
import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import { splitPath } from '../../../helpers/path'; | |||
import { getPathUrlAsString, getProjectUrl } from '../../../helpers/urls'; | |||
import { getPathUrlAsString, getBranchLikeUrl } from '../../../helpers/urls'; | |||
/*:: import type { ComponentEnhanced } from '../types'; */ | |||
/*:: type Props = { | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
component: ComponentEnhanced, | |||
onClick: string => void | |||
}; */ | |||
@@ -65,15 +65,16 @@ export default class ComponentCell extends React.PureComponent { | |||
} | |||
render() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
return ( | |||
<td className="measure-details-component-cell"> | |||
<div className="text-ellipsis"> | |||
{/* TODO make this <a> link a react-router <Link /> */} | |||
{component.refKey == null ? ( | |||
<a | |||
id={'component-measures-component-link-' + component.key} | |||
className="link-no-underline" | |||
href={getPathUrlAsString(getProjectUrl(component.key, branch))} | |||
href={getPathUrlAsString(getBranchLikeUrl(component.key, branchLike))} | |||
onClick={this.handleClick}> | |||
{this.renderInner()} | |||
</a> | |||
@@ -81,7 +82,7 @@ export default class ComponentCell extends React.PureComponent { | |||
<Link | |||
className="link-no-underline" | |||
id={'component-measures-component-link-' + component.key} | |||
to={getProjectUrl(component.refKey, branch)}> | |||
to={getBranchLikeUrl(component.refKey, branchLike)}> | |||
<span className="big-spacer-right"> | |||
<LinkIcon /> | |||
</span> |
@@ -27,7 +27,7 @@ import { getLocalizedMetricName } from '../../../helpers/l10n'; | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
components: Array<ComponentEnhanced>, | |||
onClick: string => void, | |||
metric: Metric, | |||
@@ -36,7 +36,7 @@ import { getLocalizedMetricName } from '../../../helpers/l10n'; | |||
|}; */ | |||
export default function ComponentsList( | |||
{ branch, components, onClick, metrics, metric, selectedComponent } /*: Props */ | |||
{ branchLike, components, onClick, metrics, metric, selectedComponent } /*: Props */ | |||
) { | |||
if (!components.length) { | |||
return <EmptyResult />; | |||
@@ -65,7 +65,7 @@ export default function ComponentsList( | |||
{components.map(component => ( | |||
<ComponentsListRow | |||
key={component.id} | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component} | |||
otherMetrics={otherMetrics} | |||
isSelected={component.key === selectedComponent} |
@@ -26,7 +26,7 @@ import MeasureCell from './MeasureCell'; | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
component: ComponentEnhanced, | |||
isSelected: boolean, | |||
onClick: string => void, | |||
@@ -35,7 +35,7 @@ import MeasureCell from './MeasureCell'; | |||
|}; */ | |||
export default function ComponentsListRow(props /*: Props */) { | |||
const { branch, component } = props; | |||
const { branchLike, component } = props; | |||
const otherMeasures = props.otherMetrics.map(metric => { | |||
const measure = component.measures.find(measure => measure.metric.key === metric.key); | |||
return { ...measure, metric }; | |||
@@ -45,7 +45,7 @@ export default function ComponentsListRow(props /*: Props */) { | |||
}); | |||
return ( | |||
<tr className={rowClass}> | |||
<ComponentCell branch={branch} component={component} onClick={props.onClick} /> | |||
<ComponentCell branchLike={branchLike} component={component} onClick={props.onClick} /> | |||
<MeasureCell component={component} metric={props.metric} /> | |||
@@ -28,7 +28,7 @@ import { scrollToElement } from '../../../helpers/scrolling'; | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
components: Array<ComponentEnhanced>, | |||
fetchMore: () => void, | |||
handleSelect: string => void, | |||
@@ -123,7 +123,7 @@ export default class ListView extends React.PureComponent { | |||
return ( | |||
<div ref={elem => (this.listContainer = elem)}> | |||
<ComponentsList | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
components={this.props.components} | |||
metrics={this.props.metrics} | |||
metric={this.props.metric} |
@@ -29,13 +29,13 @@ import QualifierIcon from '../../../components/icons-components/QualifierIcon'; | |||
import TreeMap from '../../../components/charts/TreeMap'; | |||
import { translate, translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n'; | |||
import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { getBranchLikeUrl } from '../../../helpers/urls'; | |||
/*:: import type { Metric } from '../../../store/metrics/actions'; */ | |||
/*:: import type { ComponentEnhanced } from '../types'; */ | |||
/*:: import type { TreeMapItem } from '../../../components/charts/TreeMap'; */ | |||
/*:: type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
components: Array<ComponentEnhanced>, | |||
handleSelect: string => void, | |||
metric: Metric | |||
@@ -64,7 +64,7 @@ export default class TreeMapView extends React.PureComponent { | |||
} | |||
} | |||
getTreemapComponents = ({ branch, components, metric } /*: Props */) => { | |||
getTreemapComponents = ({ branchLike, components, metric } /*: Props */) => { | |||
const colorScale = this.getColorScale(metric); | |||
return components | |||
.map(component => { | |||
@@ -95,7 +95,7 @@ export default class TreeMapView extends React.PureComponent { | |||
sizeValue | |||
), | |||
label: component.name, | |||
link: getProjectUrl(component.refKey || component.key, branch) | |||
link: getBranchLikeUrl(component.refKey || component.key, branchLike) | |||
}; | |||
}) | |||
.filter(Boolean); |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { PullRequest, BranchType, ShortLivingBranch } from '../../../app/types'; | |||
import SourceViewer from '../../../components/SourceViewer/SourceViewer'; | |||
interface Props { | |||
@@ -26,6 +27,7 @@ interface Props { | |||
branch?: string; | |||
id: string; | |||
line?: string; | |||
pullRequest?: string; | |||
}; | |||
}; | |||
} | |||
@@ -45,15 +47,30 @@ export default class App extends React.PureComponent<Props> { | |||
}; | |||
render() { | |||
const { branch, id, line } = this.props.location.query; | |||
const { branch, id, line, pullRequest } = this.props.location.query; | |||
const finalLine = line ? Number(line) : undefined; | |||
// TODO find a way to avoid creating this fakeBranchLike | |||
// probably the best way would be to drop this page completely | |||
// and redirect to the Code page | |||
let fakeBranchLike: ShortLivingBranch | PullRequest | undefined = undefined; | |||
if (branch) { | |||
fakeBranchLike = { | |||
isMain: false, | |||
mergeBranch: '', | |||
name: branch, | |||
type: BranchType.SHORT | |||
} as ShortLivingBranch; | |||
} else if (pullRequest) { | |||
fakeBranchLike = { base: '', branch: '', key: pullRequest, title: '' } as PullRequest; | |||
} | |||
return ( | |||
<div className="page page-limited"> | |||
<SourceViewer | |||
aroundLine={finalLine} | |||
branch={branch} | |||
branchLike={fakeBranchLike} | |||
component={id} | |||
highlightedLine={finalLine} | |||
onLoaded={this.scrollToLine} |
@@ -6,7 +6,14 @@ exports[`renders 1`] = ` | |||
> | |||
<Connect(SourceViewerBase) | |||
aroundLine={7} | |||
branch="b" | |||
branchLike={ | |||
Object { | |||
"isMain": false, | |||
"mergeBranch": "", | |||
"name": "b", | |||
"type": "SHORT", | |||
} | |||
} | |||
component="foo" | |||
highlightedLine={7} | |||
onLoaded={[Function]} |
@@ -18,11 +18,11 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Component, CurrentUser } from '../../../app/types'; | |||
import { Component, CurrentUser, BranchLike } from '../../../app/types'; | |||
import { RawQuery } from '../../../helpers/query'; | |||
interface Props { | |||
branch?: { name: string }; | |||
branchLike?: BranchLike; | |||
component?: Component; | |||
currentUser: CurrentUser; | |||
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<any>; |
@@ -59,7 +59,12 @@ import ListFooter from '../../../components/controls/ListFooter'; | |||
import EmptySearch from '../../../components/common/EmptySearch'; | |||
import FiltersHeader from '../../../components/common/FiltersHeader'; | |||
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; | |||
import { getBranchName, isShortLivingBranch } from '../../../helpers/branches'; | |||
import { | |||
isShortLivingBranch, | |||
isSameBranchLike, | |||
getBranchLikeQuery, | |||
isPullRequest | |||
} from '../../../helpers/branches'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { scrollToElement } from '../../../helpers/scrolling'; | |||
import Checkbox from '../../../components/controls/Checkbox'; | |||
@@ -69,7 +74,7 @@ import '../styles.css'; | |||
/*:: | |||
export type Props = { | |||
branch?: { name: string }, | |||
branchLike?: { id?: string; name: string }, | |||
component?: Component, | |||
currentUser: CurrentUser, | |||
fetchIssues: (query: RawQuery, requestOrganizations?: boolean) => Promise<*>, | |||
@@ -193,7 +198,7 @@ export default class App extends React.PureComponent { | |||
const { query: prevQuery } = prevProps.location; | |||
if ( | |||
prevProps.component !== this.props.component || | |||
prevProps.branch !== this.props.branch || | |||
!isSameBranchLike(prevProps.branchLike, this.props.branchLike) || | |||
!areQueriesEqual(prevQuery, query) || | |||
areMyIssuesSelected(prevQuery) !== areMyIssuesSelected(query) | |||
) { | |||
@@ -337,7 +342,7 @@ export default class App extends React.PureComponent { | |||
pathname: this.props.location.pathname, | |||
query: { | |||
...serializeQuery(this.state.query), | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
id: this.props.component && this.props.component.key, | |||
myIssues: this.state.myIssues ? 'true' : undefined, | |||
open: issue | |||
@@ -356,7 +361,7 @@ export default class App extends React.PureComponent { | |||
pathname: this.props.location.pathname, | |||
query: { | |||
...serializeQuery(this.state.query), | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
id: this.props.component && this.props.component.key, | |||
myIssues: this.state.myIssues ? 'true' : undefined, | |||
open: undefined | |||
@@ -399,7 +404,7 @@ export default class App extends React.PureComponent { | |||
: undefined; | |||
const parameters = { | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
componentKeys: component && component.key, | |||
s: 'FILE_LINE', | |||
...serializeQuery(query), | |||
@@ -594,7 +599,7 @@ export default class App extends React.PureComponent { | |||
pathname: this.props.location.pathname, | |||
query: { | |||
...serializeQuery({ ...this.state.query, ...changes }), | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
id: this.props.component && this.props.component.key, | |||
myIssues: this.state.myIssues ? 'true' : undefined | |||
} | |||
@@ -610,7 +615,7 @@ export default class App extends React.PureComponent { | |||
pathname: this.props.location.pathname, | |||
query: { | |||
...serializeQuery({ ...this.state.query, assigned: true, assignees: [] }), | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
id: this.props.component && this.props.component.key, | |||
myIssues: myIssues ? 'true' : undefined | |||
} | |||
@@ -637,7 +642,7 @@ export default class App extends React.PureComponent { | |||
pathname: this.props.location.pathname, | |||
query: { | |||
...DEFAULT_QUERY, | |||
branch: getBranchName(this.props.branch), | |||
...getBranchLikeQuery(this.props.branchLike), | |||
id: this.props.component && this.props.component.key, | |||
myIssues: this.state.myIssues ? 'true' : undefined | |||
} | |||
@@ -724,7 +729,7 @@ export default class App extends React.PureComponent { | |||
handleReload = () => { | |||
this.fetchFirstIssues(); | |||
if (isShortLivingBranch(this.props.branch)) { | |||
if (isShortLivingBranch(this.props.branchLike) || isPullRequest(this.props.branchLike)) { | |||
this.props.onBranchesChange(); | |||
} | |||
}; | |||
@@ -892,7 +897,7 @@ export default class App extends React.PureComponent { | |||
} | |||
renderList() { | |||
const { branch, component, currentUser, organization } = this.props; | |||
const { branchLike, component, currentUser, organization } = this.props; | |||
const { issues, openIssue, paging } = this.state; | |||
const selectedIndex = this.getSelectedIndex(); | |||
const selectedIssue = selectedIndex != null ? issues[selectedIndex] : null; | |||
@@ -905,7 +910,7 @@ export default class App extends React.PureComponent { | |||
<div> | |||
{paging.total > 0 && ( | |||
<IssuesList | |||
branch={getBranchName(branch)} | |||
branchLike={branchLike} | |||
checked={this.state.checked} | |||
component={component} | |||
issues={issues} | |||
@@ -971,7 +976,7 @@ export default class App extends React.PureComponent { | |||
{openIssue != null ? ( | |||
<div className="pull-left width-60"> | |||
<ComponentBreadcrumbs | |||
branch={getBranchName(this.props.branch)} | |||
branchLike={this.props.branchLike} | |||
component={component} | |||
issue={openIssue} | |||
organization={this.props.organization} | |||
@@ -1000,7 +1005,7 @@ export default class App extends React.PureComponent { | |||
<div> | |||
{openIssue ? ( | |||
<IssuesSourceViewer | |||
branch={getBranchName(this.props.branch)} | |||
branchLike={this.props.branchLike} | |||
component={component} | |||
openIssue={openIssue} | |||
loadIssues={this.fetchIssuesForComponent} |
@@ -21,11 +21,11 @@ import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import Organization from '../../../components/shared/Organization'; | |||
import { collapsePath, limitComponentName } from '../../../helpers/path'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
import { Component } from '../../../app/types'; | |||
import { getBranchLikeUrl, getCodeUrl } from '../../../helpers/urls'; | |||
import { Component, BranchLike } from '../../../app/types'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component?: Component; | |||
issue: { | |||
component: string; | |||
@@ -39,7 +39,12 @@ interface Props { | |||
organization?: { key: string }; | |||
} | |||
export default function ComponentBreadcrumbs({ branch, component, issue, organization }: Props) { | |||
export default function ComponentBreadcrumbs({ | |||
branchLike, | |||
component, | |||
issue, | |||
organization | |||
}: Props) { | |||
const displayOrganization = | |||
!organization && (component == null || ['VW', 'SVW'].includes(component.qualifier)); | |||
const displayProject = component == null || !['TRK', 'BRC', 'DIR'].includes(component.qualifier); | |||
@@ -53,7 +58,7 @@ export default function ComponentBreadcrumbs({ branch, component, issue, organiz | |||
{displayProject && ( | |||
<span title={issue.projectName}> | |||
<Link to={getProjectUrl(issue.project, branch)} className="link-no-underline"> | |||
<Link to={getBranchLikeUrl(issue.project, branchLike)} className="link-no-underline"> | |||
{limitComponentName(issue.projectName)} | |||
</Link> | |||
<span className="slash-separator" /> | |||
@@ -64,14 +69,16 @@ export default function ComponentBreadcrumbs({ branch, component, issue, organiz | |||
issue.subProject !== undefined && | |||
issue.subProjectName !== undefined && ( | |||
<span title={issue.subProjectName}> | |||
<Link to={getProjectUrl(issue.subProject, branch)} className="link-no-underline"> | |||
<Link to={getBranchLikeUrl(issue.subProject, branchLike)} className="link-no-underline"> | |||
{limitComponentName(issue.subProjectName)} | |||
</Link> | |||
<span className="slash-separator" /> | |||
</span> | |||
)} | |||
<Link to={getProjectUrl(issue.component, branch)} className="link-no-underline"> | |||
<Link | |||
to={getCodeUrl(issue.project, branchLike, issue.component)} | |||
className="link-no-underline"> | |||
<span title={issue.componentLongName}>{collapsePath(issue.componentLongName)}</span> | |||
</Link> | |||
</div> |
@@ -25,7 +25,7 @@ import ListItem from './ListItem'; | |||
/*:: | |||
type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
checked: Array<string>, | |||
component?: Component, | |||
issues: Array<Issue>, | |||
@@ -44,13 +44,13 @@ export default class IssuesList extends React.PureComponent { | |||
/*:: props: Props; */ | |||
render() { | |||
const { branch, checked, component, issues, openPopup, selectedIssue } = this.props; | |||
const { branchLike, checked, component, issues, openPopup, selectedIssue } = this.props; | |||
return ( | |||
<div> | |||
{issues.map((issue, index) => ( | |||
<ListItem | |||
branch={branch} | |||
branchLike={branchLike} | |||
checked={checked.includes(issue.key)} | |||
component={component} | |||
key={issue.key} |
@@ -26,7 +26,7 @@ import { scrollToElement } from '../../../helpers/scrolling'; | |||
/*:: | |||
type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
component: Component, | |||
loadIssues: (string, number, number) => Promise<*>, | |||
onIssueChange: Issue => void, | |||
@@ -107,7 +107,7 @@ export default class IssuesSourceViewer extends React.PureComponent { | |||
<div ref={node => (this.node = node)}> | |||
<SourceViewer | |||
aroundLine={aroundLine} | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
component={openIssue.component} | |||
displayAllIssues={true} | |||
displayIssueLocationsCount={false} |
@@ -26,7 +26,7 @@ import Issue from '../../../components/issue/Issue'; | |||
/*:: | |||
type Props = {| | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
checked: boolean, | |||
component?: Component, | |||
issue: IssueType, | |||
@@ -89,7 +89,7 @@ export default class ListItem extends React.PureComponent { | |||
}; | |||
render() { | |||
const { branch, component, issue, previousIssue } = this.props; | |||
const { branchLike, component, issue, previousIssue } = this.props; | |||
const displayComponent = previousIssue == null || previousIssue.component !== issue.component; | |||
@@ -98,7 +98,7 @@ export default class ListItem extends React.PureComponent { | |||
{displayComponent && ( | |||
<div className="issues-workspace-list-component"> | |||
<ComponentBreadcrumbs | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component} | |||
issue={this.props.issue} | |||
organization={this.props.organization} | |||
@@ -106,7 +106,7 @@ export default class ListItem extends React.PureComponent { | |||
</div> | |||
)} | |||
<Issue | |||
branch={branch} | |||
branchLike={branchLike} | |||
checked={this.props.checked} | |||
displayLocationsLink={false} | |||
issue={issue} |
@@ -20,6 +20,7 @@ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import ComponentBreadcrumbs from '../ComponentBreadcrumbs'; | |||
import { ShortLivingBranch, BranchType } from '../../../../app/types'; | |||
const baseIssue = { | |||
component: 'comp', | |||
@@ -40,5 +41,13 @@ it('renders with sub-project', () => { | |||
it('renders with branch', () => { | |||
const issue = { ...baseIssue, subProject: 'sub-proj', subProjectName: 'sub-proj-name' }; | |||
expect(shallow(<ComponentBreadcrumbs branch="feature" issue={issue} />)).toMatchSnapshot(); | |||
const shortBranch: ShortLivingBranch = { | |||
isMain: false, | |||
mergeBranch: '', | |||
name: 'feature', | |||
type: BranchType.SHORT | |||
}; | |||
expect( | |||
shallow(<ComponentBreadcrumbs branchLike={shortBranch} issue={issue} />) | |||
).toMatchSnapshot(); | |||
}); |
@@ -19,7 +19,6 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "proj", | |||
}, | |||
} | |||
@@ -37,10 +36,10 @@ exports[`renders 1`] = ` | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "comp", | |||
"id": "proj", | |||
"selected": "comp", | |||
}, | |||
} | |||
} | |||
@@ -71,10 +70,11 @@ exports[`renders with branch 1`] = ` | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": "feature", | |||
"id": "proj", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
@@ -94,10 +94,11 @@ exports[`renders with branch 1`] = ` | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": "feature", | |||
"id": "sub-proj", | |||
"resolved": "false", | |||
}, | |||
} | |||
} | |||
@@ -114,10 +115,11 @@ exports[`renders with branch 1`] = ` | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": "feature", | |||
"id": "comp", | |||
"id": "proj", | |||
"selected": "comp", | |||
}, | |||
} | |||
} | |||
@@ -150,7 +152,6 @@ exports[`renders with sub-project 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "proj", | |||
}, | |||
} | |||
@@ -173,7 +174,6 @@ exports[`renders with sub-project 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "sub-proj", | |||
}, | |||
} | |||
@@ -191,10 +191,10 @@ exports[`renders with sub-project 1`] = ` | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/dashboard", | |||
"pathname": "/code", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "comp", | |||
"id": "proj", | |||
"selected": "comp", | |||
}, | |||
} | |||
} |
@@ -21,15 +21,16 @@ import * as React from 'react'; | |||
import BadgeButton from './BadgeButton'; | |||
import BadgeParams from './BadgeParams'; | |||
import { BadgeType, BadgeOptions, getBadgeUrl } from './utils'; | |||
import { Metric } from '../../../app/types'; | |||
import { Metric, BranchLike } from '../../../app/types'; | |||
import CodeSnippet from '../../../components/common/CodeSnippet'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import './styles.css'; | |||
import { Button, ResetButtonLink } from '../../../components/ui/buttons'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
metrics: { [key: string]: Metric }; | |||
project: string; | |||
} | |||
@@ -64,10 +65,10 @@ export default class BadgesModal extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const { branch, project } = this.props; | |||
const { branchLike, project } = this.props; | |||
const { selectedType, badgeOptions } = this.state; | |||
const header = translate('overview.badges.title'); | |||
const fullBadgeOptions = { branch, project, ...badgeOptions }; | |||
const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) }; | |||
return ( | |||
<div className="overview-meta-card"> | |||
<Button className="js-project-badges" onClick={this.handleOpen}> |
@@ -21,13 +21,20 @@ import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import BadgesModal from '../BadgesModal'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import { ShortLivingBranch, BranchType } from '../../../../app/types'; | |||
jest.mock('../../../../helpers/urls', () => ({ | |||
getHostUrl: () => 'host' | |||
})); | |||
it('should display the modal after click', () => { | |||
const wrapper = shallow(<BadgesModal branch="branch-6.6" metrics={{}} project="foo" />); | |||
const shortBranch: ShortLivingBranch = { | |||
isMain: false, | |||
mergeBranch: '', | |||
name: 'branch-6.6', | |||
type: BranchType.SHORT | |||
}; | |||
const wrapper = shallow(<BadgesModal branchLike={shortBranch} metrics={{}} project="foo" />); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper.find('Button')); | |||
expect(wrapper.find('Modal')).toMatchSnapshot(); |
@@ -28,6 +28,7 @@ export interface BadgeOptions { | |||
color?: BadgeColors; | |||
project?: string; | |||
metric?: string; | |||
pullRequest?: string; | |||
} | |||
export enum BadgeType { | |||
@@ -38,19 +39,19 @@ export enum BadgeType { | |||
export function getBadgeUrl( | |||
type: BadgeType, | |||
{ branch, project, color = 'white', metric = 'alert_status' }: BadgeOptions | |||
{ branch, project, color = 'white', metric = 'alert_status', pullRequest }: BadgeOptions | |||
) { | |||
switch (type) { | |||
case BadgeType.marketing: | |||
return `${getHostUrl()}/images/project_badges/sonarcloud-${color}.svg`; | |||
case BadgeType.qualityGate: | |||
return `${getHostUrl()}/api/project_badges/quality_gate?${stringify( | |||
omitNil({ branch, project }) | |||
omitNil({ branch, project, pullRequest }) | |||
)}`; | |||
case BadgeType.measure: | |||
default: | |||
return `${getHostUrl()}/api/project_badges/measure?${stringify( | |||
omitNil({ branch, project, metric }) | |||
omitNil({ branch, project, metric, pullRequest }) | |||
)}`; | |||
} | |||
} |
@@ -21,12 +21,12 @@ import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import OverviewApp from './OverviewApp'; | |||
import EmptyOverview from './EmptyOverview'; | |||
import { getBranchName, isShortLivingBranch } from '../../../helpers/branches'; | |||
import { getProjectBranchUrl, getCodeUrl } from '../../../helpers/urls'; | |||
import { Branch, Component } from '../../../app/types'; | |||
import { Component, BranchLike } from '../../../app/types'; | |||
import { isShortLivingBranch } from '../../../helpers/branches'; | |||
import { getShortLivingBranchUrl, getCodeUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
branch?: Branch; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
isInProgress?: boolean; | |||
isPending?: boolean; | |||
@@ -39,7 +39,7 @@ export default class App extends React.PureComponent<Props> { | |||
}; | |||
componentDidMount() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
if (this.isPortfolio()) { | |||
this.context.router.replace({ | |||
@@ -48,10 +48,10 @@ export default class App extends React.PureComponent<Props> { | |||
}); | |||
} else if (this.isFile()) { | |||
this.context.router.replace( | |||
getCodeUrl(component.breadcrumbs[0].key, getBranchName(branch), component.key) | |||
getCodeUrl(component.breadcrumbs[0].key, branchLike, component.key) | |||
); | |||
} else if (isShortLivingBranch(branch)) { | |||
this.context.router.replace(getProjectBranchUrl(component.key, branch)); | |||
} else if (isShortLivingBranch(branchLike)) { | |||
this.context.router.replace(getShortLivingBranchUrl(component.key, branchLike.name)); | |||
} | |||
} | |||
@@ -60,9 +60,9 @@ export default class App extends React.PureComponent<Props> { | |||
isFile = () => ['FIL', 'UTS'].includes(this.props.component.qualifier); | |||
render() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branch)) { | |||
if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branchLike)) { | |||
return null; | |||
} | |||
@@ -77,7 +77,7 @@ export default class App extends React.PureComponent<Props> { | |||
return ( | |||
<OverviewApp | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component} | |||
onComponentChange={this.props.onComponentChange} | |||
/> |
@@ -36,14 +36,14 @@ import { getLeakPeriod, Period } from '../../../helpers/periods'; | |||
import { getCustomGraph, getGraph } from '../../../helpers/storage'; | |||
import { METRICS, HISTORY_METRICS_LIST } from '../utils'; | |||
import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils'; | |||
import { getBranchName } from '../../../helpers/branches'; | |||
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { fetchMetrics } from '../../../store/rootActions'; | |||
import { getMetrics } from '../../../store/rootReducer'; | |||
import { Branch, Component, Metric } from '../../../app/types'; | |||
import { BranchLike, Component, Metric } from '../../../app/types'; | |||
import '../styles.css'; | |||
interface OwnProps { | |||
branch?: Branch; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
onComponentChange: (changes: {}) => void; | |||
} | |||
@@ -79,7 +79,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
componentDidUpdate(prevProps: Props) { | |||
if ( | |||
this.props.component.key !== prevProps.component.key || | |||
this.props.branch !== prevProps.branch | |||
!isSameBranchLike(this.props.branchLike, prevProps.branchLike) | |||
) { | |||
this.loadMeasures().then(this.loadHistory, () => {}); | |||
} | |||
@@ -90,12 +90,12 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
} | |||
loadMeasures() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
this.setState({ loading: true }); | |||
return getMeasuresAndMeta(component.key, METRICS, { | |||
additionalFields: 'metrics,periods', | |||
branch: getBranchName(branch) | |||
...getBranchLikeQuery(branchLike) | |||
}).then( | |||
r => { | |||
if (this.mounted && r.metrics) { | |||
@@ -116,7 +116,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
} | |||
loadHistory = () => { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); | |||
if (!graphMetrics || graphMetrics.length <= 0) { | |||
@@ -124,22 +124,24 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
} | |||
const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics)); | |||
return getAllTimeMachineData(component.key, metrics, { branch: getBranchName(branch) }).then( | |||
r => { | |||
if (this.mounted) { | |||
const history: History = {}; | |||
r.measures.forEach(measure => { | |||
const measureHistory = measure.history.map(analysis => ({ | |||
date: parseDate(analysis.date), | |||
value: analysis.value | |||
})); | |||
history[measure.metric] = measureHistory; | |||
}); | |||
const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date; | |||
this.setState({ history, historyStartDate }); | |||
} | |||
return getAllTimeMachineData({ | |||
...getBranchLikeQuery(branchLike), | |||
component: component.key, | |||
metrics: metrics.join() | |||
}).then(r => { | |||
if (this.mounted) { | |||
const history: History = {}; | |||
r.measures.forEach(measure => { | |||
const measureHistory = measure.history.map(analysis => ({ | |||
date: parseDate(analysis.date), | |||
value: analysis.value | |||
})); | |||
history[measure.metric] = measureHistory; | |||
}); | |||
const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date; | |||
this.setState({ history, historyStartDate }); | |||
} | |||
); | |||
}); | |||
}; | |||
getApplicationLeakPeriod = () => | |||
@@ -156,7 +158,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
} | |||
render() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
const { loading, measures, periods, history, historyStartDate } = this.state; | |||
if (loading) { | |||
@@ -165,9 +167,8 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
const leakPeriod = | |||
component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods); | |||
const branchName = getBranchName(branch); | |||
const domainProps = { | |||
branch: branchName, | |||
branchLike, | |||
component, | |||
measures, | |||
leakPeriod, | |||
@@ -182,7 +183,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
{component.qualifier === 'APP' ? ( | |||
<ApplicationQualityGate component={component} /> | |||
) : ( | |||
<QualityGate branch={branchName} component={component} measures={measures} /> | |||
<QualityGate branchLike={branchLike} component={component} measures={measures} /> | |||
)} | |||
<div className="overview-domains-list"> | |||
@@ -195,7 +196,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
<div className="overview-sidebar page-sidebar-fixed"> | |||
<Meta | |||
branch={branchName} | |||
branchLike={branchLike} | |||
component={component} | |||
history={history} | |||
measures={measures} |
@@ -54,7 +54,7 @@ it('redirects on Code page for files', () => { | |||
qualifier: 'FIL' | |||
}; | |||
const replace = jest.fn(); | |||
mount(<App branch={branch} component={newComponent} onComponentChange={jest.fn()} />, { | |||
mount(<App branchLike={branch} component={newComponent} onComponentChange={jest.fn()} />, { | |||
context: { router: { replace } } | |||
}); | |||
expect(replace).toBeCalledWith({ |
@@ -23,11 +23,13 @@ import Analysis from './Analysis'; | |||
import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity'; | |||
import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Metric, Component } from '../../../app/types'; | |||
import { Metric, Component, BranchLike } from '../../../app/types'; | |||
import { History } from '../../../api/time-machine'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { getActivityUrl } from '../../../helpers/urls'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
history?: History; | |||
metrics: { [key: string]: Metric }; | |||
@@ -76,7 +78,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> { | |||
this.setState({ loading: true }); | |||
getProjectActivity({ | |||
branch: this.props.branch, | |||
...getBranchLikeQuery(this.props.branchLike), | |||
project: this.getTopLevelComponent(), | |||
ps: PAGE_SIZE | |||
}).then( | |||
@@ -101,7 +103,7 @@ export default class AnalysesList extends React.PureComponent<Props, State> { | |||
return ( | |||
<ul className="spacer-top"> | |||
{analyses.map(analysis => ( | |||
<Analysis key={analysis.key} analysis={analysis} qualifier={this.props.qualifier} /> | |||
<Analysis analysis={analysis} key={analysis.key} qualifier={this.props.qualifier} /> | |||
))} | |||
</ul> | |||
); | |||
@@ -121,20 +123,16 @@ export default class AnalysesList extends React.PureComponent<Props, State> { | |||
</h4> | |||
<PreviewGraph | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
history={this.props.history} | |||
project={this.props.component.key} | |||
metrics={this.props.metrics} | |||
project={this.props.component.key} | |||
/> | |||
{this.renderList(analyses)} | |||
<div className="spacer-top small"> | |||
<Link | |||
to={{ | |||
pathname: '/project/activity', | |||
query: { id: this.props.component.key, branch: this.props.branch } | |||
}}> | |||
<Link to={getActivityUrl(this.props.component.key, this.props.branchLike)}> | |||
{translate('show_more')} | |||
</Link> | |||
</div> |
@@ -0,0 +1,38 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 AnalysesList from '../AnalysesList'; | |||
it('should render show more link', () => { | |||
const branchLike = { analysisDate: '2018-03-08T09:49:22+0100', isMain: true, name: 'master' }; | |||
const component = { | |||
breadcrumbs: [{ key: 'foo', name: 'foo', qualifier: 'TRK' }], | |||
key: 'foo', | |||
name: 'foo', | |||
organization: 'org', | |||
qualifier: 'TRK' | |||
}; | |||
const wrapper = shallow( | |||
<AnalysesList branchLike={branchLike} component={component} metrics={{}} qualifier="TRK" /> | |||
); | |||
wrapper.setState({ loading: false }); | |||
expect(wrapper.find('Link')).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,18 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render show more link 1`] = ` | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/project/activity", | |||
"query": Object { | |||
"id": "foo", | |||
}, | |||
} | |||
} | |||
> | |||
show_more | |||
</Link> | |||
`; |
@@ -31,7 +31,7 @@ import { translate } from '../../../helpers/l10n'; | |||
export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | |||
renderHeader() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
return ( | |||
<div className="overview-card-header"> | |||
@@ -39,13 +39,13 @@ export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> { | |||
<span>{translate('metric.bugs.name')}</span> | |||
<Link | |||
className="button button-small spacer-left text-text-bottom" | |||
to={getComponentDrilldownUrl(component.key, 'Reliability', branch)}> | |||
to={getComponentDrilldownUrl(component.key, 'Reliability', branchLike)}> | |||
<BubblesIcon size={14} /> | |||
</Link> | |||
<span className="big-spacer-left">{translate('metric.vulnerabilities.name')}</span> | |||
<Link | |||
className="button button-small spacer-left text-text-bottom" | |||
to={getComponentDrilldownUrl(component.key, 'Security', branch)}> | |||
to={getComponentDrilldownUrl(component.key, 'Security', branchLike)}> | |||
<BubblesIcon size={14} /> | |||
</Link> | |||
</div> |
@@ -26,6 +26,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; | |||
import { getComponentIssuesUrl } from '../../../helpers/urls'; | |||
import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
export class CodeSmells extends React.PureComponent<ComposedProps> { | |||
renderHeader() { | |||
@@ -33,10 +34,15 @@ export class CodeSmells extends React.PureComponent<ComposedProps> { | |||
} | |||
renderDebt(metric: string, type: string) { | |||
const { branch, measures, component } = this.props; | |||
const { branchLike, measures, component } = this.props; | |||
const measure = measures.find(measure => measure.metric.key === metric); | |||
const value = measure ? this.props.getValue(measure) : undefined; | |||
const params = { branch, resolved: 'false', facetMode: 'effort', types: type }; | |||
const params = { | |||
...getBranchLikeQuery(branchLike), | |||
resolved: 'false', | |||
facetMode: 'effort', | |||
types: type | |||
}; | |||
if (isDiffMetric(metric)) { | |||
Object.assign(params, { sinceLeakPeriod: 'true' }); |
@@ -44,7 +44,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
} | |||
renderCoverage() { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
const metric = 'coverage'; | |||
const coverage = this.getCoverage(); | |||
@@ -56,7 +56,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
<div className="display-inline-block text-middle"> | |||
<div className="overview-domain-measure-value"> | |||
<DrilldownLink branch={branch} component={component.key} metric={metric}> | |||
<DrilldownLink branchLike={branchLike} component={component.key} metric={metric}> | |||
<span className="js-overview-main-coverage"> | |||
{formatMeasure(coverage, 'PERCENT')} | |||
</span> | |||
@@ -73,7 +73,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
} | |||
renderNewCoverage() { | |||
const { branch, component, leakPeriod, measures } = this.props; | |||
const { branchLike, component, leakPeriod, measures } = this.props; | |||
if (!leakPeriod) { | |||
return null; | |||
} | |||
@@ -85,7 +85,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
newCoverageMeasure && newCoverageValue !== undefined ? ( | |||
<div> | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component.key} | |||
metric={newCoverageMeasure.metric.key}> | |||
<span className="js-overview-main-new-coverage"> | |||
@@ -106,7 +106,7 @@ export class Coverage extends React.PureComponent<ComposedProps> { | |||
{translate('overview.coverage_on')} | |||
<br /> | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
className="spacer-right overview-domain-secondary-measure-value" | |||
component={component.key} | |||
metric={newLinesToCover.metric.key}> |
@@ -39,8 +39,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
} | |||
renderDuplications() { | |||
const { branch, component, measures } = this.props; | |||
const { branchLike, component, measures } = this.props; | |||
const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density'); | |||
if (!measure) { | |||
return null; | |||
@@ -57,7 +56,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
<div className="display-inline-block text-middle"> | |||
<div className="overview-domain-measure-value"> | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component.key} | |||
metric="duplicated_lines_density"> | |||
{formatMeasure(duplications, 'PERCENT')} | |||
@@ -74,11 +73,10 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
} | |||
renderNewDuplications() { | |||
const { branch, component, measures, leakPeriod } = this.props; | |||
const { branchLike, component, measures, leakPeriod } = this.props; | |||
if (!leakPeriod) { | |||
return null; | |||
} | |||
const newDuplicationsMeasure = measures.find( | |||
measure => measure.metric.key === 'new_duplicated_lines_density' | |||
); | |||
@@ -88,7 +86,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
newDuplicationsMeasure && newDuplicationsValue ? ( | |||
<div> | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component.key} | |||
metric={newDuplicationsMeasure.metric.key}> | |||
<span className="js-overview-main-new-duplications"> | |||
@@ -108,7 +106,7 @@ export class Duplications extends React.PureComponent<ComposedProps> { | |||
{translate('overview.duplications_on')} | |||
<br /> | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
className="spacer-right overview-domain-secondary-measure-value" | |||
component={component.key} | |||
metric={newLinesMeasure.metric.key}> |
@@ -40,11 +40,12 @@ import { | |||
getComponentIssuesUrl, | |||
getMeasureHistoryUrl | |||
} from '../../../helpers/urls'; | |||
import { Component } from '../../../app/types'; | |||
import { Component, BranchLike } from '../../../app/types'; | |||
import { History } from '../../../api/time-machine'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
export interface EnhanceProps { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
measures: MeasureEnhanced[]; | |||
leakPeriod?: { index: number; date?: string }; | |||
@@ -77,14 +78,14 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
}; | |||
renderHeader = (domain: string, label: string) => { | |||
const { branch, component } = this.props; | |||
const { branchLike, component } = this.props; | |||
return ( | |||
<div className="overview-card-header"> | |||
<div className="overview-title"> | |||
<span>{label}</span> | |||
<Link | |||
className="button button-small spacer-left text-text-bottom" | |||
to={getComponentDrilldownUrl(component.key, domain, branch)}> | |||
to={getComponentDrilldownUrl(component.key, domain, branchLike)}> | |||
<BubblesIcon size={14} /> | |||
</Link> | |||
</div> | |||
@@ -93,7 +94,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
}; | |||
renderMeasure = (metricKey: string) => { | |||
const { branch, measures, component } = this.props; | |||
const { branchLike, measures, component } = this.props; | |||
const measure = measures.find(measure => measure.metric.key === metricKey); | |||
if (!measure) { | |||
return null; | |||
@@ -102,7 +103,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
return ( | |||
<div className="overview-domain-measure"> | |||
<div className="overview-domain-measure-value"> | |||
<DrilldownLink branch={branch} component={component.key} metric={metricKey}> | |||
<DrilldownLink branchLike={branchLike} component={component.key} metric={metricKey}> | |||
<span className="js-overview-main-tests"> | |||
{formatMeasure(measure.value, getShortType(measure.metric.type))} | |||
</span> | |||
@@ -118,7 +119,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
}; | |||
renderRating = (metricKey: string) => { | |||
const { branch, component, measures } = this.props; | |||
const { branchLike, component, measures } = this.props; | |||
const measure = measures.find(measure => measure.metric.key === metricKey); | |||
if (!measure) { | |||
return null; | |||
@@ -130,7 +131,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
<Tooltip overlay={title} placement="top"> | |||
<div className="overview-domain-measure-sup"> | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
className="link-no-underline" | |||
component={component.key} | |||
metric={metricKey}> | |||
@@ -142,14 +143,14 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
}; | |||
renderIssues = (metric: string, type: string) => { | |||
const { branch, measures, component } = this.props; | |||
const { branchLike, measures, component } = this.props; | |||
const measure = measures.find(measure => measure.metric.key === metric); | |||
if (!measure) { | |||
return null; | |||
} | |||
const value = this.getValue(measure); | |||
const params = { branch, resolved: 'false', types: type }; | |||
const params = { ...getBranchLikeQuery(branchLike), resolved: 'false', types: type }; | |||
if (isDiffMetric(metric)) { | |||
Object.assign(params, { sinceLeakPeriod: 'true' }); | |||
} | |||
@@ -166,7 +167,7 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP | |||
return ( | |||
<Link | |||
className={linkClass} | |||
to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}> | |||
to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branchLike)}> | |||
<HistoryIcon /> | |||
</Link> | |||
); |
@@ -28,13 +28,13 @@ import MetaSize from './MetaSize'; | |||
import MetaTags from './MetaTags'; | |||
import BadgesModal from '../badges/BadgesModal'; | |||
import AnalysesList from '../events/AnalysesList'; | |||
import { Visibility, Component, Metric } from '../../../app/types'; | |||
import { Visibility, Component, Metric, BranchLike } from '../../../app/types'; | |||
import { History } from '../../../api/time-machine'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { MeasureEnhanced } from '../../../helpers/measures'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: Component; | |||
history?: History; | |||
measures: MeasureEnhanced[]; | |||
@@ -50,7 +50,7 @@ export default class Meta extends React.PureComponent<Props> { | |||
render() { | |||
const { onSonarCloud, organizationsEnabled } = this.context; | |||
const { branch, component, metrics } = this.props; | |||
const { branchLike, component, metrics } = this.props; | |||
const { qualifier, description, qualityProfiles, qualityGate, visibility } = component; | |||
const isProject = qualifier === 'TRK'; | |||
@@ -66,11 +66,11 @@ export default class Meta extends React.PureComponent<Props> { | |||
{isProject && ( | |||
<MetaTags component={component} onComponentChange={this.props.onComponentChange} /> | |||
)} | |||
<MetaSize branch={branch} component={component} measures={this.props.measures} /> | |||
<MetaSize branchLike={branchLike} component={component} measures={this.props.measures} /> | |||
</div> | |||
<AnalysesList | |||
branch={branch} | |||
branchLike={branchLike} | |||
component={component} | |||
history={this.props.history} | |||
metrics={metrics} | |||
@@ -106,7 +106,9 @@ export default class Meta extends React.PureComponent<Props> { | |||
{onSonarCloud && | |||
isProject && | |||
!isPrivate && <BadgesModal branch={branch} metrics={metrics} project={component.key} />} | |||
!isPrivate && ( | |||
<BadgesModal branchLike={branchLike} metrics={metrics} project={component.key} /> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -25,10 +25,10 @@ import SizeRating from '../../../components/ui/SizeRating'; | |||
import { formatMeasure, MeasureEnhanced } from '../../../helpers/measures'; | |||
import { getMetricName } from '../helpers/metrics'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { LightComponent } from '../../../app/types'; | |||
import { LightComponent, BranchLike } from '../../../app/types'; | |||
interface Props { | |||
branch?: string; | |||
branchLike?: BranchLike; | |||
component: LightComponent; | |||
measures: MeasureEnhanced[]; | |||
} | |||
@@ -43,7 +43,10 @@ export default class MetaSize extends React.PureComponent<Props> { | |||
<span className="spacer-right"> | |||
<SizeRating value={Number(ncloc.value)} /> | |||
</span> | |||
<DrilldownLink branch={this.props.branch} component={this.props.component.key} metric="ncloc"> | |||
<DrilldownLink | |||
branchLike={this.props.branchLike} | |||
component={this.props.component.key} | |||
metric="ncloc"> | |||
{formatMeasure(ncloc.value, 'SHORT_INT')} | |||
</DrilldownLink> | |||
<div className="spacer-top text-muted">{getMetricName('ncloc')}</div> | |||
@@ -71,7 +74,7 @@ export default class MetaSize extends React.PureComponent<Props> { | |||
return projects ? ( | |||
<div id="overview-projects" className="overview-meta-size-ncloc is-half-width"> | |||
<DrilldownLink | |||
branch={this.props.branch} | |||
branchLike={this.props.branchLike} | |||
component={this.props.component.key} | |||
metric="projects"> | |||
{formatMeasure(projects.value, 'SHORT_INT')} |
@@ -38,13 +38,13 @@ function isProject(component /*: Component */) { | |||
/*:: | |||
type Props = { | |||
branch?: string, | |||
branchLike?: {id?: string; name: string }, | |||
component: Component, | |||
measures: MeasuresList | |||
}; | |||
*/ | |||
export default function QualityGate({ branch, component, measures } /*: Props */) { | |||
export default function QualityGate({ branchLike, component, measures } /*: Props */) { | |||
const statusMeasure = measures.find(measure => measure.metric.key === 'alert_status'); | |||
const detailsMeasure = measures.find(measure => measure.metric.key === 'quality_gate_details'); | |||
@@ -81,7 +81,11 @@ export default function QualityGate({ branch, component, measures } /*: Props */ | |||
)} | |||
{conditions.length > 0 && ( | |||
<QualityGateConditions branch={branch} component={component} conditions={conditions} /> | |||
<QualityGateConditions | |||
branchLike={branchLike} | |||
component={component} | |||
conditions={conditions} | |||
/> | |||
)} | |||
</div> | |||
); |
@@ -27,12 +27,13 @@ import IssueTypeIcon from '../../../components/ui/IssueTypeIcon'; | |||
import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getComponentIssuesUrl } from '../../../helpers/urls'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
/*:: import type { Component } from '../types'; */ | |||
/*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ | |||
export default class QualityGateCondition extends React.PureComponent { | |||
/*:: props: { | |||
branch?: string, | |||
branchLike?: { id?: string; name: string }, | |||
component: Component, | |||
condition: { | |||
level: string, | |||
@@ -54,7 +55,11 @@ export default class QualityGateCondition extends React.PureComponent { | |||
} | |||
getIssuesUrl = (sinceLeakPeriod /*: boolean */, customQuery /*: {} */) => { | |||
const query /*: Object */ = { resolved: 'false', branch: this.props.branch, ...customQuery }; | |||
const query /*: Object */ = { | |||
resolved: 'false', | |||
...getBranchLikeQuery(this.props.branchLike), | |||
...customQuery | |||
}; | |||
if (sinceLeakPeriod) { | |||
Object.assign(query, { sinceLeakPeriod: 'true' }); | |||
} | |||
@@ -89,7 +94,7 @@ export default class QualityGateCondition extends React.PureComponent { | |||
} | |||
wrapWithLink(children /*: React.Element<*> */) { | |||
const { branch, component, condition } = this.props; | |||
const { branchLike, component, condition } = this.props; | |||
const className = classNames( | |||
'overview-quality-gate-condition', | |||
@@ -114,7 +119,7 @@ export default class QualityGateCondition extends React.PureComponent { | |||
</Link> | |||
) : ( | |||
<DrilldownLink | |||
branch={branch} | |||
branchLike={branchLike} | |||
className={className} | |||
component={component.key} | |||
metric={condition.measure.metric.key} |
@@ -19,10 +19,12 @@ | |||
*/ | |||
import React from 'react'; | |||
import { sortBy } from 'lodash'; | |||
import PropTypes from 'prop-types'; | |||
import QualityGateCondition from './QualityGateCondition'; | |||
import { ComponentType, ConditionsListType } from '../propTypes'; | |||
import { getMeasuresAndMeta } from '../../../api/measures'; | |||
import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; | |||
import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; | |||
const LEVEL_ORDER = ['ERROR', 'WARN']; | |||
@@ -35,7 +37,7 @@ function enhanceConditions(conditions, measures) { | |||
export default class QualityGateConditions extends React.PureComponent { | |||
static propTypes = { | |||
// branch | |||
branchLike: PropTypes.object, | |||
component: ComponentType.isRequired, | |||
conditions: ConditionsListType.isRequired | |||
}; | |||
@@ -51,7 +53,7 @@ export default class QualityGateConditions extends React.PureComponent { | |||
componentDidUpdate(prevProps) { | |||
if ( | |||
prevProps.branch !== this.props.branch || | |||
!isSameBranchLike(prevProps.branchLike, this.props.branchLike) || | |||
prevProps.conditions !== this.props.conditions || | |||
prevProps.component !== this.props.component | |||
) { | |||
@@ -64,13 +66,13 @@ export default class QualityGateConditions extends React.PureComponent { | |||
} | |||
loadFailedMeasures() { | |||
const { branch, component, conditions } = this.props; | |||
const { branchLike, component, conditions } = this.props; | |||
const failedConditions = conditions.filter(c => c.level !== 'OK'); | |||
if (failedConditions.length > 0) { | |||
const metrics = failedConditions.map(condition => condition.metric); | |||
getMeasuresAndMeta(component.key, metrics, { | |||
additionalFields: 'metrics', | |||
branch | |||
...getBranchLikeQuery(branchLike) | |||
}).then(r => { | |||
if (this.mounted) { | |||
const measures = enhanceMeasuresWithMetrics(r.component.measures, r.metrics); |
@@ -9,7 +9,6 @@ exports[`renders 1`] = ` | |||
Object { | |||
"pathname": "/dashboard", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "foo", | |||
}, | |||
} |
@@ -9,7 +9,6 @@ exports[`new_maintainability_rating 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"sinceLeakPeriod": "true", | |||
@@ -104,7 +103,6 @@ exports[`new_reliability_rating 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR", | |||
@@ -158,7 +156,6 @@ exports[`new_security_rating 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR", | |||
@@ -254,7 +251,6 @@ exports[`reliability_rating 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR", | |||
@@ -307,7 +303,6 @@ exports[`security_rating 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"severities": "BLOCKER,CRITICAL,MAJOR,MINOR", | |||
@@ -360,7 +355,6 @@ exports[`should work with branch 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": "feature", | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"sinceLeakPeriod": "true", | |||
@@ -413,7 +407,6 @@ exports[`sqale_rating 1`] = ` | |||
Object { | |||
"pathname": "/project/issues", | |||
"query": Object { | |||
"branch": undefined, | |||
"id": "abcd-key", | |||
"resolved": "false", | |||
"types": "CODE_SMELL", |
@@ -60,13 +60,13 @@ export default class Activity extends React.PureComponent<Props> { | |||
fetchHistory = () => { | |||
const { component } = this.props; | |||
let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); | |||
let graphMetrics: string[] = getDisplayedHistoryMetrics(getGraph(), getCustomGraph()); | |||
if (!graphMetrics || graphMetrics.length <= 0) { | |||
graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); | |||
} | |||
this.setState({ loading: true }); | |||
return getAllTimeMachineData(component, graphMetrics).then( | |||
return getAllTimeMachineData({ component, metrics: graphMetrics.join() }).then( | |||
timeMachine => { | |||
if (this.mounted) { | |||
const history: History = {}; |