Explorar el Código

SONAR-11476 Stop supporting homepage and other space pages for modules and directories (#951)

tags/7.6
Stas Vilchik hace 5 años
padre
commit
b44c75c2b9
Se han modificado 25 ficheros con 156 adiciones y 722 borrados
  1. 24
    27
      server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
  2. 42
    102
      server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
  3. 2
    6
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
  4. 1
    5
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx
  5. 2
    2
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx
  6. 2
    123
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
  7. 23
    9
      server/sonar-web/src/main/js/app/components/search/Search.tsx
  8. 7
    5
      server/sonar-web/src/main/js/app/components/search/SearchResult.tsx
  9. 14
    24
      server/sonar-web/src/main/js/apps/code/components/Component.tsx
  10. 0
    40
      server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx
  11. 4
    6
      server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx
  12. 0
    1
      server/sonar-web/src/main/js/apps/issues/components/App.tsx
  13. 4
    38
      server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx
  14. 0
    2
      server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
  15. 2
    34
      server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx
  16. 7
    176
      server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap
  17. 1
    8
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  18. 2
    28
      server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx
  19. 1
    5
      server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
  20. 1
    3
      server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
  21. 2
    4
      server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
  22. 6
    51
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap
  23. 1
    2
      server/sonar-web/src/main/js/components/SourceViewer/styles.css
  24. 8
    0
      server/sonar-web/src/main/js/store/rootActions.ts
  25. 0
    21
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 24
- 27
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx Ver fichero

@@ -23,13 +23,12 @@ import { differenceBy } from 'lodash';
import { ComponentContext } from './ComponentContext';
import ComponentContainerNotFound from './ComponentContainerNotFound';
import ComponentNav from './nav/component/ComponentNav';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
import { getBranches, getPullRequests } from '../../api/branches';
import { getTasksForComponent, getAnalysisStatus } from '../../api/ce';
import { getComponentData } from '../../api/components';
import { getMeasures } from '../../api/measures';
import { getComponentNavigation } from '../../api/nav';
import { fetchOrganization } from '../../store/rootActions';
import { fetchOrganization, requireAuthorization } from '../../store/rootActions';
import { STATUSES } from '../../apps/background-tasks/constants';
import {
isPullRequest,
@@ -39,15 +38,15 @@ import {
isShortLivingBranch,
getBranchLikeQuery
} from '../../helpers/branches';
import { Store, getAppState } from '../../store/rootReducer';
import { isSonarCloud } from '../../helpers/system';
import { withRouter, Router, Location } from '../../components/hoc/withRouter';

interface Props {
appState: Pick<T.AppState, 'organizationsEnabled'>;
children: any;
children: React.ReactElement<any>;
fetchOrganization: (organization: string) => void;
location: {
query: { branch?: string; id: string; pullRequest?: string };
};
location: Pick<Location, 'query'>;
requireAuthorization: (router: Pick<Router, 'replace'>) => void;
router: Pick<Router, 'replace'>;
}

interface State {
@@ -74,13 +73,13 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
this.fetchComponent();
}

componentWillReceiveProps(nextProps: Props) {
componentDidUpdate(prevProps: Props) {
if (
nextProps.location.query.id !== this.props.location.query.id ||
nextProps.location.query.branch !== this.props.location.query.branch ||
nextProps.location.query.pullRequest !== this.props.location.query.pullRequest
prevProps.location.query.id !== this.props.location.query.id ||
prevProps.location.query.branch !== this.props.location.query.branch ||
prevProps.location.query.pullRequest !== this.props.location.query.pullRequest
) {
this.fetchComponent(nextProps);
this.fetchComponent();
}
}

@@ -94,16 +93,16 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
qualifier: component.breadcrumbs[component.breadcrumbs.length - 1].qualifier
});

fetchComponent(props = this.props) {
const { branch, id: key, pullRequest } = props.location.query;
fetchComponent() {
const { branch, id: key, pullRequest } = this.props.location.query;
this.setState({ loading: true });

const onError = (response?: Response) => {
if (this.mounted) {
if (response && response.status === 403) {
handleRequiredAuthorization();
this.props.requireAuthorization(this.props.router);
} else {
this.setState({ loading: false });
this.setState({ component: undefined, loading: false });
}
}
};
@@ -115,7 +114,7 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
.then(([nav, data]) => {
const component = this.addQualifier({ ...nav, ...data });

if (this.props.appState.organizationsEnabled) {
if (isSonarCloud()) {
this.props.fetchOrganization(component.organization);
}
return component;
@@ -375,13 +374,11 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
}
}

const mapStateToProps = (state: Store) => ({
appState: getAppState(state)
});

const mapDispatchToProps = { fetchOrganization };
const mapDispatchToProps = { fetchOrganization, requireAuthorization };

export default connect(
mapStateToProps,
mapDispatchToProps
)(ComponentContainer);
export default withRouter(
connect(
null,
mapDispatchToProps
)(ComponentContainer)
);

+ 42
- 102
server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx Ver fichero

@@ -18,7 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { shallow, mount } from 'enzyme';
import { shallow } from 'enzyme';
import { Location } from 'history';
import { ComponentContainer } from '../ComponentContainer';
import { getBranches, getPullRequests } from '../../../api/branches';
import { getTasksForComponent } from '../../../api/ce';
@@ -27,6 +28,7 @@ import { getComponentNavigation } from '../../../api/nav';
import { STATUSES } from '../../../apps/background-tasks/constants';
import { waitAndUpdate } from '../../../helpers/testUtils';
import { getMeasures } from '../../../api/measures';
import { isSonarCloud } from '../../../helpers/system';

jest.mock('../../../api/branches', () => ({
getBranches: jest.fn().mockResolvedValue([]),
@@ -58,6 +60,10 @@ jest.mock('../../../api/nav', () => ({
})
}));

jest.mock('../../../helpers/system', () => ({
isSonarCloud: jest.fn()
}));

// mock this, because some of its children are using redux store
jest.mock('../nav/component/ComponentNav', () => ({
default: () => null
@@ -65,6 +71,8 @@ jest.mock('../nav/component/ComponentNav', () => ({

const Inner = () => <div />;

const mainBranch: T.MainBranch = { isMain: true, name: 'master' };

beforeEach(() => {
(getBranches as jest.Mock).mockClear();
(getPullRequests as jest.Mock).mockClear();
@@ -72,18 +80,11 @@ beforeEach(() => {
(getComponentNavigation as jest.Mock).mockClear();
(getTasksForComponent as jest.Mock).mockClear();
(getMeasures as jest.Mock).mockClear();
(isSonarCloud as jest.Mock).mockReturnValue(false).mockClear();
});

it('changes component', () => {
const wrapper = shallow<ComponentContainer>(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
wrapper.instance().mounted = true;
const wrapper = shallowRender();
wrapper.setState({
branchLikes: [{ isMain: true, name: 'master' }],
component: { qualifier: 'TRK', visibility: 'public' } as T.Component,
@@ -94,40 +95,8 @@ it('changes component', () => {
expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
});

it("loads branches for module's project", async () => {
(getComponentNavigation as jest.Mock<any>).mockResolvedValueOnce({
breadcrumbs: [
{ key: 'projectKey', name: 'project', qualifier: 'TRK' },
{ key: 'moduleKey', name: 'module', qualifier: 'BRC' }
]
});

mount(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'moduleKey' } }}>
<Inner />
</ComponentContainer>
);

await new Promise(setImmediate);
expect(getBranches).toBeCalledWith('projectKey');
expect(getPullRequests).toBeCalledWith('projectKey');
expect(getComponentData).toBeCalledWith({ component: 'moduleKey', branch: undefined });
expect(getComponentNavigation).toBeCalledWith({ component: 'moduleKey', branch: undefined });
});

it("doesn't load branches portfolio", async () => {
const wrapper = mount(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
);

const wrapper = shallowRender({ location: { query: { id: 'portfolioKey' } } as Location });
await new Promise(setImmediate);
expect(getBranches).not.toBeCalled();
expect(getPullRequests).not.toBeCalled();
@@ -138,21 +107,15 @@ it("doesn't load branches portfolio", async () => {
});

it('updates branches on change', () => {
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'portfolioKey' } }}>
<Inner />
</ComponentContainer>
);
(wrapper.instance() as ComponentContainer).mounted = true;
const wrapper = shallowRender({ location: { query: { id: 'portfolioKey' } } as Location });
wrapper.setState({
branches: [{ isMain: true }],
component: { breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: 'TRK' }] },
branchLikes: [mainBranch],
component: {
breadcrumbs: [{ key: 'projectKey', name: 'project', qualifier: 'TRK' }]
} as T.Component,
loading: false
});
(wrapper.find(Inner).prop('onBranchesChange') as Function)();
wrapper.find(Inner).prop<Function>('onBranchesChange')();
expect(getBranches).toBeCalledWith('projectKey');
expect(getPullRequests).toBeCalledWith('projectKey');
});
@@ -166,18 +129,12 @@ it('updates the branch measures', async () => {
{ isMain: false, mergeBranch: 'master', name: 'feature', type: 'SHORT' }
]);
(getPullRequests as jest.Mock<any>).mockResolvedValueOnce([]);
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo', branch: 'feature' } }}>
<Inner />
</ComponentContainer>
);
(wrapper.instance() as ComponentContainer).mounted = true;
const wrapper = shallowRender({
location: { query: { id: 'foo', branch: 'feature' } } as Location
});
wrapper.setState({
branches: [{ isMain: true }],
component: { breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: 'TRK' }] },
branchLikes: [mainBranch],
component: { breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: 'TRK' }] } as T.Component,
loading: false
});

@@ -193,18 +150,11 @@ it('updates the branch measures', async () => {
});

it('loads organization', async () => {
(isSonarCloud as jest.Mock).mockReturnValue(true);
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });

const fetchOrganization = jest.fn();
mount(
<ComponentContainer
appState={{ organizationsEnabled: true }}
fetchOrganization={fetchOrganization}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);

shallowRender({ fetchOrganization });
await new Promise(setImmediate);
expect(fetchOrganization).toBeCalledWith('org');
});
@@ -212,30 +162,14 @@ it('loads organization', async () => {
it('fetches status', async () => {
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ organization: 'org' });

mount(
<ComponentContainer
appState={{ organizationsEnabled: true }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);

shallowRender();
await new Promise(setImmediate);
expect(getTasksForComponent).toBeCalledWith('portfolioKey');
});

it('filters correctly the pending tasks for a main branch', () => {
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);

const component = wrapper.instance() as ComponentContainer;
const wrapper = shallowRender();
const component = wrapper.instance();
const mainBranch: T.MainBranch = { isMain: true, name: 'master' };
const shortBranch: T.ShortLivingBranch = {
isMain: false,
@@ -294,14 +228,7 @@ it('reload component after task progress finished', async () => {
jest.useFakeTimers();
const inProgressTask = { id: 'foo', status: STATUSES.IN_PROGRESS } as T.Task;
(getTasksForComponent as jest.Mock<any>).mockResolvedValueOnce({ queue: [inProgressTask] });
const wrapper = shallow(
<ComponentContainer
appState={{ organizationsEnabled: false }}
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}>
<Inner />
</ComponentContainer>
);
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(getComponentNavigation).toHaveBeenCalledTimes(1);
expect(getTasksForComponent).toHaveBeenCalledTimes(1);
@@ -317,3 +244,16 @@ it('reload component after task progress finished', async () => {
expect(getComponentNavigation).toHaveBeenCalledTimes(2);
expect(getTasksForComponent).toHaveBeenCalledTimes(3);
});

function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
return shallow<ComponentContainer>(
<ComponentContainer
fetchOrganization={jest.fn()}
location={{ query: { id: 'foo' } }}
requireAuthorization={jest.fn()}
router={{ replace: jest.fn() }}
{...props}>
<Inner />
</ComponentContainer>
);
}

+ 2
- 6
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx Ver fichero

@@ -27,7 +27,6 @@ import OrganizationAvatar from '../../../../components/common/OrganizationAvatar
import OrganizationHelmet from '../../../../components/common/OrganizationHelmet';
import OrganizationLink from '../../../../components/ui/OrganizationLink';
import { sanitizeAlmId } from '../../../../helpers/almIntegrations';
import { collapsePath } from '../../../../helpers/path';
import { getProjectUrl, getBaseUrl } from '../../../../helpers/urls';
import { isSonarCloud } from '../../../../helpers/system';
import { isMainBranch } from '../../../../helpers/branches';
@@ -102,9 +101,6 @@ export function ComponentNavHeader(props: Props) {
function renderBreadcrumbs(breadcrumbs: T.Breadcrumb[], shouldLinkLast: boolean) {
const lastItem = breadcrumbs[breadcrumbs.length - 1];
return breadcrumbs.map((item, index) => {
const isPath = item.qualifier === 'DIR';
const itemName = isPath ? collapsePath(item.name, 15) : item.name;

return (
<React.Fragment key={item.key}>
{index === 0 && <QualifierIcon className="spacer-right" qualifier={lastItem.qualifier} />}
@@ -113,11 +109,11 @@ function renderBreadcrumbs(breadcrumbs: T.Breadcrumb[], shouldLinkLast: boolean)
className="navbar-context-header-breadcrumb-link link-base-color link-no-underline"
title={item.name}
to={getProjectUrl(item.key)}>
{itemName}
{item.name}
</Link>
) : (
<span className="navbar-context-header-breadcrumb-link" title={item.name}>
{itemName}
{item.name}
</span>
)}
{index < breadcrumbs.length - 1 && <span className="slash-separator" />}

+ 1
- 5
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMenu.tsx Ver fichero

@@ -182,11 +182,7 @@ export class ComponentNavMenu extends React.PureComponent<Props> {
}

renderSecurityReports() {
const { branchLike, component } = this.props;

if (component.qualifier === 'BRC' || component.qualifier === 'DIR') {
return null;
}
const { branchLike } = this.props;

if (isShortLivingBranch(branchLike) || isPullRequest(branchLike)) {
return null;

+ 2
- 2
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavMenu-test.tsx Ver fichero

@@ -114,8 +114,8 @@ it('should work for long-living branches', () => {
});

it('should work for all qualifiers', () => {
['TRK', 'BRC', 'VW', 'SVW', 'APP'].forEach(checkWithQualifier);
expect.assertions(5);
['TRK', 'VW', 'SVW', 'APP'].forEach(checkWithQualifier);
expect.assertions(4);

function checkWithQualifier(qualifier: string) {
const component = { ...baseComponent, configuration: { showSettings: true }, qualifier };

+ 2
- 123
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap Ver fichero

@@ -218,127 +218,6 @@ exports[`should work for all qualifiers 1`] = `
`;

exports[`should work for all qualifiers 2`] = `
<NavBarTabs>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"id": "foo",
},
}
}
>
overview.page
</Link>
</li>
<li>
<Link
activeClassName="active"
className=""
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"query": Object {
"id": "foo",
"resolved": "false",
},
}
}
>
issues.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/component_measures",
"query": Object {
"id": "foo",
},
}
}
>
layout.measures
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/code",
"query": Object {
"id": "foo",
},
}
}
>
code.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/activity",
"query": Object {
"id": "foo",
},
}
}
>
project_activity.page
</Link>
</li>
<Dropdown
data-test="administration"
overlay={
<ul
className="menu"
>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/settings",
"query": Object {
"id": "foo",
},
}
}
>
project_settings.page
</Link>
</li>
</ul>
}
tagName="li"
>
<Component />
</Dropdown>
</NavBarTabs>
`;

exports[`should work for all qualifiers 3`] = `
<NavBarTabs>
<li>
<Link
@@ -504,7 +383,7 @@ exports[`should work for all qualifiers 3`] = `
</NavBarTabs>
`;

exports[`should work for all qualifiers 4`] = `
exports[`should work for all qualifiers 3`] = `
<NavBarTabs>
<li>
<Link
@@ -641,7 +520,7 @@ exports[`should work for all qualifiers 4`] = `
</NavBarTabs>
`;

exports[`should work for all qualifiers 5`] = `
exports[`should work for all qualifiers 4`] = `
<NavBarTabs>
<li>
<Link

+ 23
- 9
server/sonar-web/src/main/js/app/components/search/Search.tsx Ver fichero

@@ -33,7 +33,7 @@ import { lazyLoad } from '../../../components/lazyLoad';
import { getSuggestions } from '../../../api/components';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { scrollToElement } from '../../../helpers/scrolling';
import { getProjectUrl } from '../../../helpers/urls';
import { getProjectUrl, getCodeUrl } from '../../../helpers/urls';
import './Search.css';

const SearchResults = lazyLoad(() => import('./SearchResults'));
@@ -162,13 +162,21 @@ export class Search extends React.PureComponent<Props, State> {
return next;
}, []);

mergeWithRecentlyBrowsed = (components: ComponentResult[]) => {
const recentlyBrowsed = RecentHistory.get().map(component => ({
...component,
isRecentlyBrowsed: true,
qualifier: component.icon.toUpperCase()
}));
return uniqBy([...components, ...recentlyBrowsed], 'key');
findFile = (key: string) => {
const findInResults = (results: ComponentResult[] | undefined) =>
results && results.find(r => r.key === key);

const file = findInResults(this.state.results['FIL']);
if (file) {
return file;
}

const test = findInResults(this.state.results['UTS']);
if (test) {
return test;
}

return undefined;
};

stopLoading = () => {
@@ -268,11 +276,17 @@ export class Search extends React.PureComponent<Props, State> {

openSelected = () => {
const { selected } = this.state;

if (selected) {
if (selected.startsWith('qualifier###')) {
this.searchMore(selected.substr(12));
} else {
this.props.router.push(getProjectUrl(selected));
const file = this.findFile(selected);
if (file) {
this.props.router.push(getCodeUrl(file.project!, undefined, file.key));
} else {
this.props.router.push(getProjectUrl(selected));
}
this.closeSearch();
}
}

+ 7
- 5
server/sonar-web/src/main/js/app/components/search/SearchResult.tsx Ver fichero

@@ -24,7 +24,7 @@ import FavoriteIcon from '../../../components/icons-components/FavoriteIcon';
import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import ClockIcon from '../../../components/icons-components/ClockIcon';
import Tooltip from '../../../components/controls/Tooltip';
import { getProjectUrl } from '../../../helpers/urls';
import { getProjectUrl, getCodeUrl } from '../../../helpers/urls';

interface Props {
appState: Pick<T.AppState, 'organizationsEnabled'>;
@@ -111,6 +111,11 @@ export default class SearchResult extends React.PureComponent<Props, State> {
render() {
const { component } = this.props;

const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS';
const to = isFile
? getCodeUrl(component.project!, undefined, component.key)
: getProjectUrl(component.key);

return (
<li
className={this.props.selected ? 'active' : undefined}
@@ -121,10 +126,7 @@ export default class SearchResult extends React.PureComponent<Props, State> {
overlay={component.key}
placement="left"
visible={this.state.tooltipVisible}>
<Link
data-key={component.key}
onClick={this.props.onClose}
to={getProjectUrl(component.key)}>
<Link data-key={component.key} onClick={this.props.onClose} to={to}>
<span className="navbar-search-item-link" onMouseEnter={this.handleMouseEnter}>
<span className="navbar-search-item-icons little-spacer-right">
{component.isFavorite && <FavoriteIcon favorite={true} size={12} />}

+ 14
- 24
server/sonar-web/src/main/js/apps/code/components/Component.tsx Ver fichero

@@ -21,7 +21,6 @@ import * as classNames from 'classnames';
import * as React from 'react';
import ComponentName from './ComponentName';
import ComponentMeasure from './ComponentMeasure';
import ComponentLink from './ComponentLink';
import ComponentPin from './ComponentPin';
import { WorkspaceContext } from '../../../components/workspace/context';

@@ -85,34 +84,25 @@ export default class Component extends React.PureComponent<Props> {
selected = false
} = this.props;

let componentAction = null;

if (!component.refKey || component.qualifier === 'SVW') {
switch (component.qualifier) {
case 'FIL':
case 'UTS':
componentAction = (
<WorkspaceContext.Consumer>
{({ openComponent }) => (
<ComponentPin
branchLike={branchLike}
component={component}
openComponent={openComponent}
/>
)}
</WorkspaceContext.Consumer>
);
break;
default:
componentAction = <ComponentLink branchLike={branchLike} component={component} />;
}
}
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS';

return (
<tr className={classNames({ selected })} ref={node => (this.node = node)}>
<td className="blank" />
<td className="thin nowrap">
<span className="spacer-right">{componentAction}</span>
<span className="spacer-right">
{isFile && (
<WorkspaceContext.Consumer>
{({ openComponent }) => (
<ComponentPin
branchLike={branchLike}
component={component}
openComponent={openComponent}
/>
)}
</WorkspaceContext.Consumer>
)}
</span>
</td>
<td className="code-name-cell">
<ComponentName

+ 0
- 40
server/sonar-web/src/main/js/apps/code/components/ComponentLink.tsx Ver fichero

@@ -1,40 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2019 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { Link } from 'react-router';
import LinkIcon from '../../../components/icons-components/LinkIcon';
import { translate } from '../../../helpers/l10n';
import { getBranchLikeUrl } from '../../../helpers/urls';

interface Props {
branchLike?: T.BranchLike;
component: T.ComponentMeasure;
}

export default function ComponentLink({ component, branchLike }: Props) {
return (
<Link
className="link-no-underline"
title={translate('code.open_component_page')}
to={getBranchLikeUrl(component.refKey || component.key, branchLike)}>
<LinkIcon />
</Link>
);
}

+ 4
- 6
server/sonar-web/src/main/js/apps/component-measures/drilldown/TreeMapView.tsx Ver fichero

@@ -28,7 +28,6 @@ import QualifierIcon from '../../../components/icons-components/QualifierIcon';
import TreeMap, { TreeMapItem } from '../../../components/charts/TreeMap';
import { translate, translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import { getBranchLikeUrl } from '../../../helpers/urls';
import { isDefined } from '../../../helpers/types';

interface Props {
@@ -54,13 +53,13 @@ export default class TreeMapView extends React.PureComponent<Props, State> {
this.state = { treemapItems: this.getTreemapComponents(props) };
}

componentWillReceiveProps(nextProps: Props) {
if (nextProps.components !== this.props.components || nextProps.metric !== this.props.metric) {
this.setState({ treemapItems: this.getTreemapComponents(nextProps) });
componentDidUpdate(prevProps: Props) {
if (prevProps.components !== this.props.components || prevProps.metric !== this.props.metric) {
this.setState({ treemapItems: this.getTreemapComponents(this.props) });
}
}

getTreemapComponents = ({ branchLike, components, metric }: Props) => {
getTreemapComponents = ({ components, metric }: Props) => {
const colorScale = this.getColorScale(metric);
return components
.map(component => {
@@ -85,7 +84,6 @@ export default class TreeMapView extends React.PureComponent<Props, State> {
icon: <QualifierIcon fill={theme.baseFontColor} qualifier={component.qualifier} />,
key: component.refKey || component.key,
label: component.name,
link: getBranchLikeUrl(component.refKey || component.key, branchLike),
size: sizeValue,
tooltip: this.getTooltip({
colorMetric: metric,

+ 0
- 1
server/sonar-web/src/main/js/apps/issues/components/App.tsx Ver fichero

@@ -1110,7 +1110,6 @@ export class App extends React.PureComponent<Props, State> {
{openIssue ? (
<div className="pull-left width-60">
<ComponentBreadcrumbs
branchLike={this.props.branchLike}
component={component}
issue={openIssue}
organization={this.props.organization}

+ 4
- 38
server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx Ver fichero

@@ -18,14 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { Link } from 'react-router';
import { getSelectedLocation } from '../utils';
import Organization from '../../../components/shared/Organization';
import { collapsePath, limitComponentName } from '../../../helpers/path';
import { getBranchLikeUrl, getCodeUrl } from '../../../helpers/urls';

interface Props {
branchLike?: T.BranchLike;
component?: T.Component;
issue: Pick<
T.Issue,
@@ -46,9 +43,7 @@ interface Props {
}

export default function ComponentBreadcrumbs({
branchLike,
component,
link = true,
issue,
organization,
selectedFlowIndex,
@@ -60,28 +55,15 @@ export default function ComponentBreadcrumbs({
const displaySubProject = !component || !['BRC', 'DIR'].includes(component.qualifier);

const selectedLocation = getSelectedLocation(issue, selectedFlowIndex, selectedLocationIndex);
const componentKey = selectedLocation ? selectedLocation.component : issue.component;
const componentName = selectedLocation ? selectedLocation.componentName : issue.componentLongName;

return (
<div className="component-name text-ellipsis">
{displayOrganization && (
<Organization
link={link}
linkClassName="link-no-underline"
organizationKey={issue.organization}
/>
)}
{displayOrganization && <Organization link={false} organizationKey={issue.organization} />}

{displayProject && (
<span title={issue.projectName}>
{link ? (
<Link className="link-no-underline" to={getBranchLikeUrl(issue.project, branchLike)}>
{limitComponentName(issue.projectName)}
</Link>
) : (
limitComponentName(issue.projectName)
)}
{limitComponentName(issue.projectName)}
<span className="slash-separator" />
</span>
)}
@@ -90,28 +72,12 @@ export default function ComponentBreadcrumbs({
issue.subProject !== undefined &&
issue.subProjectName !== undefined && (
<span title={issue.subProjectName}>
{link ? (
<Link
className="link-no-underline"
to={getBranchLikeUrl(issue.subProject, branchLike)}>
{limitComponentName(issue.subProjectName)}
</Link>
) : (
limitComponentName(issue.subProjectName)
)}
{limitComponentName(issue.subProjectName)}
<span className="slash-separator" />
</span>
)}

{link ? (
<Link
className="link-no-underline"
to={getCodeUrl(issue.project, branchLike, componentKey)}>
<span title={componentName}>{collapsePath(componentName || '')}</span>
</Link>
) : (
collapsePath(componentName || '')
)}
{collapsePath(componentName || '')}
</div>
);
}

+ 0
- 2
server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx Ver fichero

@@ -105,10 +105,8 @@ export default class ListItem extends React.PureComponent<Props, State> {
{displayComponent && (
<div className="issues-workspace-list-component note">
<ComponentBreadcrumbs
branchLike={branchLike}
component={component}
issue={this.props.issue}
link={false}
organization={this.props.organization}
/>
</div>

+ 2
- 34
server/sonar-web/src/main/js/apps/issues/components/__tests__/ComponentBreadcrumbs-test.tsx Ver fichero

@@ -34,12 +34,7 @@ const baseIssue = {
it('renders', () => {
expect(
shallow(
<ComponentBreadcrumbs
branchLike={undefined}
component={undefined}
issue={baseIssue}
organization={undefined}
/>
<ComponentBreadcrumbs component={undefined} issue={baseIssue} organization={undefined} />
)
).toMatchSnapshot();
});
@@ -47,33 +42,6 @@ it('renders', () => {
it('renders with sub-project', () => {
const issue = { ...baseIssue, subProject: 'sub-proj', subProjectName: 'sub-proj-name' };
expect(
shallow(
<ComponentBreadcrumbs
branchLike={undefined}
component={undefined}
issue={issue}
organization={undefined}
/>
)
).toMatchSnapshot();
});

it('renders with branch', () => {
const issue = { ...baseIssue, subProject: 'sub-proj', subProjectName: 'sub-proj-name' };
const shortBranch: T.ShortLivingBranch = {
isMain: false,
mergeBranch: '',
name: 'feature',
type: 'SHORT'
};
expect(
shallow(
<ComponentBreadcrumbs
branchLike={shortBranch}
component={undefined}
issue={issue}
organization={undefined}
/>
)
shallow(<ComponentBreadcrumbs component={undefined} issue={issue} organization={undefined} />)
).toMatchSnapshot();
});

+ 7
- 176
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/ComponentBreadcrumbs-test.tsx.snap Ver fichero

@@ -5,136 +5,18 @@ exports[`renders 1`] = `
className="component-name text-ellipsis"
>
<Connect(Organization)
link={true}
linkClassName="link-no-underline"
link={false}
organizationKey="org"
/>
<span
title="proj-name"
>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "proj",
},
}
}
>
proj-name
</Link>
proj-name
<span
className="slash-separator"
/>
</span>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/code",
"query": Object {
"id": "proj",
"line": undefined,
"selected": "comp",
},
}
}
>
<span
title="comp-name"
>
comp-name
</span>
</Link>
</div>
`;

exports[`renders with branch 1`] = `
<div
className="component-name text-ellipsis"
>
<Connect(Organization)
link={true}
linkClassName="link-no-underline"
organizationKey="org"
/>
<span
title="proj-name"
>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "proj",
"resolved": "false",
},
}
}
>
proj-name
</Link>
<span
className="slash-separator"
/>
</span>
<span
title="sub-proj-name"
>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "sub-proj",
"resolved": "false",
},
}
}
>
sub-proj-name
</Link>
<span
className="slash-separator"
/>
</span>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/code",
"query": Object {
"branch": "feature",
"id": "proj",
"line": undefined,
"selected": "comp",
},
}
}
>
<span
title="comp-name"
>
comp-name
</span>
</Link>
comp-name
</div>
`;

@@ -143,29 +25,13 @@ exports[`renders with sub-project 1`] = `
className="component-name text-ellipsis"
>
<Connect(Organization)
link={true}
linkClassName="link-no-underline"
link={false}
organizationKey="org"
/>
<span
title="proj-name"
>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "proj",
},
}
}
>
proj-name
</Link>
proj-name
<span
className="slash-separator"
/>
@@ -173,46 +39,11 @@ exports[`renders with sub-project 1`] = `
<span
title="sub-proj-name"
>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "sub-proj",
},
}
}
>
sub-proj-name
</Link>
sub-proj-name
<span
className="slash-separator"
/>
</span>
<Link
className="link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/code",
"query": Object {
"id": "proj",
"line": undefined,
"selected": "comp",
},
}
}
>
<span
title="comp-name"
>
comp-name
</span>
</Link>
comp-name
</div>
`;

