Explorar el Código

SONAR-13342 Fix faulty links

tags/8.4.0.35506
Jeremy Davis hace 4 años
padre
commit
d2dc804652
Se han modificado 23 ficheros con 192 adiciones y 97 borrados
  1. 14
    4
      server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
  2. 19
    19
      server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
  3. 2
    2
      server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx
  4. 7
    7
      server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
  5. 1
    2
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Breadcrumb-test.tsx.snap
  6. 22
    11
      server/sonar-web/src/main/js/app/components/search/Search.tsx
  7. 2
    2
      server/sonar-web/src/main/js/app/components/search/SearchResult.tsx
  8. 44
    9
      server/sonar-web/src/main/js/app/components/search/__tests__/Search-test.tsx
  9. 0
    8
      server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap
  10. 2
    1
      server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
  11. 2
    2
      server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
  12. 3
    4
      server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx
  13. 1
    0
      server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap
  14. 2
    4
      server/sonar-web/src/main/js/apps/overview/components/App.tsx
  15. 7
    3
      server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx
  16. 1
    4
      server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap
  17. 4
    9
      server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
  18. 0
    2
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
  19. 2
    3
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
  20. 1
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap
  21. 29
    0
      server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
  22. 16
    1
      server/sonar-web/src/main/js/helpers/urls.ts
  23. 11
    0
      server/sonar-web/src/main/js/types/component.ts

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

isMainBranch, isMainBranch,
isPullRequest isPullRequest
} from '../../helpers/branch-like'; } from '../../helpers/branch-like';
import { isSonarCloud } from '../../helpers/system';
import { getPortfolioUrl } from '../../helpers/urls';
import { import {
fetchOrganization, fetchOrganization,
registerBranchStatus, registerBranchStatus,
requireAuthorization requireAuthorization
} from '../../store/rootActions'; } from '../../store/rootActions';
import { BranchLike } from '../../types/branch-like'; import { BranchLike } from '../../types/branch-like';
import { isPortfolioLike } from '../../types/component';
import ComponentContainerNotFound from './ComponentContainerNotFound'; import ComponentContainerNotFound from './ComponentContainerNotFound';
import { ComponentContext } from './ComponentContext'; import { ComponentContext } from './ComponentContext';
import ComponentNav from './nav/component/ComponentNav'; import ComponentNav from './nav/component/ComponentNav';
interface Props { interface Props {
children: React.ReactElement; children: React.ReactElement;
fetchOrganization: (organization: string) => void; fetchOrganization: (organization: string) => void;
location: Pick<Location, 'query'>;
location: Pick<Location, 'query' | 'pathname'>;
registerBranchStatus: (branchLike: BranchLike, component: string, status: T.Status) => void; registerBranchStatus: (branchLike: BranchLike, component: string, status: T.Status) => void;
requireAuthorization: (router: Pick<Router, 'replace'>) => void; requireAuthorization: (router: Pick<Router, 'replace'>) => void;
router: Pick<Router, 'replace'>; router: Pick<Router, 'replace'>;
.then(([nav, { component }]) => { .then(([nav, { component }]) => {
const componentWithQualifier = this.addQualifier({ ...nav, ...component }); const componentWithQualifier = this.addQualifier({ ...nav, ...component });


if (isSonarCloud()) {
this.props.fetchOrganization(componentWithQualifier.organization);
/*
* There used to be a redirect from /dashboard to /portfolio which caused issues.
* Links should be fixed to not rely on this redirect, but:
* This is a fail-safe in case there are still some faulty links remaining.
*/
if (
this.props.location.pathname.match('dashboard') &&
isPortfolioLike(componentWithQualifier.qualifier)
) {
this.props.router.replace(getPortfolioUrl(component.key));
} }

return componentWithQualifier; return componentWithQualifier;
}, onError) }, onError)
.then(this.fetchBranches) .then(this.fetchBranches)

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

import { getComponentNavigation } from '../../../api/nav'; import { getComponentNavigation } from '../../../api/nav';
import { STATUSES } from '../../../apps/background-tasks/constants'; import { STATUSES } from '../../../apps/background-tasks/constants';
import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; import { mockBranch, mockMainBranch, mockPullRequest } from '../../../helpers/mocks/branch-like';
import { isSonarCloud } from '../../../helpers/system';
import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks'; import { mockComponent, mockLocation, mockRouter } from '../../../helpers/testMocks';
import { ComponentQualifier } from '../../../types/component';
import { ComponentContainer } from '../ComponentContainer'; import { ComponentContainer } from '../ComponentContainer';


jest.mock('../../../api/branches', () => { jest.mock('../../../api/branches', () => {
}) })
})); }));


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

// mock this, because some of its children are using redux store // mock this, because some of its children are using redux store
jest.mock('../nav/component/ComponentNav', () => ({ jest.mock('../nav/component/ComponentNav', () => ({
default: () => null default: () => null
expect(registerBranchStatus).toBeCalledTimes(2); expect(registerBranchStatus).toBeCalledTimes(2);
}); });


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

const fetchOrganization = jest.fn();
shallowRender({ fetchOrganization });
await new Promise(setImmediate);
expect(fetchOrganization).toBeCalledWith('org');
});

it('fetches status', async () => { it('fetches status', async () => {
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({ (getComponentData as jest.Mock<any>).mockResolvedValueOnce({
component: { organization: 'org' } component: { organization: 'org' }
}); });


it('should show component not found if it does not exist', async () => { it('should show component not found if it does not exist', async () => {
(getComponentNavigation as jest.Mock).mockRejectedValue({ status: 404 });
(getComponentNavigation as jest.Mock).mockRejectedValueOnce({ status: 404 });
const wrapper = shallowRender(); const wrapper = shallowRender();
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });


it('should redirect if the user has no access', async () => { it('should redirect if the user has no access', async () => {
(getComponentNavigation as jest.Mock).mockRejectedValue({ status: 403 });
(getComponentNavigation as jest.Mock).mockRejectedValueOnce({ status: 403 });
const requireAuthorization = jest.fn(); const requireAuthorization = jest.fn();
const wrapper = shallowRender({ requireAuthorization }); const wrapper = shallowRender({ requireAuthorization });
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);
expect(requireAuthorization).toBeCalled(); expect(requireAuthorization).toBeCalled();
}); });


it('should redirect if the component is a portfolio', async () => {
const componentKey = 'comp-key';
(getComponentData as jest.Mock<any>).mockResolvedValueOnce({
component: { key: componentKey, breadcrumbs: [{ qualifier: ComponentQualifier.Portfolio }] }
});

const replace = jest.fn();

const wrapper = shallowRender({
location: mockLocation({ pathname: '/dashboard' }),
router: mockRouter({ replace })
});
await waitAndUpdate(wrapper);
expect(replace).toBeCalledWith({ pathname: '/portfolio', query: { id: componentKey } });
});

function shallowRender(props: Partial<ComponentContainer['props']> = {}) { function shallowRender(props: Partial<ComponentContainer['props']> = {}) {
return shallow<ComponentContainer>( return shallow<ComponentContainer>(
<ComponentContainer <ComponentContainer

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

import { Link } from 'react-router'; import { Link } from 'react-router';
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import { isMainBranch } from '../../../../helpers/branch-like'; import { isMainBranch } from '../../../../helpers/branch-like';
import { getProjectUrl } from '../../../../helpers/urls';
import { getComponentOverviewUrl } from '../../../../helpers/urls';
import { BranchLike } from '../../../../types/branch-like'; import { BranchLike } from '../../../../types/branch-like';


interface Props { interface Props {
<Link <Link
className="link-no-underline text-ellipsis" className="link-no-underline text-ellipsis"
title={breadcrumbElement.name} title={breadcrumbElement.name}
to={getProjectUrl(breadcrumbElement.key)}>
to={getComponentOverviewUrl(breadcrumbElement.key, breadcrumbElement.qualifier)}>
{breadcrumbElement.name} {breadcrumbElement.name}
</Link> </Link>
) : ( ) : (

+ 7
- 7
server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx Ver fichero

import { withAppState } from '../../../../components/hoc/withAppState'; import { withAppState } from '../../../../components/hoc/withAppState';
import { getBranchLikeQuery, isMainBranch, isPullRequest } from '../../../../helpers/branch-like'; import { getBranchLikeQuery, isMainBranch, isPullRequest } from '../../../../helpers/branch-like';
import { isSonarCloud } from '../../../../helpers/system'; import { isSonarCloud } from '../../../../helpers/system';
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
import { BranchLike, BranchParameters } from '../../../../types/branch-like'; import { BranchLike, BranchParameters } from '../../../../types/branch-like';
import { ComponentQualifier } from '../../../../types/component';
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component';
import './Menu.css'; import './Menu.css';


const SETTINGS_URLS = [ const SETTINGS_URLS = [


isPortfolio = () => { isPortfolio = () => {
const { qualifier } = this.props.component; const { qualifier } = this.props.component;
return (
qualifier === ComponentQualifier.Portfolio || qualifier === ComponentQualifier.SubPortfolio
);
return isPortfolioLike(qualifier);
}; };


isApplication = () => { isApplication = () => {
return { id: this.props.component.key, ...getBranchLikeQuery(this.props.branchLike) }; return { id: this.props.component.key, ...getBranchLikeQuery(this.props.branchLike) };
}; };


renderDashboardLink = (query: Query, isPortfolio: boolean) => {
const pathname = isPortfolio ? '/portfolio' : '/dashboard';
renderDashboardLink = ({ id, ...branchLike }: Query, isPortfolio: boolean) => {
return ( return (
<li> <li>
<Link activeClassName="active" to={{ pathname, query }}>
<Link
activeClassName="active"
to={isPortfolio ? getPortfolioUrl(id) : getProjectQueryUrl(id, branchLike)}>
{translate('overview.page')} {translate('overview.page')}
</Link> </Link>
</li> </li>

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

title="parent-portfolio" title="parent-portfolio"
to={ to={
Object { Object {
"pathname": "/dashboard",
"pathname": "/portfolio",
"query": Object { "query": Object {
"branch": undefined,
"id": "parent-portfolio", "id": "parent-portfolio",
}, },
} }

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

import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling'; import { scrollToElement } from 'sonar-ui-common/helpers/scrolling';
import { getSuggestions } from '../../../api/components'; import { getSuggestions } from '../../../api/components';
import { getCodeUrl, getProjectUrl } from '../../../helpers/urls';
import { getCodeUrl, getComponentOverviewUrl } from '../../../helpers/urls';
import { ComponentQualifier } from '../../../types/component';
import RecentHistory from '../RecentHistory'; import RecentHistory from '../RecentHistory';
import './Search.css'; import './Search.css';
import { ComponentResult, More, Results, sortQualifiers } from './utils'; import { ComponentResult, More, Results, sortQualifiers } from './utils';
}; };


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


if (selected) {
if (selected.startsWith('qualifier###')) {
this.searchMore(selected.substr(12));
if (!selected) {
return;
}

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

if ((results[ComponentQualifier.Portfolio] ?? []).find(r => r.key === selected)) {
qualifier = ComponentQualifier.Portfolio;
} else if ((results[ComponentQualifier.SubPortfolio] ?? []).find(r => r.key === selected)) {
qualifier = ComponentQualifier.SubPortfolio;
} }
this.closeSearch();

this.props.router.push(getComponentOverviewUrl(selected, qualifier));
} }
this.closeSearch();
} }
}; };



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

import ClockIcon from 'sonar-ui-common/components/icons/ClockIcon'; import ClockIcon from 'sonar-ui-common/components/icons/ClockIcon';
import FavoriteIcon from 'sonar-ui-common/components/icons/FavoriteIcon'; import FavoriteIcon from 'sonar-ui-common/components/icons/FavoriteIcon';
import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import { getCodeUrl, getProjectUrl } from '../../../helpers/urls';
import { getCodeUrl, getComponentOverviewUrl } from '../../../helpers/urls';
import { ComponentResult } from './utils'; import { ComponentResult } from './utils';


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


return ( return (
<li <li

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

import { shallow, ShallowWrapper } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { elementKeydown } from 'sonar-ui-common/helpers/testUtils'; import { elementKeydown } from 'sonar-ui-common/helpers/testUtils';
import { mockRouter } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
import { Search } from '../Search'; import { Search } from '../Search';


it('selects results', () => { it('selects results', () => {
open: true, open: true,
results: { results: {
TRK: [component('foo'), component('bar')], TRK: [component('foo'), component('bar')],
BRC: [component('qwe', 'BRC')]
BRC: [component('qwe', ComponentQualifier.SubProject)]
}, },
selected: 'foo' selected: 'foo'
}); });
prev(form, 'foo'); prev(form, 'foo');
}); });


it('opens selected on enter', () => {
const form = shallowRender();
it('opens selected project on enter', () => {
const router = mockRouter();
const form = shallowRender({ router });
const selectedKey = 'project';
form.setState({ form.setState({
open: true, open: true,
results: { TRK: [component('foo')] },
selected: 'foo'
results: { [ComponentQualifier.Project]: [component(selectedKey)] },
selected: selectedKey
});

elementKeydown(form.find('SearchBox'), 13);
expect(router.push).toBeCalledWith({ pathname: '/dashboard', query: { id: selectedKey } });
});

it('opens selected portfolio on enter', () => {
const router = mockRouter();
const form = shallowRender({ router });
const selectedKey = 'portfolio';
form.setState({
open: true,
results: {
[ComponentQualifier.Portfolio]: [component(selectedKey, ComponentQualifier.Portfolio)]
},
selected: selectedKey
}); });
const openSelected = jest.fn();
(form.instance() as Search).openSelected = openSelected;

elementKeydown(form.find('SearchBox'), 13);
expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
});

it('opens selected subportfolio on enter', () => {
const router = mockRouter();
const form = shallowRender({ router });
const selectedKey = 'sbprtfl';
form.setState({
open: true,
results: {
[ComponentQualifier.SubPortfolio]: [component(selectedKey, ComponentQualifier.SubPortfolio)]
},
selected: selectedKey
});

elementKeydown(form.find('SearchBox'), 13); elementKeydown(form.find('SearchBox'), 13);
expect(openSelected).toBeCalled();
expect(router.push).toBeCalledWith({ pathname: '/portfolio', query: { id: selectedKey } });
}); });


it('shows warning about short input', () => { it('shows warning about short input', () => {
); );
} }


function component(key: string, qualifier = 'TRK') {
function component(key: string, qualifier = ComponentQualifier.Project) {
return { key, name: key, qualifier }; return { key, name: key, qualifier };
} }



+ 0
- 8
server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap Ver fichero

Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "qwe", "id": "qwe",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }

+ 2
- 1
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx Ver fichero

import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import MetaLink from '../../../app/components/nav/component/projectInformation/meta/MetaLink'; import MetaLink from '../../../app/components/nav/component/projectInformation/meta/MetaLink';
import { orderLinks } from '../../../helpers/projectLinks'; import { orderLinks } from '../../../helpers/projectLinks';
import { getProjectUrl } from '../../../helpers/urls';


interface Props { interface Props {
project: T.MyProject; project: T.MyProject;
</aside> </aside>


<h3 className="account-project-name"> <h3 className="account-project-name">
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
<Link to={getProjectUrl(project.key)}>{project.name}</Link>
</h3> </h3>


{orderedLinks.length > 0 && ( {orderedLinks.length > 0 && (

+ 2
- 2
server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx Ver fichero

getProjectUrl, getProjectUrl,
getPullRequestUrl getPullRequestUrl
} from '../../../helpers/urls'; } from '../../../helpers/urls';
import { ComponentQualifier } from '../../../types/component';
import { isPortfolioLike } from '../../../types/component';
import TaskType from './TaskType'; import TaskType from './TaskType';


interface Props { interface Props {
} }


function getTaskComponentUrl(componentKey: string, task: T.Task) { function getTaskComponentUrl(componentKey: string, task: T.Task) {
if (task.componentQualifier === ComponentQualifier.Portfolio) {
if (isPortfolioLike(task.componentQualifier)) {
return getPortfolioUrl(componentKey); return getPortfolioUrl(componentKey);
} else if (task.branch) { } else if (task.branch) {
return getBranchUrl(componentKey, task.branch); return getBranchUrl(componentKey, task.branch);

+ 3
- 4
server/sonar-web/src/main/js/apps/code/components/ComponentName.tsx Ver fichero

import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { colors } from '../../../app/theme'; import { colors } from '../../../app/theme';
import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { getProjectUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like'; import { BranchLike } from '../../../types/branch-like';


export function getTooltip(component: T.ComponentMeasure) { export function getTooltip(component: T.ComponentMeasure) {
let inner = null; let inner = null;


if (component.refKey && component.qualifier !== 'SVW') { if (component.refKey && component.qualifier !== 'SVW') {
const branch = rootComponent.qualifier === 'APP' ? { branch: component.branch } : {};
const branch = rootComponent.qualifier === 'APP' ? component.branch : undefined;
inner = ( inner = (
<Link
className="link-with-icon"
to={{ pathname: '/dashboard', query: { id: component.refKey, ...branch } }}>
<Link className="link-with-icon" to={getProjectUrl(component.refKey, branch)}>
<QualifierIcon qualifier={component.qualifier} /> <span>{name}</span> <QualifierIcon qualifier={component.qualifier} /> <span>{name}</span>
</Link> </Link>
); );

+ 1
- 0
server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/ComponentName-test.tsx.snap Ver fichero

Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "src/main/ts/app", "id": "src/main/ts/app",
}, },
} }

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

import { Router, withRouter } from '../../../components/hoc/withRouter'; import { Router, withRouter } from '../../../components/hoc/withRouter';
import { isPullRequest } from '../../../helpers/branch-like'; import { isPullRequest } from '../../../helpers/branch-like';
import { BranchLike } from '../../../types/branch-like'; import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { isPortfolioLike } from '../../../types/component';
import BranchOverview from '../branches/BranchOverview'; import BranchOverview from '../branches/BranchOverview';


const EmptyOverview = lazyLoadComponent(() => import('./EmptyOverview')); const EmptyOverview = lazyLoadComponent(() => import('./EmptyOverview'));


export class App extends React.PureComponent<Props> { export class App extends React.PureComponent<Props> {
isPortfolio = () => { isPortfolio = () => {
return ([ComponentQualifier.Portfolio, ComponentQualifier.SubPortfolio] as string[]).includes(
this.props.component.qualifier
);
return isPortfolioLike(this.props.component.qualifier);
}; };


render() { render() {

+ 7
- 3
server/sonar-web/src/main/js/apps/portfolio/components/WorstProjects.tsx Ver fichero

import { formatMeasure } from 'sonar-ui-common/helpers/measures'; import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import { colors } from '../../../app/theme'; import { colors } from '../../../app/theme';
import Measure from '../../../components/measure/Measure'; import Measure from '../../../components/measure/Measure';
import { getProjectUrl } from '../../../helpers/urls';
import { getComponentOverviewUrl } from '../../../helpers/urls';
import { ComponentQualifier } from '../../../types/component';
import { SubComponent } from '../types'; import { SubComponent } from '../types';


interface Props { interface Props {
<td> <td>
<Link <Link
className="link-with-icon" className="link-with-icon"
to={getProjectUrl(component.refKey || component.key)}>
to={getComponentOverviewUrl(
component.refKey || component.key,
component.qualifier
)}>
<QualifierIcon qualifier={component.qualifier} /> {component.name} <QualifierIcon qualifier={component.qualifier} /> {component.name}
</Link> </Link>
</td> </td>
{component.qualifier === 'TRK'
{component.qualifier === ComponentQualifier.Project
? renderCell(component.measures, 'alert_status', 'LEVEL') ? renderCell(component.measures, 'alert_status', 'LEVEL')
: renderCell(component.measures, 'releasability_rating', 'RATING')} : renderCell(component.measures, 'releasability_rating', 'RATING')}
{renderCell(component.measures, 'reliability_rating', 'RATING')} {renderCell(component.measures, 'reliability_rating', 'RATING')}

+ 1
- 4
server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/WorstProjects-test.tsx.snap Ver fichero

style={Object {}} style={Object {}}
to={ to={
Object { Object {
"pathname": "/dashboard",
"pathname": "/portfolio",
"query": Object { "query": Object {
"branch": undefined,
"id": "foo", "id": "foo",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "barbar", "id": "barbar",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "bazbaz", "id": "bazbaz",
}, },
} }

+ 4
- 9
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx Ver fichero

import DateTooltipFormatter from 'sonar-ui-common/components/intl/DateTooltipFormatter'; import DateTooltipFormatter from 'sonar-ui-common/components/intl/DateTooltipFormatter';
import { Project } from '../../api/components'; import { Project } from '../../api/components';
import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer'; import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer';
import { getPortfolioUrl, getProjectUrl } from '../../helpers/urls';
import { ComponentQualifier } from '../../types/component';
import { getComponentOverviewUrl } from '../../helpers/urls';
import './ProjectRow.css'; import './ProjectRow.css';
import ProjectRowActions from './ProjectRowActions'; import ProjectRowActions from './ProjectRowActions';


this.props.onProjectCheck(this.props.project, checked); this.props.onProjectCheck(this.props.project, checked);
}; };


getComponentUrl(project: Project) {
return project.qualifier === ComponentQualifier.Portfolio
? getPortfolioUrl(project.key)
: getProjectUrl(project.key);
}

render() { render() {
const { organization, project, selected } = this.props; const { organization, project, selected } = this.props;


</td> </td>


<td className="nowrap hide-overflow project-row-text-cell"> <td className="nowrap hide-overflow project-row-text-cell">
<Link className="link-with-icon" to={this.getComponentUrl(project)}>
<Link
className="link-with-icon"
to={getComponentOverviewUrl(project.key, project.qualifier)}>
<QualifierIcon className="little-spacer-right" qualifier={project.qualifier} /> <QualifierIcon className="little-spacer-right" qualifier={project.qualifier} />


<Tooltip overlay={project.name} placement="left"> <Tooltip overlay={project.name} placement="left">

+ 0
- 2
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap Ver fichero

Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "project", "id": "project",
}, },
} }
Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "project", "id": "project",
}, },
} }

+ 2
- 3
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx Ver fichero

import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon';
import { translate } from 'sonar-ui-common/helpers/l10n'; import { translate } from 'sonar-ui-common/helpers/l10n';
import { getProfileProjects } from '../../../api/quality-profiles'; import { getProfileProjects } from '../../../api/quality-profiles';
import { getProjectUrl } from '../../../helpers/urls';
import { Profile } from '../types'; import { Profile } from '../types';
import ChangeProjectsForm from './ChangeProjectsForm'; import ChangeProjectsForm from './ChangeProjectsForm';


<ul> <ul>
{projects.map(project => ( {projects.map(project => (
<li className="spacer-top js-profile-project" data-key={project.key} key={project.key}> <li className="spacer-top js-profile-project" data-key={project.key} key={project.key}>
<Link
className="link-with-icon"
to={{ pathname: '/dashboard', query: { id: project.key } }}>
<Link className="link-with-icon" to={getProjectUrl(project.key)}>
<QualifierIcon qualifier="TRK" /> <span>{project.name}</span> <QualifierIcon qualifier="TRK" /> <span>{project.name}</span>
</Link> </Link>
</li> </li>

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileProjects-test.tsx.snap Ver fichero

Object { Object {
"pathname": "/dashboard", "pathname": "/dashboard",
"query": Object { "query": Object {
"branch": undefined,
"id": "org.sonarsource.xml:xml", "id": "org.sonarsource.xml:xml",
}, },
} }

+ 29
- 0
server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts Ver fichero

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { ComponentQualifier } from '../../types/component';
import { import {
getComponentDrilldownUrl, getComponentDrilldownUrl,
getComponentIssuesUrl, getComponentIssuesUrl,
getComponentOverviewUrl,
getComponentSecurityHotspotsUrl, getComponentSecurityHotspotsUrl,
getQualityGatesUrl, getQualityGatesUrl,
getQualityGateUrl getQualityGateUrl
}); });
}); });


describe('getComponentOverviewUrl', () => {
it('should return a portfolio url for a portfolio', () => {
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Portfolio)).toEqual({
pathname: '/portfolio',
query: { id: SIMPLE_COMPONENT_KEY }
});
});
it('should return a portfolio url for a subportfolio', () => {
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.SubPortfolio)).toEqual({
pathname: '/portfolio',
query: { id: SIMPLE_COMPONENT_KEY }
});
});
it('should return a dashboard url for a project', () => {
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Project)).toEqual({
pathname: '/dashboard',
query: { id: SIMPLE_COMPONENT_KEY }
});
});
it('should return a dashboard url for an app', () => {
expect(getComponentOverviewUrl(SIMPLE_COMPONENT_KEY, ComponentQualifier.Application)).toEqual({
pathname: '/dashboard',
query: { id: SIMPLE_COMPONENT_KEY }
});
});
});

describe('#getComponentDrilldownUrl', () => { describe('#getComponentDrilldownUrl', () => {
it('should return component drilldown url', () => { it('should return component drilldown url', () => {
expect( expect(

+ 16
- 1
server/sonar-web/src/main/js/helpers/urls.ts Ver fichero

*/ */
import { getBaseUrl, Location } from 'sonar-ui-common/helpers/urls'; import { getBaseUrl, Location } from 'sonar-ui-common/helpers/urls';
import { getProfilePath } from '../apps/quality-profiles/utils'; import { getProfilePath } from '../apps/quality-profiles/utils';
import { BranchLike } from '../types/branch-like';
import { BranchLike, BranchParameters } from '../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../types/component';
import { GraphType } from '../types/project-activity'; import { GraphType } from '../types/project-activity';
import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like'; import { getBranchLikeQuery, isBranch, isMainBranch, isPullRequest } from './branch-like';


type Query = Location['query']; type Query = Location['query'];


export function getComponentOverviewUrl(
componentKey: string,
componentQualifier: ComponentQualifier | string,
branchParameters?: BranchParameters
) {
return isPortfolioLike(componentQualifier)
? getPortfolioUrl(componentKey)
: getProjectQueryUrl(componentKey, branchParameters);
}

export function getProjectUrl(project: string, branch?: string): Location { export function getProjectUrl(project: string, branch?: string): Location {
return { pathname: '/dashboard', query: { id: project, branch } }; return { pathname: '/dashboard', query: { id: project, branch } };
} }


export function getProjectQueryUrl(project: string, branchParameters?: BranchParameters): Location {
return { pathname: '/dashboard', query: { id: project, ...branchParameters } };
}

export function getPortfolioUrl(key: string): Location { export function getPortfolioUrl(key: string): Location {
return { pathname: '/portfolio', query: { id: key } }; return { pathname: '/portfolio', query: { id: key } };
} }

+ 11
- 0
server/sonar-web/src/main/js/types/component.ts Ver fichero

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */

export enum ComponentQualifier { export enum ComponentQualifier {
Application = 'APP', Application = 'APP',
Directory = 'DIR', Directory = 'DIR',
TestFile = 'UTS' TestFile = 'UTS'
} }


export function isPortfolioLike(componentQualifier?: string | ComponentQualifier) {
return Boolean(
componentQualifier &&
[
ComponentQualifier.Portfolio.toString(),
ComponentQualifier.SubPortfolio.toString()
].includes(componentQualifier)
);
}

export enum Visibility { export enum Visibility {
Public = 'public', Public = 'public',
Private = 'private' Private = 'private'

Cargando…
Cancelar
Guardar