+ 1
- 8
server/sonar-web/src/main/js/apps/overview/components/App.tsx Ver fichero

@@ -26,7 +26,6 @@ import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { isShortLivingBranch } from '../../../helpers/branches';
import {
getShortLivingBranchUrl,
getCodeUrl,
getProjectUrl,
getBaseUrl,
getPathUrlAsString
@@ -53,10 +52,6 @@ export class App extends React.PureComponent<Props> {
pathname: '/portfolio',
query: { id: component.key }
});
} else if (this.isFile()) {
this.props.router.replace(
getCodeUrl(component.breadcrumbs[0].key, branchLike, component.key)
);
} else if (isShortLivingBranch(branchLike)) {
this.props.router.replace(getShortLivingBranchUrl(component.key, branchLike.name));
}
@@ -64,12 +59,10 @@ export class App extends React.PureComponent<Props> {

isPortfolio = () => ['VW', 'SVW'].includes(this.props.component.qualifier);

isFile = () => ['FIL', 'UTS'].includes(this.props.component.qualifier);

render() {
const { branchLike, branchLikes, component } = this.props;

if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branchLike)) {
if (this.isPortfolio() || isShortLivingBranch(branchLike)) {
return null;
}


+ 2
- 28
server/sonar-web/src/main/js/apps/overview/components/__tests__/App-test.tsx Ver fichero

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import { shallow } from 'enzyme';
import { App } from '../App';
import { isSonarCloud } from '../../../../helpers/system';

@@ -64,33 +64,7 @@ it('should render SonarCloudEmptyOverview', () => {
).toBeTruthy();
});

it('redirects on Code page for files', () => {
const branch: T.LongLivingBranch = { isMain: false, name: 'b', type: 'LONG' };
const newComponent = {
...component,
breadcrumbs: [
{ key: 'project', name: 'Project', qualifier: 'TRK' },
{ key: 'foo', name: 'Foo', qualifier: 'DIR' }
],
qualifier: 'FIL'
};
const replace = jest.fn();
mount(
<App
branchLike={branch}
branchLikes={[branch]}
component={newComponent}
onComponentChange={jest.fn()}
router={{ replace }}
/>
);
expect(replace).toBeCalledWith({
pathname: '/code',
query: { branch: 'b', id: 'project', selected: 'foo' }
});
});

function getWrapper(props: Partial<App['props']> = {}) {
function getWrapper(props = {}) {
return shallow(
<App
branchLikes={[]}

+ 1
- 5
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx Ver fichero

@@ -103,11 +103,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State

{subProject != null && (
<div className="component-name-parent">
<a
className="link-with-icon"
href={getPathUrlAsString(getBranchLikeUrl(subProject, this.props.branchLike))}>
<QualifierIcon qualifier="BRC" /> <span>{subProjectName}</span>
</a>
<QualifierIcon qualifier="BRC" /> <span>{subProjectName}</span>
</div>
)}


+ 1
- 3
server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx Ver fichero

@@ -132,9 +132,7 @@ export default class DuplicationPopup extends React.PureComponent<Props> {
duplication.file.subProjectName && (
<div className="component-name-parent">
<QualifierIcon className="little-spacer-right" qualifier="BRC" />
<Link to={getProjectUrl(duplication.file.subProject)}>
{duplication.file.subProjectName}
</Link>
{duplication.file.subProjectName}
</div>
)}
</>

+ 2
- 4
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx Ver fichero

@@ -400,14 +400,12 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
{sourceViewerFile.subProject && (
<>
<QualifierIcon className="big-spacer-left little-spacer-right" qualifier="BRC" />
<Link to={getBranchLikeUrl(sourceViewerFile.subProject, branchLike)}>
{sourceViewerFile.subProjectName}
</Link>
{sourceViewerFile.subProjectName}
</>
)}
</div>

<div className="source-viewer-header-component-name">
<div className="source-viewer-header-component-name display-flex-center little-spacer-top">
<QualifierIcon className="little-spacer-right" qualifier={sourceViewerFile.q} />
{sourceViewerFile.path}
</div>

+ 6
- 51
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/MeasuresOverlay-test.tsx.snap Ver fichero

@@ -39,25 +39,10 @@ exports[`should render source file 1`] = `
className="big-spacer-left little-spacer-right"
qualifier="BRC"
/>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "sub-project-key",
"resolved": "false",
},
}
}
>
Sub-Project Name
</Link>
Sub-Project Name
</div>
<div
className="source-viewer-header-component-name"
className="source-viewer-header-component-name display-flex-center little-spacer-top"
>
<QualifierIcon
className="little-spacer-right"
@@ -414,25 +399,10 @@ exports[`should render source file 2`] = `
className="big-spacer-left little-spacer-right"
qualifier="BRC"
/>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "sub-project-key",
"resolved": "false",
},
}
}
>
Sub-Project Name
</Link>
Sub-Project Name
</div>
<div
className="source-viewer-header-component-name"
className="source-viewer-header-component-name display-flex-center little-spacer-top"
>
<QualifierIcon
className="little-spacer-right"
@@ -1395,25 +1365,10 @@ exports[`should render test file 1`] = `
className="big-spacer-left little-spacer-right"
qualifier="BRC"
/>
<Link
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/issues",
"query": Object {
"branch": "feature",
"id": "sub-project-key",
"resolved": "false",
},
}
}
>
Sub-Project Name
</Link>
Sub-Project Name
</div>
<div
className="source-viewer-header-component-name"
className="source-viewer-header-component-name display-flex-center little-spacer-top"
>
<QualifierIcon
className="little-spacer-right"

+ 1
- 2
server/sonar-web/src/main/js/components/SourceViewer/styles.css Ver fichero

@@ -290,12 +290,11 @@
}

.source-viewer-header-component-project {
color: var(--secondFontColor);
font-size: var(--smallFontSize);
}

.source-viewer-header-component-name {
font-weight: 600;
font-size: var(--smallFontSize);
}

.source-viewer-header-favorite {

+ 8
- 0
server/sonar-web/src/main/js/store/rootActions.ts Ver fichero

@@ -18,6 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { Dispatch } from 'redux';
import { InjectedRouter } from 'react-router';
import { requireAuthorization as requireAuthorizationAction } from './appState';
import { addGlobalErrorMessage } from './globalMessages';
import { receiveLanguages } from './languages';
import { receiveMetrics } from './metrics';
@@ -84,3 +86,9 @@ export function doLogout() {
}
);
}

export function requireAuthorization(router: Pick<InjectedRouter, 'replace'>) {
const returnTo = window.location.pathname + window.location.search + window.location.hash;
router.replace({ pathname: '/sessions/new', query: { return_to: returnTo } });
return requireAuthorizationAction();
}

+ 0
- 21
sonar-core/src/main/resources/org/sonar/l10n/core.properties Ver fichero

@@ -351,16 +351,9 @@ qualifier.UTS=Test File
qualifier.DEV=Developer

qualifier.configuration.TRK=Project Configuration
qualifier.configuration.BRC=Sub-project Configuration
qualifier.configuration.DIR=Directory Configuration
qualifier.configuration.PAC=Package Configuration
qualifier.configuration.VW=Portfolio Configuration
qualifier.configuration.SVW=Portfolio Configuration
qualifier.configuration.APP=Application Configuration
qualifier.configuration.FIL=File Configuration
qualifier.configuration.CLA=File Configuration
qualifier.configuration.UTS=Test File Configuration
qualifier.configuration.DEV=Developer Configuration

qualifiers.TRK=Projects
qualifiers.BRC=Sub-projects
@@ -2430,17 +2423,11 @@ overview.on_new_code=On New Code
overview.about_this_portfolio=About This Portfolio
overview.about_this_project.APP=About This Application
overview.about_this_project.TRK=About This Project
overview.about_this_project.BRC=About This Sub-Project
overview.about_this_project.DIR=About This Directory
overview.project_activity.APP=Application Activity
overview.project_activity.TRK=Project Activity
overview.project_activity.BRC=Sub-Project Activity
overview.project_activity.DIR=Directory Activity
overview.external_links=External Links
overview.project_key.APP=Application Key
overview.project_key.TRK=Project Key
overview.project_key.BRC=Sub-Project Key
overview.project_key.DIR=Directory Key

overview.project.no_lines_of_code=This project as no lines of code.
overview.project.empty=This project is empty.
@@ -3100,25 +3087,17 @@ homepage.check=Check to make the current page your homepage
#
#------------------------------------------------------------------------------
favorite.check.TRK=Click to mark this project as favorite.
favorite.check.BRC=Click to mark this sub-project as favorite.
favorite.check.DIR=Click to mark this directory as favorite.
favorite.check.PAC=Click to mark this package as favorite.
favorite.check.VW=Click to mark this portfolio as favorite.
favorite.check.SVW=Click to mark this sub-ortfolio as favorite.
favorite.check.APP=Click to mark this application as favorite.
favorite.check.FIL=Click to mark this file as favorite.
favorite.check.CLA=Click to mark this file as favorite.
favorite.check.UTS=Click to mark this test file as favorite.

favorite.current.TRK=This project is marked as favorite.
favorite.current.BRC=This sub-project is marked as favorite.
favorite.current.DIR=This directory is marked as favorite.
favorite.current.PAC=This package is marked as favorite.
favorite.current.VW=This portfolio is marked as favorite.
favorite.current.SVW=This sub-ortfolio is marked as favorite.
favorite.current.APP=This application is marked as favorite.
favorite.current.FIL=This file is marked as favorite.
favorite.current.CLA=This file is marked as favorite.
favorite.current.UTS=This test file is marked as favorite.



Cargando…
Cancelar
Guardar