Browse Source

SONAR-10188 Update project/organization header

tags/7.0-RC1
Stas Vilchik 6 years ago
parent
commit
7b757f9053
95 changed files with 1789 additions and 1723 deletions
  1. 11
    27
      server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx
  2. 11
    27
      server/sonar-web/src/main/js/app/components/SimpleContainer.tsx
  3. 13
    29
      server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.tsx
  4. 4
    4
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css
  5. 8
    12
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx
  6. 47
    45
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
  7. 12
    4
      server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
  8. 2
    2
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx
  9. 5
    4
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx
  10. 3
    1
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap
  11. 0
    98
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.tsx.snap
  12. 104
    0
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
  13. 18
    14
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap
  14. 6
    1
      server/sonar-web/src/main/js/app/styles/components/page.css
  15. 1
    1
      server/sonar-web/src/main/js/app/styles/init/base.css
  16. 1
    0
      server/sonar-web/src/main/js/app/styles/init/icons.css
  17. 3
    4
      server/sonar-web/src/main/js/app/styles/print.css
  18. 0
    5
      server/sonar-web/src/main/js/app/styles/style.css
  19. 17
    8
      server/sonar-web/src/main/js/app/types.ts
  20. 4
    0
      server/sonar-web/src/main/js/apps/about/components/AboutApp.js
  21. 1
    1
      server/sonar-web/src/main/js/apps/account/account.css
  22. 2
    2
      server/sonar-web/src/main/js/apps/account/components/Password.js
  23. 0
    4
      server/sonar-web/src/main/js/apps/account/components/Security.js
  24. 24
    22
      server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.js
  25. 1
    6
      server/sonar-web/src/main/js/apps/account/notifications/Notifications.js
  26. 26
    24
      server/sonar-web/src/main/js/apps/account/notifications/Projects.js
  27. 65
    61
      server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.js.snap
  28. 1
    4
      server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap
  29. 150
    138
      server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap
  30. 5
    0
      server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx
  31. 13
    20
      server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx
  32. 19
    17
      server/sonar-web/src/main/js/apps/account/profile/Profile.js
  33. 73
    69
      server/sonar-web/src/main/js/apps/account/templates/account-tokens.hbs
  34. 30
    28
      server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.js
  35. 3
    1
      server/sonar-web/src/main/js/apps/code/components/App.tsx
  36. 8
    6
      server/sonar-web/src/main/js/apps/code/components/Search.tsx
  37. 6
    0
      server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js
  38. 4
    0
      server/sonar-web/src/main/js/apps/component-measures/components/App.js
  39. 14
    12
      server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list.hbs
  40. 1
    1
      server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs
  41. 1
    1
      server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs
  42. 6
    0
      server/sonar-web/src/main/js/apps/issues/components/App.js
  43. 5
    1
      server/sonar-web/src/main/js/apps/issues/components/PageActions.js
  44. 1
    1
      server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx
  45. 1
    1
      server/sonar-web/src/main/js/apps/marketplace/Search.tsx
  46. 0
    1
      server/sonar-web/src/main/js/apps/marketplace/style.css
  47. 1
    1
      server/sonar-web/src/main/js/apps/metrics/templates/metrics-layout.hbs
  48. 16
    14
      server/sonar-web/src/main/js/apps/organizations/components/MembersList.js
  49. 78
    76
      server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js
  50. 39
    35
      server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap
  51. 325
    313
      server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap
  52. 26
    6
      server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx
  53. 2
    1
      server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx
  54. 1
    1
      server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
  55. 2
    2
      server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap
  56. 0
    8
      server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
  57. 6
    4
      server/sonar-web/src/main/js/apps/permission-templates/components/List.js
  58. 10
    8
      server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx
  59. 0
    8
      server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
  60. 1
    1
      server/sonar-web/src/main/js/apps/project-admin/key/Key.js
  61. 6
    4
      server/sonar-web/src/main/js/apps/project-admin/links/Table.js
  62. 0
    4
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
  63. 24
    20
      server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx
  64. 64
    60
      server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap
  65. 13
    11
      server/sonar-web/src/main/js/apps/projectQualityProfiles/Table.tsx
  66. 82
    78
      server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Table-test.tsx.snap
  67. 0
    10
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  68. 7
    7
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
  69. 1
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
  70. 1
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx
  71. 28
    26
      server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
  72. 62
    58
      server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
  73. 4
    0
      server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js
  74. 0
    11
      server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx
  75. 0
    11
      server/sonar-web/src/main/js/apps/settings/components/App.js
  76. 28
    26
      server/sonar-web/src/main/js/apps/users/UsersList.tsx
  77. 61
    57
      server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/UsersList.tsx.snap
  78. 21
    19
      server/sonar-web/src/main/js/apps/web-api/components/Action.tsx
  79. 43
    39
      server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap
  80. 1
    4
      server/sonar-web/src/main/js/apps/web-api/styles/web-api.css
  81. 1
    1
      server/sonar-web/src/main/js/components/SourceViewer/styles.css
  82. 3
    0
      server/sonar-web/src/main/js/components/common/BranchStatus.css
  83. 5
    5
      server/sonar-web/src/main/js/components/common/BranchStatus.tsx
  84. 30
    12
      server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap
  85. 2
    2
      server/sonar-web/src/main/js/components/controls/Favorite.tsx
  86. 5
    4
      server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx
  87. 0
    42
      server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js
  88. 13
    7
      server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx
  89. 1
    1
      server/sonar-web/src/main/js/components/controls/__tests__/Favorite-test.tsx
  90. 9
    3
      server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.tsx
  91. 1
    0
      server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Favorite-test.tsx.snap
  92. 4
    4
      server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.tsx.snap
  93. 3
    2
      server/sonar-web/src/main/js/components/nav/ContextNavBar.css
  94. 7
    7
      server/sonar-web/src/main/js/helpers/urls.ts
  95. 22
    3
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 11
- 27
server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx View File

import { Link } from 'react-router'; import { Link } from 'react-router';
import { translate } from '../../helpers/l10n'; import { translate } from '../../helpers/l10n';


export default class ComponentContainerNotFound extends React.PureComponent {
componentDidMount() {
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
}

componentWillUnmount() {
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
}

render() {
return (
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<h2 className="big-spacer-bottom">{translate('dashboard.project_not_found')}</h2>
<p className="spacer-bottom">{translate('dashboard.project_not_found.2')}</p>
<p>
<Link to="/">Go back to the homepage</Link>
</p>
</div>
export default function ComponentContainerNotFound() {
return (
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<h2 className="big-spacer-bottom">{translate('dashboard.project_not_found')}</h2>
<p className="spacer-bottom">{translate('dashboard.project_not_found.2')}</p>
<p>
<Link to="/">Go back to the homepage</Link>
</p>
</div> </div>
);
}
</div>
);
} }

+ 11
- 27
server/sonar-web/src/main/js/app/components/SimpleContainer.tsx View File

hideLoggedInInfo?: boolean; hideLoggedInInfo?: boolean;
} }


export default class SimpleContainer extends React.PureComponent<Props> {
componentDidMount() {
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
}
export default function SimpleContainer(props: Props) {
return (
<div className="global-container">
<div className="page-wrapper" id="container">
<NavBar className="navbar-global" height={theme.globalNavHeightRaw} />


componentWillUnmount() {
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
}

render() {
return (
<div className="global-container">
<div className="page-wrapper" id="container">
<NavBar className="navbar-global" height={theme.globalNavHeightRaw} />

<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
{this.props.children}
</div>
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
{props.children}
</div> </div>
</div> </div>
<GlobalFooterContainer hideLoggedInInfo={this.props.hideLoggedInInfo} />
</div> </div>
);
}
<GlobalFooterContainer hideLoggedInInfo={props.hideLoggedInInfo} />
</div>
);
} }

+ 13
- 29
server/sonar-web/src/main/js/app/components/extensions/ExtensionNotFound.tsx View File

import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';


export default class ExtensionNotFound extends React.PureComponent {
componentDidMount() {
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
}

componentWillUnmount() {
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
}

render() {
return (
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2>
<p className="spacer-bottom">
You may have mistyped the address or the page may have moved.
</p>
<p>
<Link to="/">Go back to the homepage</Link>
</p>
</div>
export default function ExtensionNotFound() {
return (
<div id="bd" className="page-wrapper-simple">
<div id="nonav" className="page-simple">
<h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2>
<p className="spacer-bottom">
You may have mistyped the address or the page may have moved.
</p>
<p>
<Link to="/">Go back to the homepage</Link>
</p>
</div> </div>
);
}
</div>
);
} }

+ 4
- 4
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.css View File

.navbar-context-branches { .navbar-context-branches {
display: inline-block;
vertical-align: top;
padding: var(--gridSize) 0;
display: inline-flex;
justify-content: center;
line-height: calc(2 * var(--gridSize));
margin-left: calc(2 * var(--gridSize)); margin-left: calc(2 * var(--gridSize));
line-height: 16px;
font-size: var(--baseFontSize);
} }


.navbar-context-meta-branch-menu-item { .navbar-context-meta-branch-menu-item {

+ 8
- 12
server/sonar-web/src/main/js/app/components/nav/component/ComponentNav.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import ComponentNavBranch from './ComponentNavBranch';
import ComponentNavBreadcrumbs from './ComponentNavBreadcrumbs';
import ComponentNavHeader from './ComponentNavHeader';
import ComponentNavMeta from './ComponentNavMeta'; import ComponentNavMeta from './ComponentNavMeta';
import ComponentNavMenu from './ComponentNavMenu'; import ComponentNavMenu from './ComponentNavMenu';
import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif'; import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif';
id="context-navigation" id="context-navigation"
height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw} height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw}
notif={notifComponent}> notif={notifComponent}>
<ComponentNavBreadcrumbs component={this.props.component} />
{this.props.currentBranch && (
<ComponentNavBranch
branches={this.props.branches}
component={this.props.component}
currentBranch={this.props.currentBranch}
// to close dropdown on any location change
location={this.props.location}
/>
)}
<ComponentNavHeader
branches={this.props.branches}
component={this.props.component}
currentBranch={this.props.currentBranch}
// to close dropdown on any location change
location={this.props.location}
/>
<ComponentNavMeta branch={this.props.currentBranch} component={this.props.component} /> <ComponentNavMeta branch={this.props.currentBranch} component={this.props.component} />
<ComponentNavMenu <ComponentNavMenu
branch={this.props.currentBranch} branch={this.props.currentBranch}

server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.tsx → server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx View File

import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { Component, Organization } from '../../../types';
import ComponentNavBranch from './ComponentNavBranch';
import { Component, Organization, Branch, Breadcrumb } from '../../../types';
import QualifierIcon from '../../../../components/shared/QualifierIcon'; import QualifierIcon from '../../../../components/shared/QualifierIcon';
import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer'; import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer';
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar'; import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
} }


interface OwnProps { interface OwnProps {
branches: Branch[];
component: Component; component: Component;
currentBranch?: Branch;
location?: any;
} }


interface Props extends StateProps, OwnProps {} interface Props extends StateProps, OwnProps {}


export function ComponentNavBreadcrumbs(props: Props) {
export function ComponentNavHeader(props: Props) {
const { component, organization, shouldOrganizationBeDisplayed } = props; const { component, organization, shouldOrganizationBeDisplayed } = props;
const { breadcrumbs } = component;

const lastItem = breadcrumbs[breadcrumbs.length - 1];

const items: JSX.Element[] = [];
breadcrumbs.forEach((item, index) => {
const isPath = item.qualifier === 'DIR';
const itemName = isPath ? collapsePath(item.name, 15) : limitComponentName(item.name);

if (index === 0) {
items.push(
<QualifierIcon
className="spacer-right"
key={`qualifier-${item.key}`}
qualifier={lastItem.qualifier}
/>
);
}

items.push(
<Link
className="link-base-color link-no-underline"
key={`name-${item.key}`}
title={item.name}
to={getProjectUrl(item.key)}>
{itemName}
</Link>
);

if (index < breadcrumbs.length - 1) {
items.push(<span className="slash-separator" key={`separator-${item.key}`} />);
}
});


return ( return (
<header className="navbar-context-header"> <header className="navbar-context-header">
title={component.name} title={component.name}
organization={organization && shouldOrganizationBeDisplayed ? organization : undefined} organization={organization && shouldOrganizationBeDisplayed ? organization : undefined}
/> />
{organization &&
shouldOrganizationBeDisplayed && <OrganizationAvatar organization={organization} />}
{organization && {organization &&
shouldOrganizationBeDisplayed && ( shouldOrganizationBeDisplayed && (
<OrganizationLink
organization={organization}
className="link-base-color link-no-underline spacer-left">
{organization.name}
</OrganizationLink>
<>
<OrganizationAvatar organization={organization} />
<OrganizationLink
organization={organization}
className="link-base-color link-no-underline spacer-left">
{organization.name}
</OrganizationLink>
<span className="slash-separator" />
</>
)} )}
{organization && shouldOrganizationBeDisplayed && <span className="slash-separator" />}
{items}
{renderBreadcrumbs(component.breadcrumbs)}
{component.visibility === 'private' && ( {component.visibility === 'private' && (
<PrivateBadge className="spacer-left" qualifier={component.qualifier} /> <PrivateBadge className="spacer-left" qualifier={component.qualifier} />
)} )}
{props.currentBranch && (
<ComponentNavBranch
branches={props.branches}
component={component}
currentBranch={props.currentBranch}
// to close dropdown on any location change
location={props.location}
/>
)}
</header> </header>
); );
} }


function renderBreadcrumbs(breadcrumbs: Breadcrumb[]) {
const lastItem = breadcrumbs[breadcrumbs.length - 1];
return breadcrumbs.map((item, index) => {
const isPath = item.qualifier === 'DIR';
const itemName = isPath ? collapsePath(item.name, 15) : limitComponentName(item.name);

return (
<React.Fragment key={item.key}>
{index === 0 && <QualifierIcon className="spacer-right" qualifier={lastItem.qualifier} />}
<Link
className="link-base-color link-no-underline"
title={item.name}
to={getProjectUrl(item.key)}>
{itemName}
</Link>
{index < breadcrumbs.length - 1 && <span className="slash-separator" />}
</React.Fragment>
);
});
}

const mapStateToProps = (state: any, ownProps: OwnProps): StateProps => ({ const mapStateToProps = (state: any, ownProps: OwnProps): StateProps => ({
organization: organization:
ownProps.component.organization && getOrganizationByKey(state, ownProps.component.organization), ownProps.component.organization && getOrganizationByKey(state, ownProps.component.organization),
shouldOrganizationBeDisplayed: areThereCustomOrganizations(state) shouldOrganizationBeDisplayed: areThereCustomOrganizations(state)
}); });


export default connect(mapStateToProps)(ComponentNavBreadcrumbs);
export default connect(mapStateToProps)(ComponentNavHeader);

+ 12
- 4
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx View File

*/ */
import * as React from 'react'; import * as React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Branch, Component, CurrentUser, isLoggedIn } from '../../../types';
import { Branch, Component, CurrentUser, isLoggedIn, HomePageType } from '../../../types';
import BranchStatus from '../../../../components/common/BranchStatus'; import BranchStatus from '../../../../components/common/BranchStatus';
import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter'; import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter';
import Favorite from '../../../../components/controls/Favorite'; import Favorite from '../../../../components/controls/Favorite';
{isLoggedIn(currentUser) && {isLoggedIn(currentUser) &&
mainBranch && ( mainBranch && (
<div className="navbar-context-meta-secondary"> <div className="navbar-context-meta-secondary">
<Favorite component={component.key} favorite={Boolean(component.isFavorite)} />
<Favorite
component={component.key}
favorite={Boolean(component.isFavorite)}
qualifier={component.qualifier}
/>
<HomePageSelect <HomePageSelect
className="spacer-left" className="spacer-left"
currentPage={{ type: 'project', key: component.key }}
currentPage={{ type: HomePageType.Project, parameter: component.key }}
/> />
</div> </div>
)} )}
{shortBranch && <BranchStatus branch={branch!} />}
{shortBranch && (
<div className="navbar-context-meta-secondary">
<BranchStatus branch={branch!} />
</div>
)}
</div> </div>
); );
} }

+ 2
- 2
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx View File

} }
})); }));


jest.mock('../ComponentNavBreadcrumbs', () => ({
jest.mock('../ComponentNavHeader', () => ({
// eslint-disable-next-line // eslint-disable-next-line
default: function ComponentNavBreadcrumbs() {
default: function ComponentNavHeader() {
return null; return null;
} }
})); }));

server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavBreadcrumbs-test.tsx → server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx View File

*/ */
import * as React from 'react'; import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { ComponentNavBreadcrumbs } from '../ComponentNavBreadcrumbs';
import { ComponentNavHeader } from '../ComponentNavHeader';
import { Visibility } from '../../../../types'; import { Visibility } from '../../../../types';


it('should not render breadcrumbs with one element', () => { it('should not render breadcrumbs with one element', () => {
visibility: 'public' visibility: 'public'
}; };
const result = shallow( const result = shallow(
<ComponentNavBreadcrumbs component={component} shouldOrganizationBeDisplayed={false} />
<ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} />
); );
expect(result).toMatchSnapshot(); expect(result).toMatchSnapshot();
}); });
projectVisibility: Visibility.Public projectVisibility: Visibility.Public
}; };
const result = shallow( const result = shallow(
<ComponentNavBreadcrumbs
<ComponentNavHeader
branches={[]}
component={component} component={component}
organization={organization} organization={organization}
shouldOrganizationBeDisplayed={true} shouldOrganizationBeDisplayed={true}
visibility: 'private' visibility: 'private'
}; };
const result = shallow( const result = shallow(
<ComponentNavBreadcrumbs component={component} shouldOrganizationBeDisplayed={false} />
<ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} />
); );
expect(result.find('PrivateBadge')).toHaveLength(1); expect(result.find('PrivateBadge')).toHaveLength(1);
}); });

+ 3
- 1
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNav-test.tsx.snap View File

/> />
} }
> >
<ComponentNavBreadcrumbs
<ComponentNavHeader
branches={Array []}
component={ component={
Object { Object {
"breadcrumbs": Array [ "breadcrumbs": Array [
"qualifier": "TRK", "qualifier": "TRK",
} }
} }
location={Object {}}
/> />
<ComponentNavMeta <ComponentNavMeta
component={ component={

+ 0
- 98
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should not render breadcrumbs with one element 1`] = `
<header
className="navbar-context-header"
>
<OrganizationHelmet
title="My Project"
/>
<QualifierIcon
className="spacer-right"
key="qualifier-my-project"
qualifier="TRK"
/>
<Link
className="link-base-color link-no-underline"
key="name-my-project"
onlyActiveOnIndex={false}
style={Object {}}
title="My Project"
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "my-project",
},
}
}
>
My Project
</Link>
</header>
`;

exports[`should render organization 1`] = `
<header
className="navbar-context-header"
>
<OrganizationHelmet
organization={
Object {
"key": "foo",
"name": "The Foo Organization",
"projectVisibility": "public",
}
}
title="My Project"
/>
<OrganizationAvatar
organization={
Object {
"key": "foo",
"name": "The Foo Organization",
"projectVisibility": "public",
}
}
/>
<OrganizationLink
className="link-base-color link-no-underline spacer-left"
organization={
Object {
"key": "foo",
"name": "The Foo Organization",
"projectVisibility": "public",
}
}
>
The Foo Organization
</OrganizationLink>
<span
className="slash-separator"
/>
<QualifierIcon
className="spacer-right"
key="qualifier-my-project"
qualifier="TRK"
/>
<Link
className="link-base-color link-no-underline"
key="name-my-project"
onlyActiveOnIndex={false}
style={Object {}}
title="My Project"
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "my-project",
},
}
}
>
My Project
</Link>
</header>
`;

+ 104
- 0
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should not render breadcrumbs with one element 1`] = `
<header
className="navbar-context-header"
>
<OrganizationHelmet
title="My Project"
/>
<React.Fragment
key="my-project"
>
<QualifierIcon
className="spacer-right"
qualifier="TRK"
/>
<Link
className="link-base-color link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
title="My Project"
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "my-project",
},
}
}
>
My Project
</Link>
</React.Fragment>
</header>
`;

exports[`should render organization 1`] = `
<header
className="navbar-context-header"
>
<OrganizationHelmet
organization={
Object {
"key": "foo",
"name": "The Foo Organization",
"projectVisibility": "public",
}
}
title="My Project"
/>
<React.Fragment>
<OrganizationAvatar
organization={
Object {
"key": "foo",
"name": "The Foo Organization",
"projectVisibility": "public",
}
}
/>
<OrganizationLink
className="link-base-color link-no-underline spacer-left"
organization={
Object {
"key": "foo",
"name": "The Foo Organization",
"projectVisibility": "public",
}
}
>
The Foo Organization
</OrganizationLink>
<span
className="slash-separator"
/>
</React.Fragment>
<React.Fragment
key="my-project"
>
<QualifierIcon
className="spacer-right"
qualifier="TRK"
/>
<Link
className="link-base-color link-no-underline"
onlyActiveOnIndex={false}
style={Object {}}
title="My Project"
to={
Object {
"pathname": "/dashboard",
"query": Object {
"branch": undefined,
"id": "my-project",
},
}
}
>
My Project
</Link>
</React.Fragment>
</header>
`;

+ 18
- 14
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMeta-test.tsx.snap View File

date="2017-01-02T00:00:00.000Z" date="2017-01-02T00:00:00.000Z"
/> />
</div> </div>
<BranchStatus
branch={
Object {
"isMain": false,
"mergeBranch": "master",
"name": "feature",
"status": Object {
"bugs": 0,
"codeSmells": 2,
"vulnerabilities": 3,
},
"type": "SHORT",
<div
className="navbar-context-meta-secondary"
>
<BranchStatus
branch={
Object {
"isMain": false,
"mergeBranch": "master",
"name": "feature",
"status": Object {
"bugs": 0,
"codeSmells": 2,
"vulnerabilities": 3,
},
"type": "SHORT",
}
} }
}
/>
/>
</div>
</div> </div>
`; `;

+ 6
- 1
server/sonar-web/src/main/js/app/styles/components/page.css View File

* 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.
*/ */
.white-page {
background-color: #fff !important;
}

.global-container { .global-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-left: calc(50vw - 370px + 10px) !important; padding-left: calc(50vw - 370px + 10px) !important;
} }


.page-footer-with-sidebar div {
.page-footer-with-sidebar div,
.page-footer-with-sidebar .page-footer-menu {
max-width: 980px; max-width: 980px;
} }



+ 1
- 1
server/sonar-web/src/main/js/app/styles/init/base.css View File



html, html,
body { body {
background-color: #fff;
background-color: var(--barBackgroundColor);
} }


body { body {

+ 1
- 0
server/sonar-web/src/main/js/app/styles/init/icons.css View File

stroke-width: 1.41421356; stroke-width: 1.41421356;
stroke-opacity: 1; stroke-opacity: 1;
fill-opacity: 0; fill-opacity: 0;
vector-effect: non-scaling-stroke;
transition: all 0.2s ease; transition: all 0.2s ease;
} }



+ 3
- 4
server/sonar-web/src/main/js/app/styles/print.css View File

display: none !important; display: none !important;
} }


.dashboard-page,
.dashboard-page body {
background-color: #fff;
html,
body {
background-color: #fff !important;
} }

.widget thead, .widget thead,
.widget tfoot { .widget tfoot {
display: table-row-group; display: table-row-group;

+ 0
- 5
server/sonar-web/src/main/js/app/styles/style.css View File

* 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.
*/ */
.dashboard-page,
.dashboard-page body {
background-color: var(--barBackgroundColor);
}

.tabs { .tabs {
height: 20px; height: 20px;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;

+ 17
- 8
server/sonar-web/src/main/js/app/types.ts View File

name: string; name: string;
} }


export interface Breadcrumb {
key: string;
name: string;
qualifier: string;
}

export interface Component { export interface Component {
analysisDate?: string; analysisDate?: string;
breadcrumbs: Array<{
key: string;
name: string;
qualifier: string;
}>;
breadcrumbs: Breadcrumb[];
configuration?: ComponentConfiguration; configuration?: ComponentConfiguration;
description?: string; description?: string;
extensions?: Extension[]; extensions?: Extension[];
showOnboardingTutorial?: boolean; showOnboardingTutorial?: boolean;
} }


export enum HomePageType {
Project = 'PROJECT',
Organization = 'ORGANIZATION',
MyProjects = 'MY_PROJECTS',
MyIssues = 'MY_ISSUES'
}

export interface HomePage { export interface HomePage {
key?: string;
type: string;
parameter?: string;
type: HomePageType;
} }


export function isSameHomePage(a: HomePage, b: HomePage) { export function isSameHomePage(a: HomePage, b: HomePage) {
return a.type === b.type && a.key === b.key;
return a.type === b.type && a.parameter === b.parameter;
} }


export interface LoggedInUser extends CurrentUser { export interface LoggedInUser extends CurrentUser {

+ 4
- 0
server/sonar-web/src/main/js/apps/about/components/AboutApp.js View File

window.location = 'https://about.sonarcloud.io'; window.location = 'https://about.sonarcloud.io';
} else { } else {
this.loadData(); this.loadData();
// $FlowFixMe
document.body.classList.add('white-page');
} }
} }


componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
// $FlowFixMe
document.body.classList.remove('white-page');
} }


loadProjects() { loadProjects() {

+ 1
- 1
server/sonar-web/src/main/js/apps/account/account.css View File

padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;
border-bottom: 1px solid var(--barBorderColor); border-bottom: 1px solid var(--barBorderColor);
background-color: var(--barBackgroundColor);
background-color: #fff;
} }


.account-nav { .account-nav {

+ 2
- 2
server/sonar-web/src/main/js/apps/account/components/Password.js View File

const { success, errors } = this.state; const { success, errors } = this.state;


return ( return (
<section>
<section className="boxed-group">
<h2 className="spacer-bottom">{translate('my_profile.password.title')}</h2> <h2 className="spacer-bottom">{translate('my_profile.password.title')}</h2>


<form onSubmit={this.handleChangePassword}>
<form className="boxed-group-inner" onSubmit={this.handleChangePassword}>
{success && ( {success && (
<div className="alert alert-success">{translate('my_profile.password.changed')}</div> <div className="alert alert-success">{translate('my_profile.password.changed')}</div>
)} )}

+ 0
- 4
server/sonar-web/src/main/js/apps/account/components/Security.js View File

return ( return (
<div className="account-body account-container"> <div className="account-body account-container">
<Helmet title={translate('my_account.security')} /> <Helmet title={translate('my_account.security')} />

<Tokens user={user} /> <Tokens user={user} />

{user.local && <hr className="account-separator" />}

{user.local && <Password user={user} />} {user.local && <Password user={user} />}
</div> </div>
); );

+ 24
- 22
server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.js View File



function GlobalNotifications(props /*: Props */) { function GlobalNotifications(props /*: Props */) {
return ( return (
<section>
<h2 className="spacer-bottom">{translate('my_profile.overall_notifications.title')}</h2>
<section className="boxed-group">
<h2>{translate('my_profile.overall_notifications.title')}</h2>


<table className="form">
<thead>
<tr>
<th />
{props.channels.map(channel => (
<th key={channel} className="text-center">
<h4>{translate('notification.channel', channel)}</h4>
</th>
))}
</tr>
</thead>
<div className="boxed-group-inner">
<table className="form">
<thead>
<tr>
<th />
{props.channels.map(channel => (
<th key={channel} className="text-center">
<h4>{translate('notification.channel', channel)}</h4>
</th>
))}
</tr>
</thead>


<NotificationsList
notifications={props.notifications}
channels={props.channels}
types={props.types}
checkboxId={(d, c) => `global-notification-${d}-${c}`}
onAdd={props.addNotification}
onRemove={props.removeNotification}
/>
</table>
<NotificationsList
notifications={props.notifications}
channels={props.channels}
types={props.types}
checkboxId={(d, c) => `global-notification-${d}-${c}`}
onAdd={props.addNotification}
onRemove={props.removeNotification}
/>
</table>
</div>
</section> </section>
); );
} }

+ 1
- 6
server/sonar-web/src/main/js/apps/account/notifications/Notifications.js View File

return ( return (
<div className="account-body account-container"> <div className="account-body account-container">
<Helmet title={translate('my_account.notifications')} /> <Helmet title={translate('my_account.notifications')} />

<p className="big-spacer-bottom">{translate('notification.dispatcher.information')}</p>

<p className="alert alert-info">{translate('notification.dispatcher.information')}</p>
<GlobalNotifications /> <GlobalNotifications />

<hr className="account-separator" />

<Projects /> <Projects />
</div> </div>
); );

+ 26
- 24
server/sonar-web/src/main/js/apps/account/notifications/Projects.js View File

const allProjects = [...this.props.projects, ...this.state.addedProjects]; const allProjects = [...this.props.projects, ...this.state.addedProjects];


return ( return (
<section>
<h2 className="spacer-bottom">{translate('my_profile.per_project_notifications.title')}</h2>

{allProjects.length === 0 && (
<div className="note">{translate('my_account.no_project_notifications')}</div>
)}

{allProjects.map(project => <ProjectNotifications key={project.key} project={project} />)}

<div className="spacer-top panel bg-muted">
<span className="text-middle spacer-right">
{translate('my_account.set_notifications_for')}:
</span>
<AsyncSelect
autoload={false}
cache={false}
name="new_project"
style={{ width: '300px' }}
loadOptions={this.loadOptions}
minimumInput={2}
optionRenderer={this.renderOption}
onChange={this.handleAddProject}
placeholder={translate('my_account.search_project')}
/>
<section className="boxed-group">
<h2>{translate('my_profile.per_project_notifications.title')}</h2>

<div className="boxed-group-inner">
{allProjects.length === 0 && (
<div className="note">{translate('my_account.no_project_notifications')}</div>
)}

{allProjects.map(project => <ProjectNotifications key={project.key} project={project} />)}

<div className="spacer-top panel bg-muted">
<span className="text-middle spacer-right">
{translate('my_account.set_notifications_for')}:
</span>
<AsyncSelect
autoload={false}
cache={false}
name="new_project"
style={{ width: '300px' }}
loadOptions={this.loadOptions}
minimumInput={2}
optionRenderer={this.renderOption}
onChange={this.handleAddProject}
placeholder={translate('my_account.search_project')}
/>
</div>
</div> </div>
</section> </section>
); );

+ 65
- 61
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/GlobalNotifications-test.js.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should match snapshot 1`] = ` exports[`should match snapshot 1`] = `
<section>
<h2
className="spacer-bottom"
>
<section
className="boxed-group"
>
<h2>
my_profile.overall_notifications.title my_profile.overall_notifications.title
</h2> </h2>
<table
className="form"
<div
className="boxed-group-inner"
> >
<thead>
<tr>
<th />
<th
className="text-center"
key="channel1"
>
<h4>
notification.channel.channel1
</h4>
</th>
<th
className="text-center"
key="channel2"
>
<h4>
notification.channel.channel2
</h4>
</th>
</tr>
</thead>
<NotificationsList
channels={
Array [
"channel1",
"channel2",
]
}
checkboxId={[Function]}
notifications={
Array [
Object {
"channel": "channel1",
"type": "type1",
},
Object {
"channel": "channel1",
"type": "type2",
},
Object {
"channel": "channel2",
"type": "type2",
},
]
}
onAdd={[Function]}
onRemove={[Function]}
types={
Array [
"type1",
"type2",
]
}
/>
</table>
<table
className="form"
>
<thead>
<tr>
<th />
<th
className="text-center"
key="channel1"
>
<h4>
notification.channel.channel1
</h4>
</th>
<th
className="text-center"
key="channel2"
>
<h4>
notification.channel.channel2
</h4>
</th>
</tr>
</thead>
<NotificationsList
channels={
Array [
"channel1",
"channel2",
]
}
checkboxId={[Function]}
notifications={
Array [
Object {
"channel": "channel1",
"type": "type1",
},
Object {
"channel": "channel1",
"type": "type2",
},
Object {
"channel": "channel2",
"type": "type2",
},
]
}
onAdd={[Function]}
onRemove={[Function]}
types={
Array [
"type1",
"type2",
]
}
/>
</table>
</div>
</section> </section>
`; `;

+ 1
- 4
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap View File

title="my_account.notifications" title="my_account.notifications"
/> />
<p <p
className="big-spacer-bottom"
className="alert alert-info"
> >
notification.dispatcher.information notification.dispatcher.information
</p> </p>
<Connect(GlobalNotifications) /> <Connect(GlobalNotifications) />
<hr
className="account-separator"
/>
<Connect(Projects) /> <Connect(Projects) />
</div> </div>
`; `;

+ 150
- 138
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Projects-test.js.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render projects 1`] = ` exports[`should render projects 1`] = `
<section>
<h2
className="spacer-bottom"
>
<section
className="boxed-group"
>
<h2>
my_profile.per_project_notifications.title my_profile.per_project_notifications.title
</h2> </h2>
<Connect(ProjectNotifications)
key="foo"
project={
Object {
"key": "foo",
"name": "Foo",
}
}
/>
<Connect(ProjectNotifications)
key="bar"
project={
Object {
"key": "bar",
"name": "Bar",
}
}
/>
<div <div
className="spacer-top panel bg-muted"
className="boxed-group-inner"
> >
<span
className="text-middle spacer-right"
>
my_account.set_notifications_for
:
</span>
<AsyncSelect
autoload={false}
cache={false}
loadOptions={[Function]}
minimumInput={2}
name="new_project"
onChange={[Function]}
optionRenderer={[Function]}
placeholder="my_account.search_project"
style={
<Connect(ProjectNotifications)
key="foo"
project={
Object {
"key": "foo",
"name": "Foo",
}
}
/>
<Connect(ProjectNotifications)
key="bar"
project={
Object { Object {
"width": "300px",
"key": "bar",
"name": "Bar",
} }
} }
/> />
<div
className="spacer-top panel bg-muted"
>
<span
className="text-middle spacer-right"
>
my_account.set_notifications_for
:
</span>
<AsyncSelect
autoload={false}
cache={false}
loadOptions={[Function]}
minimumInput={2}
name="new_project"
onChange={[Function]}
optionRenderer={[Function]}
placeholder="my_account.search_project"
style={
Object {
"width": "300px",
}
}
/>
</div>
</div> </div>
</section> </section>
`; `;


exports[`should render projects 2`] = ` exports[`should render projects 2`] = `
<section>
<h2
className="spacer-bottom"
>
<section
className="boxed-group"
>
<h2>
my_profile.per_project_notifications.title my_profile.per_project_notifications.title
</h2> </h2>
<Connect(ProjectNotifications)
key="foo"
project={
Object {
"key": "foo",
"name": "Foo",
}
}
/>
<Connect(ProjectNotifications)
key="bar"
project={
Object {
"key": "bar",
"name": "Bar",
}
}
/>
<Connect(ProjectNotifications)
key="qux"
project={
Object {
"key": "qux",
"name": "Qux",
}
}
/>
<div <div
className="spacer-top panel bg-muted"
className="boxed-group-inner"
> >
<span
className="text-middle spacer-right"
>
my_account.set_notifications_for
:
</span>
<AsyncSelect
autoload={false}
cache={false}
loadOptions={[Function]}
minimumInput={2}
name="new_project"
onChange={[Function]}
optionRenderer={[Function]}
placeholder="my_account.search_project"
style={
<Connect(ProjectNotifications)
key="foo"
project={
Object {
"key": "foo",
"name": "Foo",
}
}
/>
<Connect(ProjectNotifications)
key="bar"
project={
Object {
"key": "bar",
"name": "Bar",
}
}
/>
<Connect(ProjectNotifications)
key="qux"
project={
Object { Object {
"width": "300px",
"key": "qux",
"name": "Qux",
} }
} }
/> />
<div
className="spacer-top panel bg-muted"
>
<span
className="text-middle spacer-right"
>
my_account.set_notifications_for
:
</span>
<AsyncSelect
autoload={false}
cache={false}
loadOptions={[Function]}
minimumInput={2}
name="new_project"
onChange={[Function]}
optionRenderer={[Function]}
placeholder="my_account.search_project"
style={
Object {
"width": "300px",
}
}
/>
</div>
</div> </div>
</section> </section>
`; `;


exports[`should render projects 3`] = ` exports[`should render projects 3`] = `
<section>
<h2
className="spacer-bottom"
>
<section
className="boxed-group"
>
<h2>
my_profile.per_project_notifications.title my_profile.per_project_notifications.title
</h2> </h2>
<Connect(ProjectNotifications)
key="foo"
project={
Object {
"key": "foo",
"name": "Foo",
}
}
/>
<Connect(ProjectNotifications)
key="bar"
project={
Object {
"key": "bar",
"name": "Bar",
}
}
/>
<Connect(ProjectNotifications)
key="qux"
project={
Object {
"key": "qux",
"name": "Qux",
}
}
/>
<div <div
className="spacer-top panel bg-muted"
className="boxed-group-inner"
> >
<span
className="text-middle spacer-right"
>
my_account.set_notifications_for
:
</span>
<AsyncSelect
autoload={false}
cache={false}
loadOptions={[Function]}
minimumInput={2}
name="new_project"
onChange={[Function]}
optionRenderer={[Function]}
placeholder="my_account.search_project"
style={
<Connect(ProjectNotifications)
key="foo"
project={
Object { Object {
"width": "300px",
"key": "foo",
"name": "Foo",
} }
} }
/> />
<Connect(ProjectNotifications)
key="bar"
project={
Object {
"key": "bar",
"name": "Bar",
}
}
/>
<Connect(ProjectNotifications)
key="qux"
project={
Object {
"key": "qux",
"name": "Qux",
}
}
/>
<div
className="spacer-top panel bg-muted"
>
<span
className="text-middle spacer-right"
>
my_account.set_notifications_for
:
</span>
<AsyncSelect
autoload={false}
cache={false}
loadOptions={[Function]}
minimumInput={2}
name="new_project"
onChange={[Function]}
optionRenderer={[Function]}
placeholder="my_account.search_project"
style={
Object {
"width": "300px",
}
}
/>
</div>
</div> </div>
</section> </section>
`; `;

+ 5
- 0
server/sonar-web/src/main/js/apps/account/organizations/OrganizationsList.tsx View File

import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
import OrganizationCard from './OrganizationCard'; import OrganizationCard from './OrganizationCard';
import { Organization } from '../../../app/types'; import { Organization } from '../../../app/types';
import { translate } from '../../../helpers/l10n';


interface Props { interface Props {
organizations: Organization[]; organizations: Organization[];
} }


export default function OrganizationsList({ organizations }: Props) { export default function OrganizationsList({ organizations }: Props) {
if (organizations.length === 0) {
return <div>{translate('my_account.organizations.no_results')}</div>;
}

return ( return (
<ul className="account-projects-list"> <ul className="account-projects-list">
{sortBy(organizations, organization => organization.name.toLocaleLowerCase()).map( {sortBy(organizations, organization => organization.name.toLocaleLowerCase()).map(

+ 13
- 20
server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.tsx View File

<div className="account-body account-container"> <div className="account-body account-container">
<Helmet title={translate('my_account.organizations')} /> <Helmet title={translate('my_account.organizations')} />


<header className="page-header">
<h2 className="page-title">{translate('my_account.organizations')}</h2>
<div className="boxed-group">
{canCreateOrganizations && ( {canCreateOrganizations && (
<div className="page-actions">
<button onClick={this.handleCreateClick}>{translate('create')}</button>
<div className="clearfix">
<div className="boxed-group-actions">
<button onClick={this.handleCreateClick}>{translate('create')}</button>
</div>
</div> </div>
)} )}
{this.props.organizations.length > 0 ? (
<div className="page-description">
{translate('my_account.organizations.description')}
</div>
) : (
<div className="page-description">
{translate('my_account.organizations.no_results')}
</div>
)}
</header>

{this.state.loading ? (
<i className="spinner" />
) : (
<OrganizationsList organizations={this.props.organizations} />
)}
<div className="boxed-group-inner">
{this.state.loading ? (
<i className="spinner" />
) : (
<OrganizationsList organizations={this.props.organizations} />
)}
</div>
</div>


{this.state.createOrganization && ( {this.state.createOrganization && (
<CreateOrganizationForm <CreateOrganizationForm

+ 19
- 17
server/sonar-web/src/main/js/apps/account/profile/Profile.js View File



return ( return (
<div className="account-body account-container"> <div className="account-body account-container">
<div className="spacer-bottom">
{translate('login')}: <strong id="login">{user.login}</strong>
</div>
<div className="boxed-group boxed-group-inner">
<div className="spacer-bottom">
{translate('login')}: <strong id="login">{user.login}</strong>
</div>

{!user.local &&
user.externalProvider !== 'sonarqube' && (
<div id="identity-provider" className="spacer-bottom">
<UserExternalIdentity user={user} />
</div>
)}


{!user.local &&
user.externalProvider !== 'sonarqube' && (
<div id="identity-provider" className="spacer-bottom">
<UserExternalIdentity user={user} />
{!!user.email && (
<div className="spacer-bottom">
{translate('my_profile.email')}: <strong id="email">{user.email}</strong>
</div> </div>
)} )}


{!!user.email && (
<div className="spacer-bottom">
{translate('my_profile.email')}: <strong id="email">{user.email}</strong>
</div>
)}
{!customOrganizations && <hr className="account-separator" />}
{!customOrganizations && <UserGroups groups={user.groups} />}


{!customOrganizations && <hr className="account-separator" />}
{!customOrganizations && <UserGroups groups={user.groups} />}
<hr />


<hr className="account-separator" />

<UserScmAccounts user={user} scmAccounts={user.scmAccounts} />
<UserScmAccounts user={user} scmAccounts={user.scmAccounts} />
</div>
</div> </div>
); );
} }

+ 73
- 69
server/sonar-web/src/main/js/apps/account/templates/account-tokens.hbs View File

<h2 class="spacer-bottom">{{t 'users.tokens'}}</h2>
<div class="boxed-group">
<h2>{{t 'users.tokens'}}</h2>


<div class="big-spacer-bottom big-spacer-right markdown">
<p>{{t 'my_account.tokens_description'}}</p>
</div>
<div class="boxed-group-inner">
<div class="big-spacer-bottom big-spacer-right markdown">
<p>{{t 'my_account.tokens_description'}}</p>
</div>


{{#notNull tokens}}
<table class="data">
<thead>
<tr>
<th>{{t 'name'}}</th>
<th class="text-right">{{t 'created'}}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{{#each tokens}}
<tr>
<td>
<div title="{{name}}">
{{limitString name}}
</div>
</td>
<td class="thin nowrap text-right">
{{d createdAt}}
</td>
<td class="thin nowrap text-right">
<div class="big-spacer-left">
<form class="js-revoke-token-form" data-token="{{name}}">
{{#if deleting}}
<button class="button-red active input-small">{{t 'users.tokens.sure'}}</button>
{{else}}
<button class="button-red input-small">{{t 'users.tokens.revoke'}}</button>
{{/if}}
</form>
</div>
</td>
</tr>
{{else}}
{{#notNull tokens}}
<table class="data">
<thead>
<tr> <tr>
<td colspan="3">
<span class="note">{{t 'users.no_tokens'}}</span>
</td>
<th>{{t 'name'}}</th>
<th class="text-right">{{t 'created'}}</th>
<th>&nbsp;</th>
</tr> </tr>
{{/each}}
</tbody>
</table>
{{/notNull}}
</thead>
<tbody>
{{#each tokens}}
<tr>
<td>
<div title="{{name}}">
{{limitString name}}
</div>
</td>
<td class="thin nowrap text-right">
{{d createdAt}}
</td>
<td class="thin nowrap text-right">
<div class="big-spacer-left">
<form class="js-revoke-token-form" data-token="{{name}}">
{{#if deleting}}
<button class="button-red active input-small">{{t 'users.tokens.sure'}}</button>
{{else}}
<button class="button-red input-small">{{t 'users.tokens.revoke'}}</button>
{{/if}}
</form>
</div>
</td>
</tr>
{{else}}
<tr>
<td colspan="3">
<span class="note">{{t 'users.no_tokens'}}</span>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{/notNull}}


{{#each errors}}
<div class="alert alert-danger">{{msg}}</div>
{{/each}}
{{#each errors}}
<div class="alert alert-danger">{{msg}}</div>
{{/each}}


<form class="js-generate-token-form spacer-top panel bg-muted">
<label>{{t 'users.generate_new_token'}}:</label>
<input type="text" required maxlength="100" placeholder="{{t 'users.enter_token_name'}}">
<button>{{t 'users.generate'}}</button>
</form>
<form class="js-generate-token-form spacer-top panel bg-muted">
<label>{{t 'users.generate_new_token'}}:</label>
<input type="text" required maxlength="100" placeholder="{{t 'users.enter_token_name'}}">
<button>{{t 'users.generate'}}</button>
</form>


{{#if newToken}}
<div class="panel panel-white big-spacer-top">
<div class="alert alert-warning">
{{tp 'users.tokens.new_token_created' newToken.name}}
</div>
{{#if newToken}}
<div class="panel panel-white big-spacer-top">
<div class="alert alert-warning">
{{tp 'users.tokens.new_token_created' newToken.name}}
</div>


<table class="data">
<tr>
<td class="thin">
<button class="js-copy-to-clipboard" data-clipboard-text="{{newToken.token}}">{{t 'copy'}}</button>
</td>
<td class="nowrap">
<div class="monospaced text-success">{{newToken.token}}</div>
</td>
</tr>
</table>
</div>
{{/if}}
<table class="data">
<tr>
<td class="thin">
<button class="js-copy-to-clipboard" data-clipboard-text="{{newToken.token}}">{{t 'copy'}}</button>
</td>
<td class="nowrap">
<div class="monospaced text-success">{{newToken.token}}</div>
</td>
</tr>
</table>
</div>
{{/if}}
</div>
</div>

+ 30
- 28
server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.js View File

}); });


return ( return (
<table className={className}>
<thead>
<tr>
<th>{translate('background_tasks.table.status')}</th>
<th>{translate('background_tasks.table.task')}</th>
<th>{translate('background_tasks.table.id')}</th>
<th>&nbsp;</th>
<th className="text-right">{translate('background_tasks.table.submitted')}</th>
<th className="text-right">{translate('background_tasks.table.started')}</th>
<th className="text-right">{translate('background_tasks.table.finished')}</th>
<th className="text-right">{translate('background_tasks.table.duration')}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{tasks.map((task, index, tasks) => (
<Task
key={task.id}
task={task}
tasks={tasks}
component={component}
onCancelTask={onCancelTask}
onFilterTask={onFilterTask}
previousTask={index > 0 ? tasks[index - 1] : undefined}
/>
))}
</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table className={className}>
<thead>
<tr>
<th>{translate('background_tasks.table.status')}</th>
<th>{translate('background_tasks.table.task')}</th>
<th>{translate('background_tasks.table.id')}</th>
<th>&nbsp;</th>
<th className="text-right">{translate('background_tasks.table.submitted')}</th>
<th className="text-right">{translate('background_tasks.table.started')}</th>
<th className="text-right">{translate('background_tasks.table.finished')}</th>
<th className="text-right">{translate('background_tasks.table.duration')}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{tasks.map((task, index, tasks) => (
<Task
key={task.id}
task={task}
tasks={tasks}
component={component}
onCancelTask={onCancelTask}
onFilterTask={onFilterTask}
previousTask={index > 0 ? tasks[index - 1] : undefined}
/>
))}
</tbody>
</table>
</div>
); );
} }
} }

+ 3
- 1
server/sonar-web/src/main/js/apps/code/components/App.tsx View File



const shouldShowBreadcrumbs = breadcrumbs.length > 1; const shouldShowBreadcrumbs = breadcrumbs.length > 1;


const componentsClassName = classNames('spacer-top', { 'new-loading': loading });
const componentsClassName = classNames('boxed-group', 'boxed-group-inner', 'spacer-top', {
'new-loading': loading
});


return ( return (
<div className="page page-limited"> <div className="page page-limited">

+ 8
- 6
server/sonar-web/src/main/js/apps/code/components/Search.tsx View File

{loading && <i className="spinner spacer-left" />} {loading && <i className="spinner spacer-left" />}


{results != null && ( {results != null && (
<Components
branch={this.props.branch}
components={results}
rootComponent={component}
selected={selected}
/>
<div className="boxed-group boxed-group-inner spacer-top">
<Components
branch={this.props.branch}
components={results}
rootComponent={component}
selected={selected}
/>
</div>
)} )}
</div> </div>
); );

+ 6
- 0
server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js View File

*/ */


componentDidMount() { componentDidMount() {
// $FlowFixMe
document.body.classList.add('white-page');

if (this.props.appState.organizationsEnabled && !this.props.params.organizationKey) { if (this.props.appState.organizationsEnabled && !this.props.params.organizationKey) {
// redirect to organization-level rules page // redirect to organization-level rules page
this.props.router.replace( this.props.router.replace(
} }


componentWillUnmount() { componentWillUnmount() {
// $FlowFixMe
document.body.classList.remove('white-page');

if (this.stop) { if (this.stop) {
this.stop(); this.stop();
} }

+ 4
- 0
server/sonar-web/src/main/js/apps/component-measures/components/App.js View File



componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
// $FlowFixMe
document.body.classList.add('white-page');
this.props.fetchMetrics(); this.props.fetchMetrics();
this.fetchMeasures(this.props); this.fetchMeasures(this.props);
key.setScope('measures-files'); key.setScope('measures-files');


componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
// $FlowFixMe
document.body.classList.remove('white-page');
key.deleteScope('measures-files'); key.deleteScope('measures-files');
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
if (footer) { if (footer) {

+ 14
- 12
server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list.hbs View File

<table class="data zebra">
<thead>
<tr>
<th>{{t 'custom_measures.metric'}}</th>
<th>{{t 'value'}}</th>
<th>{{t 'description'}}</th>
<th>{{t 'date'}}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="boxed-group boxed-group-inner">
<table class="data zebra">
<thead>
<tr>
<th>{{t 'custom_measures.metric'}}</th>
<th>{{t 'value'}}</th>
<th>{{t 'description'}}</th>
<th>{{t 'date'}}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>

+ 1
- 1
server/sonar-web/src/main/js/apps/groups/templates/groups-list.hbs View File

<div>
<div class="boxed-group boxed-group-inner">
{{#isNull organization}} {{#isNull organization}}
<div class="panel panel-vertical js-anyone"> <div class="panel panel-vertical js-anyone">
<div class="display-inline-block text-top width-20"> <div class="display-inline-block text-top width-20">

+ 1
- 1
server/sonar-web/src/main/js/apps/groups/templates/groups-search.hbs View File

<div class="panel panel-vertical bordered-bottom spacer-bottom">
<div class="big-spacer-bottom">
<form id="groups-search-form" class="search-box"> <form id="groups-search-form" class="search-box">
<input id="groups-search-query" class="search-box-input" type="text" name="q" placeholder="{{t 'search.search_by_name'}}" maxlength="100"> <input id="groups-search-query" class="search-box-input" type="text" name="q" placeholder="{{t 'search.search_by_name'}}" maxlength="100">
<svg class="search-box-magnifier" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"> <svg class="search-box-magnifier" width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">

+ 6
- 0
server/sonar-web/src/main/js/apps/issues/components/App.js View File

return; return;
} }


// $FlowFixMe
document.body.classList.add('white-page');

const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
if (footer) { if (footer) {
footer.classList.add('page-footer-with-sidebar'); footer.classList.add('page-footer-with-sidebar');
componentWillUnmount() { componentWillUnmount() {
this.detachShortcuts(); this.detachShortcuts();


// $FlowFixMe
document.body.classList.remove('white-page');

const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
if (footer) { if (footer) {
footer.classList.remove('page-footer-with-sidebar'); footer.classList.remove('page-footer-with-sidebar');

+ 5
- 1
server/sonar-web/src/main/js/apps/issues/components/PageActions.js View File

import IssuesCounter from './IssuesCounter'; import IssuesCounter from './IssuesCounter';
import ReloadButton from './ReloadButton'; import ReloadButton from './ReloadButton';
/*:: import type { Paging } from '../utils'; */ /*:: import type { Paging } from '../utils'; */
import { HomePageType } from '../../../app/types';
import DeferredSpinner from '../../../components/common/DeferredSpinner'; import DeferredSpinner from '../../../components/common/DeferredSpinner';
import HomePageSelect from '../../../components/controls/HomePageSelect'; import HomePageSelect from '../../../components/controls/HomePageSelect';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
</div> </div>


{this.props.canSetHome && ( {this.props.canSetHome && (
<HomePageSelect className="huge-spacer-left" currentPage={{ type: 'my-issues' }} />
<HomePageSelect
className="huge-spacer-left"
currentPage={{ type: HomePageType.MyIssues }}
/>
)} )}
</div> </div>
); );

+ 1
- 1
server/sonar-web/src/main/js/apps/marketplace/PluginsList.tsx View File



render() { render() {
return ( return (
<div id="marketplace-plugins">
<div className="boxed-group boxed-group-inner" id="marketplace-plugins">
<ul> <ul>
{this.props.plugins.map(plugin => ( {this.props.plugins.map(plugin => (
<li key={plugin.key} className="panel panel-vertical"> <li key={plugin.key} className="panel panel-vertical">

+ 1
- 1
server/sonar-web/src/main/js/apps/marketplace/Search.tsx View File

} }
]; ];
return ( return (
<div id="marketplace-search" className="panel panel-vertical bordered-bottom spacer-bottom">
<div id="marketplace-search" className="big-spacer-bottom">
<div className="display-inline-block text-top nowrap abs-width-150 spacer-right"> <div className="display-inline-block text-top nowrap abs-width-150 spacer-right">
<RadioToggle <RadioToggle
name="marketplace-filter" name="marketplace-filter"

+ 0
- 1
server/sonar-web/src/main/js/apps/marketplace/style.css View File

display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
background-color: #f3f3f3;
margin-left: 8px; margin-left: 8px;
margin-right: 8px; margin-right: 8px;
} }

+ 1
- 1
server/sonar-web/src/main/js/apps/metrics/templates/metrics-layout.hbs View File

<div class="page page-limited"> <div class="page page-limited">
<div id="metrics-header"></div> <div id="metrics-header"></div>
<div id="metrics-list"></div>
<div id="metrics-list" class="boxed-group boxed-group-inner"></div>
<div id="metrics-list-footer"></div> <div id="metrics-list-footer"></div>
</div> </div>

+ 16
- 14
server/sonar-web/src/main/js/apps/organizations/components/MembersList.js View File



render() { render() {
return ( return (
<table className="data zebra">
<tbody>
{this.props.members.map(member => (
<MembersListItem
key={member.login}
member={member}
organizationGroups={this.props.organizationGroups}
organization={this.props.organization}
removeMember={this.props.removeMember}
updateMemberGroups={this.props.updateMemberGroups}
/>
))}
</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table className="data zebra">
<tbody>
{this.props.members.map(member => (
<MembersListItem
key={member.login}
member={member}
organizationGroups={this.props.organizationGroups}
organization={this.props.organization}
removeMember={this.props.removeMember}
updateMemberGroups={this.props.updateMemberGroups}
/>
))}
</tbody>
</table>
</div>
); );
} }
} }

+ 78
- 76
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js View File

<h1 className="page-title">{title}</h1> <h1 className="page-title">{title}</h1>
</header> </header>


<form onSubmit={this.handleSubmit}>
<div className="modal-field">
<label htmlFor="organization-name">
{translate('organization.name')}
<em className="mandatory">*</em>
</label>
<input
id="organization-name"
name="name"
required={true}
type="text"
maxLength="64"
value={this.state.name}
disabled={this.state.loading}
onChange={e => this.setState({ name: e.target.value })}
/>
<div className="modal-field-description">
{translate('organization.name.description')}
</div>
</div>
<div className="modal-field">
<label htmlFor="organization-avatar">{translate('organization.avatar')}</label>
<input
id="organization-avatar"
name="avatar"
type="text"
maxLength="256"
value={this.state.avatar}
disabled={this.state.loading}
onChange={this.handleAvatarInputChange}
/>
<div className="modal-field-description">
{translate('organization.avatar.description')}
<div className="boxed-group boxed-group-inner">
<form onSubmit={this.handleSubmit}>
<div className="modal-field">
<label htmlFor="organization-name">
{translate('organization.name')}
<em className="mandatory">*</em>
</label>
<input
id="organization-name"
name="name"
required={true}
type="text"
maxLength="64"
value={this.state.name}
disabled={this.state.loading}
onChange={e => this.setState({ name: e.target.value })}
/>
<div className="modal-field-description">
{translate('organization.name.description')}
</div>
</div> </div>
{!!this.state.avatarImage && (
<div className="spacer-top spacer-bottom">
<div className="little-spacer-bottom">
{translate('organization.avatar.preview')}
{':'}
<div className="modal-field">
<label htmlFor="organization-avatar">{translate('organization.avatar')}</label>
<input
id="organization-avatar"
name="avatar"
type="text"
maxLength="256"
value={this.state.avatar}
disabled={this.state.loading}
onChange={this.handleAvatarInputChange}
/>
<div className="modal-field-description">
{translate('organization.avatar.description')}
</div>
{!!this.state.avatarImage && (
<div className="spacer-top spacer-bottom">
<div className="little-spacer-bottom">
{translate('organization.avatar.preview')}
{':'}
</div>
<img src={this.state.avatarImage} alt="" height={30} />
</div> </div>
<img src={this.state.avatarImage} alt="" height={30} />
)}
</div>
<div className="modal-field">
<label htmlFor="organization-description">{translate('description')}</label>
<textarea
id="organization-description"
name="description"
rows="3"
maxLength="256"
value={this.state.description}
disabled={this.state.loading}
onChange={e => this.setState({ description: e.target.value })}
/>
<div className="modal-field-description">
{translate('organization.description.description')}
</div>
</div>
<div className="modal-field">
<label htmlFor="organization-url">{translate('organization.url')}</label>
<input
id="organization-url"
name="url"
type="text"
maxLength="256"
value={this.state.url}
disabled={this.state.loading}
onChange={e => this.setState({ url: e.target.value })}
/>
<div className="modal-field-description">
{translate('organization.url.description')}
</div> </div>
)}
</div>
<div className="modal-field">
<label htmlFor="organization-description">{translate('description')}</label>
<textarea
id="organization-description"
name="description"
rows="3"
maxLength="256"
value={this.state.description}
disabled={this.state.loading}
onChange={e => this.setState({ description: e.target.value })}
/>
<div className="modal-field-description">
{translate('organization.description.description')}
</div> </div>
</div>
<div className="modal-field">
<label htmlFor="organization-url">{translate('organization.url')}</label>
<input
id="organization-url"
name="url"
type="text"
maxLength="256"
value={this.state.url}
disabled={this.state.loading}
onChange={e => this.setState({ url: e.target.value })}
/>
<div className="modal-field-description">
{translate('organization.url.description')}
<div className="modal-field">
<button type="submit" disabled={this.state.loading}>
{translate('save')}
</button>
{this.state.loading && <i className="spinner spacer-left" />}
</div> </div>
</div>
<div className="modal-field">
<button type="submit" disabled={this.state.loading}>
{translate('save')}
</button>
{this.state.loading && <i className="spinner spacer-left" />}
</div>
</form>
</form>
</div>
</div> </div>
); );
} }

+ 39
- 35
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/MembersList-test.js.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render a list of members of an organization 1`] = ` exports[`should render a list of members of an organization 1`] = `
<table
className="data zebra"
<div
className="boxed-group boxed-group-inner"
> >
<tbody>
<MembersListItem
key="admin"
member={
Object {
"avatar": "",
"groupCount": 3,
"login": "admin",
"name": "Admin Istrator",
<table
className="data zebra"
>
<tbody>
<MembersListItem
key="admin"
member={
Object {
"avatar": "",
"groupCount": 3,
"login": "admin",
"name": "Admin Istrator",
}
} }
}
organization={
Object {
"key": "foo",
"name": "Foo",
organization={
Object {
"key": "foo",
"name": "Foo",
}
} }
}
/>
<MembersListItem
key="john"
member={
Object {
"avatar": "7daf6c79d4802916d83f6266e24850af",
"groupCount": 1,
"login": "john",
"name": "John Doe",
/>
<MembersListItem
key="john"
member={
Object {
"avatar": "7daf6c79d4802916d83f6266e24850af",
"groupCount": 1,
"login": "john",
"name": "John Doe",
}
} }
}
organization={
Object {
"key": "foo",
"name": "Foo",
organization={
Object {
"key": "foo",
"name": "Foo",
}
} }
}
/>
</tbody>
</table>
/>
</tbody>
</table>
</div>
`; `;

+ 325
- 313
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap View File

organization.edit organization.edit
</h1> </h1>
</header> </header>
<form
onSubmit={[Function]}
<div
className="boxed-group boxed-group-inner"
> >
<div
className="modal-field"
<form
onSubmit={[Function]}
> >
<label
htmlFor="organization-name"
<div
className="modal-field"
> >
organization.name
<em
className="mandatory"
<label
htmlFor="organization-name"
>
organization.name
<em
className="mandatory"
>
*
</em>
</label>
<input
disabled={false}
id="organization-name"
maxLength="64"
name="name"
onChange={[Function]}
required={true}
type="text"
value="Foo"
/>
<div
className="modal-field-description"
> >
*
</em>
</label>
<input
disabled={false}
id="organization-name"
maxLength="64"
name="name"
onChange={[Function]}
required={true}
type="text"
value="Foo"
/>
organization.name.description
</div>
</div>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.name.description
<label
htmlFor="organization-avatar"
>
organization.avatar
</label>
<input
disabled={false}
id="organization-avatar"
maxLength="256"
name="avatar"
onChange={[Function]}
type="text"
value=""
/>
<div
className="modal-field-description"
>
organization.avatar.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-avatar"
>
organization.avatar
</label>
<input
disabled={false}
id="organization-avatar"
maxLength="256"
name="avatar"
onChange={[Function]}
type="text"
value=""
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.avatar.description
<label
htmlFor="organization-description"
>
description
</label>
<textarea
disabled={false}
id="organization-description"
maxLength="256"
name="description"
onChange={[Function]}
rows="3"
value=""
/>
<div
className="modal-field-description"
>
organization.description.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-description"
>
description
</label>
<textarea
disabled={false}
id="organization-description"
maxLength="256"
name="description"
onChange={[Function]}
rows="3"
value=""
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.description.description
<label
htmlFor="organization-url"
>
organization.url
</label>
<input
disabled={false}
id="organization-url"
maxLength="256"
name="url"
onChange={[Function]}
type="text"
value=""
/>
<div
className="modal-field-description"
>
organization.url.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-url"
>
organization.url
</label>
<input
disabled={false}
id="organization-url"
maxLength="256"
name="url"
onChange={[Function]}
type="text"
value=""
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.url.description
<button
disabled={false}
type="submit"
>
save
</button>
</div> </div>
</div>
<div
className="modal-field"
>
<button
disabled={false}
type="submit"
>
save
</button>
</div>
</form>
</form>
</div>
</div> </div>
`; `;


organization.edit organization.edit
</h1> </h1>
</header> </header>
<form
onSubmit={[Function]}
<div
className="boxed-group boxed-group-inner"
> >
<div
className="modal-field"
<form
onSubmit={[Function]}
> >
<label
htmlFor="organization-name"
>
organization.name
<em
className="mandatory"
>
*
</em>
</label>
<input
disabled={false}
id="organization-name"
maxLength="64"
name="name"
onChange={[Function]}
required={true}
type="text"
value="New Foo"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.name.description
<label
htmlFor="organization-name"
>
organization.name
<em
className="mandatory"
>
*
</em>
</label>
<input
disabled={false}
id="organization-name"
maxLength="64"
name="name"
onChange={[Function]}
required={true}
type="text"
value="New Foo"
/>
<div
className="modal-field-description"
>
organization.name.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-avatar"
>
organization.avatar
</label>
<input
disabled={false}
id="organization-avatar"
maxLength="256"
name="avatar"
onChange={[Function]}
type="text"
value="foo-avatar"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.avatar.description
<label
htmlFor="organization-avatar"
>
organization.avatar
</label>
<input
disabled={false}
id="organization-avatar"
maxLength="256"
name="avatar"
onChange={[Function]}
type="text"
value="foo-avatar"
/>
<div
className="modal-field-description"
>
organization.avatar.description
</div>
<div
className="spacer-top spacer-bottom"
>
<div
className="little-spacer-bottom"
>
organization.avatar.preview
:
</div>
<img
alt=""
height={30}
src="foo-avatar-image"
/>
</div>
</div> </div>
<div <div
className="spacer-top spacer-bottom"
className="modal-field"
> >
<label
htmlFor="organization-description"
>
description
</label>
<textarea
disabled={false}
id="organization-description"
maxLength="256"
name="description"
onChange={[Function]}
rows="3"
value="foo-description"
/>
<div <div
className="little-spacer-bottom"
className="modal-field-description"
> >
organization.avatar.preview
:
organization.description.description
</div> </div>
<img
alt=""
height={30}
src="foo-avatar-image"
/>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-description"
>
description
</label>
<textarea
disabled={false}
id="organization-description"
maxLength="256"
name="description"
onChange={[Function]}
rows="3"
value="foo-description"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.description.description
<label
htmlFor="organization-url"
>
organization.url
</label>
<input
disabled={false}
id="organization-url"
maxLength="256"
name="url"
onChange={[Function]}
type="text"
value="foo-url"
/>
<div
className="modal-field-description"
>
organization.url.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-url"
>
organization.url
</label>
<input
disabled={false}
id="organization-url"
maxLength="256"
name="url"
onChange={[Function]}
type="text"
value="foo-url"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.url.description
<button
disabled={false}
type="submit"
>
save
</button>
</div> </div>
</div>
<div
className="modal-field"
>
<button
disabled={false}
type="submit"
>
save
</button>
</div>
</form>
</form>
</div>
</div> </div>
`; `;


organization.edit organization.edit
</h1> </h1>
</header> </header>
<form
onSubmit={[Function]}
<div
className="boxed-group boxed-group-inner"
> >
<div
className="modal-field"
<form
onSubmit={[Function]}
> >
<label
htmlFor="organization-name"
>
organization.name
<em
className="mandatory"
>
*
</em>
</label>
<input
disabled={true}
id="organization-name"
maxLength="64"
name="name"
onChange={[Function]}
required={true}
type="text"
value="New Foo"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.name.description
<label
htmlFor="organization-name"
>
organization.name
<em
className="mandatory"
>
*
</em>
</label>
<input
disabled={true}
id="organization-name"
maxLength="64"
name="name"
onChange={[Function]}
required={true}
type="text"
value="New Foo"
/>
<div
className="modal-field-description"
>
organization.name.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-avatar"
>
organization.avatar
</label>
<input
disabled={true}
id="organization-avatar"
maxLength="256"
name="avatar"
onChange={[Function]}
type="text"
value="foo-avatar"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.avatar.description
<label
htmlFor="organization-avatar"
>
organization.avatar
</label>
<input
disabled={true}
id="organization-avatar"
maxLength="256"
name="avatar"
onChange={[Function]}
type="text"
value="foo-avatar"
/>
<div
className="modal-field-description"
>
organization.avatar.description
</div>
<div
className="spacer-top spacer-bottom"
>
<div
className="little-spacer-bottom"
>
organization.avatar.preview
:
</div>
<img
alt=""
height={30}
src="foo-avatar-image"
/>
</div>
</div> </div>
<div <div
className="spacer-top spacer-bottom"
className="modal-field"
> >
<label
htmlFor="organization-description"
>
description
</label>
<textarea
disabled={true}
id="organization-description"
maxLength="256"
name="description"
onChange={[Function]}
rows="3"
value="foo-description"
/>
<div <div
className="little-spacer-bottom"
className="modal-field-description"
> >
organization.avatar.preview
:
organization.description.description
</div> </div>
<img
alt=""
height={30}
src="foo-avatar-image"
/>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-description"
>
description
</label>
<textarea
disabled={true}
id="organization-description"
maxLength="256"
name="description"
onChange={[Function]}
rows="3"
value="foo-description"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.description.description
<label
htmlFor="organization-url"
>
organization.url
</label>
<input
disabled={true}
id="organization-url"
maxLength="256"
name="url"
onChange={[Function]}
type="text"
value="foo-url"
/>
<div
className="modal-field-description"
>
organization.url.description
</div>
</div> </div>
</div>
<div
className="modal-field"
>
<label
htmlFor="organization-url"
>
organization.url
</label>
<input
disabled={true}
id="organization-url"
maxLength="256"
name="url"
onChange={[Function]}
type="text"
value="foo-url"
/>
<div <div
className="modal-field-description"
className="modal-field"
> >
organization.url.description
<button
disabled={true}
type="submit"
>
save
</button>
<i
className="spinner spacer-left"
/>
</div> </div>
</div>
<div
className="modal-field"
>
<button
disabled={true}
type="submit"
>
save
</button>
<i
className="spinner spacer-left"
/>
</div>
</form>
</form>
</div>
</div> </div>
`; `;

+ 26
- 6
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import * as React from 'react'; import * as React from 'react';
import { Organization } from '../../../app/types';
import { connect } from 'react-redux';
import { Organization, HomePageType } from '../../../app/types';
import HomePageSelect from '../../../components/controls/HomePageSelect'; import HomePageSelect from '../../../components/controls/HomePageSelect';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { getGlobalSettingValue } from '../../../store/rootReducer';


interface Props {
interface StateProps {
onSonarCloud: boolean;
}

interface Props extends StateProps {
organization: Organization; organization: Organization;
} }


export default function OrganizationNavigationMeta({ organization }: Props) {
export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props) {
return ( return (
<div className="navbar-context-meta"> <div className="navbar-context-meta">
{organization.url != null && ( {organization.url != null && (
<div className="text-muted"> <div className="text-muted">
<strong>{translate('organization.key')}:</strong> {organization.key} <strong>{translate('organization.key')}:</strong> {organization.key}
</div> </div>
<div className="navbar-context-meta-secondary">
<HomePageSelect currentPage={{ type: 'organization', key: organization.key }} />
</div>
{onSonarCloud && (
<div className="navbar-context-meta-secondary">
<HomePageSelect
currentPage={{ type: HomePageType.Organization, parameter: organization.key }}
/>
</div>
)}
</div> </div>
); );
} }

const mapStateToProps = (state: any): StateProps => {
const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');

return {
onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
};
};

export default connect(mapStateToProps)(OrganizationNavigationMeta);

+ 2
- 1
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx View File

*/ */
import * as React from 'react'; import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import OrganizationNavigationMeta from '../OrganizationNavigationMeta';
import { OrganizationNavigationMeta } from '../OrganizationNavigationMeta';
import { Visibility } from '../../../../app/types'; import { Visibility } from '../../../../app/types';


it('renders', () => { it('renders', () => {
expect( expect(
shallow( shallow(
<OrganizationNavigationMeta <OrganizationNavigationMeta
onSonarCloud={true}
organization={{ organization={{
key: 'foo', key: 'foo',
name: 'Foo', name: 'Foo',

+ 1
- 1
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap View File

} }
} }
/> />
<OrganizationNavigationMeta
<Connect(OrganizationNavigationMeta)
organization={ organization={
Object { Object {
"key": "foo", "key": "foo",

+ 2
- 2
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap View File

<Connect(HomePageSelect) <Connect(HomePageSelect)
currentPage={ currentPage={
Object { Object {
"key": "foo",
"type": "organization",
"parameter": "foo",
"type": "ORGANIZATION",
} }
} }
/> />

+ 0
- 8
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js View File



componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
const domElement = document.querySelector('html');
if (domElement) {
domElement.classList.add('dashboard-page');
}
this.loadMeasures().then(this.loadHistory); this.loadMeasures().then(this.loadHistory);
} }




componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
const domElement = document.querySelector('html');
if (domElement) {
domElement.classList.remove('dashboard-page');
}
} }


loadMeasures() { loadMeasures() {

+ 6
- 4
server/sonar-web/src/main/js/apps/permission-templates/components/List.js View File

)); ));


return ( return (
<table id="permission-templates" className="data zebra permissions-table">
<ListHeader organization={this.props.organization} permissions={this.props.permissions} />
<tbody>{permissionTemplates}</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table id="permission-templates" className="data zebra permissions-table">
<ListHeader organization={this.props.organization} permissions={this.props.permissions} />
<tbody>{permissionTemplates}</tbody>
</table>
</div>
); );
} }
} }

+ 10
- 8
server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx View File

)); ));


return ( return (
<table className="data zebra permissions-table">
{this.renderTableHeader()}
<tbody>
{users.length === 0 && groups.length === 0 && this.renderEmpty()}
{users}
{groups}
</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table className="data zebra permissions-table">
{this.renderTableHeader()}
<tbody>
{users.length === 0 && groups.length === 0 && this.renderEmpty()}
{users}
{groups}
</tbody>
</table>
</div>
); );
} }
} }

+ 0
- 8
server/sonar-web/src/main/js/apps/portfolio/components/App.tsx View File



componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
this.fetchData(); this.fetchData();
} }




componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
} }


fetchData() { fetchData() {

+ 1
- 1
server/sonar-web/src/main/js/apps/project-admin/key/Key.js View File

)} )}


{hasModules && ( {hasModules && (
<div>
<div className="boxed-group boxed-group-inner">
<div className="big-spacer-bottom"> <div className="big-spacer-bottom">
<ul className="tabs"> <ul className="tabs">
<li> <li>

+ 6
- 4
server/sonar-web/src/main/js/apps/project-admin/links/Table.js View File

)); ));


return ( return (
<table id="project-links" className="data zebra">
{this.renderHeader()}
<tbody>{linkRows}</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table id="project-links" className="data zebra">
{this.renderHeader()}
<tbody>{linkRows}</tbody>
</table>
</div>
); );
} }
} }

+ 0
- 4
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js View File



componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
const elem = document.querySelector('html');
elem && elem.classList.add('dashboard-page');
if (this.shouldRedirect()) { if (this.shouldRedirect()) {
const newQuery = { ...this.state.query, graph: getGraph() }; const newQuery = { ...this.state.query, graph: getGraph() };
if (isCustomGraph(newQuery.graph)) { if (isCustomGraph(newQuery.graph)) {


componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
const elem = document.querySelector('html');
elem && elem.classList.remove('dashboard-page');
} }


addCustomEvent = (analysis /*: string */, name /*: string */, category /*: ?string */) => addCustomEvent = (analysis /*: string */, name /*: string */, category /*: ?string */) =>

+ 24
- 20
server/sonar-web/src/main/js/apps/projectBranches/components/App.tsx View File

{this.renderBranchLifeTime()} {this.renderBranchLifeTime()}
</header> </header>


<table className="data zebra zebra-hover">
<thead>
<tr>
<th>{translate('branch')}</th>
<th className="thin nowrap text-right">{translate('status')}</th>
<th className="thin nowrap text-right">{translate('branches.last_analysis_date')}</th>
<th className="thin nowrap text-right">{translate('actions')}</th>
</tr>
</thead>
<tbody>
{sortBranchesAsTree(branches).map(branch => (
<BranchRow
branch={branch}
component={component.key}
key={branch.name}
onChange={onBranchesChange}
/>
))}
</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table className="data zebra zebra-hover">
<thead>
<tr>
<th>{translate('branch')}</th>
<th className="thin nowrap text-right">{translate('status')}</th>
<th className="thin nowrap text-right">
{translate('branches.last_analysis_date')}
</th>
<th className="thin nowrap text-right">{translate('actions')}</th>
</tr>
</thead>
<tbody>
{sortBranchesAsTree(branches).map(branch => (
<BranchRow
branch={branch}
component={component.key}
key={branch.name}
onChange={onBranchesChange}
/>
))}
</tbody>
</table>
</div>
</div> </div>
); );
} }

+ 64
- 60
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/App-test.tsx.snap View File

/> />
</p> </p>
</header> </header>
<table
className="data zebra zebra-hover"
<div
className="boxed-group boxed-group-inner"
> >
<thead>
<tr>
<th>
branch
</th>
<th
className="thin nowrap text-right"
>
status
</th>
<th
className="thin nowrap text-right"
>
branches.last_analysis_date
</th>
<th
className="thin nowrap text-right"
>
actions
</th>
</tr>
</thead>
<tbody>
<BranchRow
branch={
Object {
"isMain": true,
"name": "master",
<table
className="data zebra zebra-hover"
>
<thead>
<tr>
<th>
branch
</th>
<th
className="thin nowrap text-right"
>
status
</th>
<th
className="thin nowrap text-right"
>
branches.last_analysis_date
</th>
<th
className="thin nowrap text-right"
>
actions
</th>
</tr>
</thead>
<tbody>
<BranchRow
branch={
Object {
"isMain": true,
"name": "master",
}
} }
}
component="foo"
key="master"
onChange={[Function]}
/>
<BranchRow
branch={
Object {
"isMain": false,
"mergeBranch": "master",
"name": "branch-1.0",
"type": "SHORT",
component="foo"
key="master"
onChange={[Function]}
/>
<BranchRow
branch={
Object {
"isMain": false,
"mergeBranch": "master",
"name": "branch-1.0",
"type": "SHORT",
}
} }
}
component="foo"
key="branch-1.0"
onChange={[Function]}
/>
<BranchRow
branch={
Object {
"isMain": false,
"name": "branch-1.0",
"type": "LONG",
component="foo"
key="branch-1.0"
onChange={[Function]}
/>
<BranchRow
branch={
Object {
"isMain": false,
"name": "branch-1.0",
"type": "LONG",
}
} }
}
component="foo"
key="branch-1.0"
onChange={[Function]}
/>
</tbody>
</table>
component="foo"
key="branch-1.0"
onChange={[Function]}
/>
</tbody>
</table>
</div>
</div> </div>
`; `;

+ 13
- 11
server/sonar-web/src/main/js/apps/projectQualityProfiles/Table.tsx View File

)); ));


return ( return (
<table className="data zebra">
<thead>
<tr>
<th className="thin nowrap">{translate('language')}</th>
<th className="thin nowrap">{translate('quality_profile')}</th>
{/* keep one empty cell for the spinner */}
<th>&nbsp;</th>
</tr>
</thead>
<tbody>{profileRows}</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table className="data zebra">
<thead>
<tr>
<th className="thin nowrap">{translate('language')}</th>
<th className="thin nowrap">{translate('quality_profile')}</th>
{/* keep one empty cell for the spinner */}
<th>&nbsp;</th>
</tr>
</thead>
<tbody>{profileRows}</tbody>
</table>
</div>
); );
} }

+ 82
- 78
server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Table-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`renders 1`] = ` exports[`renders 1`] = `
<table
className="data zebra"
<div
className="boxed-group boxed-group-inner"
> >
<thead>
<tr>
<th
className="thin nowrap"
>
language
</th>
<th
className="thin nowrap"
>
quality_profile
</th>
<th>
 
</th>
</tr>
</thead>
<tbody>
<ProfileRow
key="java"
onChangeProfile={[Function]}
possibleProfiles={
Array [
<table
className="data zebra"
>
<thead>
<tr>
<th
className="thin nowrap"
>
language
</th>
<th
className="thin nowrap"
>
quality_profile
</th>
<th>
 
</th>
</tr>
</thead>
<tbody>
<ProfileRow
key="java"
onChangeProfile={[Function]}
possibleProfiles={
Array [
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "foo-java",
"language": "java",
"languageName": "java",
"name": "foo-java",
"organization": "org",
},
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "bar-java",
"language": "java",
"languageName": "java",
"name": "bar-java",
"organization": "org",
},
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "baz-java",
"language": "java",
"languageName": "java",
"name": "baz-java",
"organization": "org",
},
]
}
profile={
Object { Object {
"activeDeprecatedRuleCount": 0, "activeDeprecatedRuleCount": 0,
"activeRuleCount": 17, "activeRuleCount": 17,
"languageName": "java", "languageName": "java",
"name": "foo-java", "name": "foo-java",
"organization": "org", "organization": "org",
},
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "bar-java",
"language": "java",
"languageName": "java",
"name": "bar-java",
"organization": "org",
},
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "baz-java",
"language": "java",
"languageName": "java",
"name": "baz-java",
"organization": "org",
},
]
}
profile={
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "foo-java",
"language": "java",
"languageName": "java",
"name": "foo-java",
"organization": "org",
}
}
/>
<ProfileRow
key="js"
onChangeProfile={[Function]}
possibleProfiles={
Array [
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "foo-js",
"language": "js",
"languageName": "js",
"name": "foo-js",
"organization": "org",
},
]
} }
}
/>
<ProfileRow
key="js"
onChangeProfile={[Function]}
possibleProfiles={
Array [
profile={
Object { Object {
"activeDeprecatedRuleCount": 0, "activeDeprecatedRuleCount": 0,
"activeRuleCount": 17, "activeRuleCount": 17,
"languageName": "js", "languageName": "js",
"name": "foo-js", "name": "foo-js",
"organization": "org", "organization": "org",
},
]
}
profile={
Object {
"activeDeprecatedRuleCount": 0,
"activeRuleCount": 17,
"key": "foo-js",
"language": "js",
"languageName": "js",
"name": "foo-js",
"organization": "org",
}
} }
}
/>
</tbody>
</table>
/>
</tbody>
</table>
</div>
`; `;

+ 0
- 10
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx View File

componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;


const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}

if (this.props.isFavorite && !isLoggedIn(this.props.currentUser)) { if (this.props.isFavorite && !isLoggedIn(this.props.currentUser)) {
handleRequiredAuthentication(); handleRequiredAuthentication();
return; return;
componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;


const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}

const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
if (footer) { if (footer) {
footer.classList.remove('page-footer-with-sidebar'); footer.classList.remove('page-footer-with-sidebar');

+ 7
- 7
server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx View File

import Tooltip from '../../../components/controls/Tooltip'; import Tooltip from '../../../components/controls/Tooltip';
import PerspectiveSelect from './PerspectiveSelect'; import PerspectiveSelect from './PerspectiveSelect';
import ProjectsSortingSelect from './ProjectsSortingSelect'; import ProjectsSortingSelect from './ProjectsSortingSelect';
import { CurrentUser, isLoggedIn } from '../../../app/types';
import { CurrentUser, isLoggedIn, HomePageType } from '../../../app/types';
import HomePageSelect from '../../../components/controls/HomePageSelect'; import HomePageSelect from '../../../components/controls/HomePageSelect';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { RawQuery } from '../../../helpers/query'; import { RawQuery } from '../../../helpers/query';
)} )}
</div> </div>


{props.onSonarCloud &&
isLoggedIn(currentUser) &&
props.isFavorite &&
!props.organization && (
<HomePageSelect className="huge-spacer-left" currentPage={{ type: 'my-projects' }} />
)}
{props.isFavorite && (
<HomePageSelect
className="huge-spacer-left"
currentPage={{ type: HomePageType.MyProjects }}
/>
)}
</header> </header>
); );
} }

+ 1
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx View File

className="spacer-right" className="spacer-right"
component={project.key} component={project.key}
favorite={project.isFavorite} favorite={project.isFavorite}
qualifier="TRK"
/> />
)} )}
<h2 className="project-card-name"> <h2 className="project-card-name">

+ 1
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx View File

className="spacer-right" className="spacer-right"
component={project.key} component={project.key}
favorite={project.isFavorite} favorite={project.isFavorite}
qualifier="TRK"
/> />
)} )}
<h2 className="project-card-name"> <h2 className="project-card-name">

+ 28
- 26
server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx View File



render() { render() {
return ( return (
<table
className={classNames('data', 'zebra', { 'new-loading': !this.props.ready })}
id="projects-management-page-projects">
<thead>
<tr>
<th />
<th>{translate('name')}</th>
<th />
<th>{translate('key')}</th>
<th className="thin nowrap text-right">{translate('last_analysis')}</th>
<th />
</tr>
</thead>
<tbody>
{this.props.projects.map(project => (
<ProjectRow
currentUser={this.props.currentUser}
key={project.key}
onApplyTemplate={this.handleApplyTemplate}
onProjectCheck={this.onProjectCheck}
project={project}
selected={this.props.selection.includes(project.key)}
/>
))}
</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table
className={classNames('data', 'zebra', { 'new-loading': !this.props.ready })}
id="projects-management-page-projects">
<thead>
<tr>
<th />
<th>{translate('name')}</th>
<th />
<th>{translate('key')}</th>
<th className="thin nowrap text-right">{translate('last_analysis')}</th>
<th />
</tr>
</thead>
<tbody>
{this.props.projects.map(project => (
<ProjectRow
currentUser={this.props.currentUser}
key={project.key}
onApplyTemplate={this.handleApplyTemplate}
onProjectCheck={this.onProjectCheck}
project={project}
selected={this.props.selection.includes(project.key)}
/>
))}
</tbody>
</table>
</div>
); );
} }
} }

+ 62
- 58
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`renders list of projects 1`] = ` exports[`renders list of projects 1`] = `
<table
className="data zebra new-loading"
id="projects-management-page-projects"
<div
className="boxed-group boxed-group-inner"
> >
<thead>
<tr>
<th />
<th>
name
</th>
<th />
<th>
key
</th>
<th
className="thin nowrap text-right"
>
last_analysis
</th>
<th />
</tr>
</thead>
<tbody>
<ProjectRow
currentUser={
Object {
"login": "foo",
<table
className="data zebra new-loading"
id="projects-management-page-projects"
>
<thead>
<tr>
<th />
<th>
name
</th>
<th />
<th>
key
</th>
<th
className="thin nowrap text-right"
>
last_analysis
</th>
<th />
</tr>
</thead>
<tbody>
<ProjectRow
currentUser={
Object {
"login": "foo",
}
} }
}
key="a"
onApplyTemplate={[Function]}
onProjectCheck={[Function]}
project={
Object {
"key": "a",
"name": "A",
"qualifier": "TRK",
"visibility": "public",
key="a"
onApplyTemplate={[Function]}
onProjectCheck={[Function]}
project={
Object {
"key": "a",
"name": "A",
"qualifier": "TRK",
"visibility": "public",
}
} }
}
selected={true}
/>
<ProjectRow
currentUser={
Object {
"login": "foo",
selected={true}
/>
<ProjectRow
currentUser={
Object {
"login": "foo",
}
} }
}
key="b"
onApplyTemplate={[Function]}
onProjectCheck={[Function]}
project={
Object {
"key": "b",
"name": "B",
"qualifier": "TRK",
"visibility": "public",
key="b"
onApplyTemplate={[Function]}
onProjectCheck={[Function]}
project={
Object {
"key": "b",
"name": "B",
"qualifier": "TRK",
"visibility": "public",
}
} }
}
selected={false}
/>
</tbody>
</table>
selected={false}
/>
</tbody>
</table>
</div>
`; `;

+ 4
- 0
server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js View File



componentDidMount() { componentDidMount() {
this.fetchQualityGates(); this.fetchQualityGates();
// $FlowFixMe
document.body.classList.add('white-page');
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
if (footer) { if (footer) {
footer.classList.add('page-footer-with-sidebar'); footer.classList.add('page-footer-with-sidebar');
} }


componentWillUnmount() { componentWillUnmount() {
// $FlowFixMe
document.body.classList.remove('white-page');
const footer = document.getElementById('footer'); const footer = document.getElementById('footer');
if (footer) { if (footer) {
footer.classList.remove('page-footer-with-sidebar'); footer.classList.remove('page-footer-with-sidebar');

+ 0
- 11
server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx View File

mounted: boolean; mounted: boolean;
state: State = { loading: true }; state: State = { loading: true };


componentWillMount() {
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
}

componentDidMount() { componentDidMount() {
this.mounted = true; this.mounted = true;
this.loadData(); this.loadData();


componentWillUnmount() { componentWillUnmount() {
this.mounted = false; this.mounted = false;
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
} }


fetchProfiles() { fetchProfiles() {

+ 0
- 11
server/sonar-web/src/main/js/apps/settings/components/App.js View File

state /*: State */ = { loaded: false }; state /*: State */ = { loaded: false };


componentDidMount() { componentDidMount() {
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
const componentKey = this.props.component ? this.props.component.key : null; const componentKey = this.props.component ? this.props.component.key : null;
this.props.fetchSettings(componentKey).then(() => this.setState({ loaded: true })); this.props.fetchSettings(componentKey).then(() => this.setState({ loaded: true }));
} }
} }
} }


componentWillUnmount() {
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
}

render() { render() {
if (!this.state.loaded) { if (!this.state.loaded) {
return null; return null;

+ 28
- 26
server/sonar-web/src/main/js/apps/users/UsersList.tsx View File

users users
}: Props) { }: Props) {
return ( return (
<table id="users-list" className="data zebra">
<thead>
<tr>
<th />
<th className="nowrap" />
<th className="nowrap">{translate('my_profile.scm_accounts')}</th>
{!organizationsEnabled && <th className="nowrap">{translate('my_profile.groups')}</th>}
<th className="nowrap">{translate('users.tokens')}</th>
<th className="nowrap">&nbsp;</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<UserListItem
identityProvider={identityProviders.find(
provider => user.externalProvider === provider.key
)}
isCurrentUser={currentUser.isLoggedIn && currentUser.login === user.login}
key={user.login}
onUpdateUsers={onUpdateUsers}
organizationsEnabled={organizationsEnabled}
user={user}
/>
))}
</tbody>
</table>
<div className="boxed-group boxed-group-inner">
<table id="users-list" className="data zebra">
<thead>
<tr>
<th />
<th className="nowrap" />
<th className="nowrap">{translate('my_profile.scm_accounts')}</th>
{!organizationsEnabled && <th className="nowrap">{translate('my_profile.groups')}</th>}
<th className="nowrap">{translate('users.tokens')}</th>
<th className="nowrap">&nbsp;</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<UserListItem
identityProvider={identityProviders.find(
provider => user.externalProvider === provider.key
)}
isCurrentUser={currentUser.isLoggedIn && currentUser.login === user.login}
key={user.login}
onUpdateUsers={onUpdateUsers}
organizationsEnabled={organizationsEnabled}
user={user}
/>
))}
</tbody>
</table>
</div>
); );
} }

+ 61
- 57
server/sonar-web/src/main/js/apps/users/__tests__/__snapshots__/UsersList.tsx.snap View File

// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP


exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<table
className="data zebra"
id="users-list"
<div
className="boxed-group boxed-group-inner"
> >
<thead>
<tr>
<th />
<th
className="nowrap"
/>
<th
className="nowrap"
>
my_profile.scm_accounts
</th>
<th
className="nowrap"
>
users.tokens
</th>
<th
className="nowrap"
>
 
</th>
</tr>
</thead>
<tbody>
<UserListItem
isCurrentUser={true}
key="luke"
onUpdateUsers={[Function]}
organizationsEnabled={true}
user={
Object {
"active": true,
"local": false,
"login": "luke",
"name": "Luke",
"scmAccounts": Array [],
<table
className="data zebra"
id="users-list"
>
<thead>
<tr>
<th />
<th
className="nowrap"
/>
<th
className="nowrap"
>
my_profile.scm_accounts
</th>
<th
className="nowrap"
>
users.tokens
</th>
<th
className="nowrap"
>
 
</th>
</tr>
</thead>
<tbody>
<UserListItem
isCurrentUser={true}
key="luke"
onUpdateUsers={[Function]}
organizationsEnabled={true}
user={
Object {
"active": true,
"local": false,
"login": "luke",
"name": "Luke",
"scmAccounts": Array [],
}
} }
}
/>
<UserListItem
isCurrentUser={false}
key="obi"
onUpdateUsers={[Function]}
organizationsEnabled={true}
user={
Object {
"active": true,
"local": false,
"login": "obi",
"name": "One",
"scmAccounts": Array [],
/>
<UserListItem
isCurrentUser={false}
key="obi"
onUpdateUsers={[Function]}
organizationsEnabled={true}
user={
Object {
"active": true,
"local": false,
"login": "obi",
"name": "One",
"scmAccounts": Array [],
}
} }
}
/>
</tbody>
</table>
/>
</tbody>
</table>
</div>
`; `;

+ 21
- 19
server/sonar-web/src/main/js/apps/web-api/components/Action.tsx View File

); );
} }


return <hr />;
return null;
} }


render() { render() {
const actionKey = getActionKey(domain.path, action.key); const actionKey = getActionKey(domain.path, action.key);


return ( return (
<div id={actionKey} className="web-api-action">
<header className="web-api-action-header">
<div id={actionKey} className="boxed-group">
<header className="web-api-action-header boxed-group-header">
<Link <Link
to={{ pathname: '/web_api/' + actionKey }} to={{ pathname: '/web_api/' + actionKey }}
className="spacer-right link-no-underline"> className="spacer-right link-no-underline">
)} )}
</header> </header>


<div
className="web-api-action-description markdown"
dangerouslySetInnerHTML={{ __html: action.description }}
/>
<div className="boxed-group-inner">
<div
className="web-api-action-description markdown"
dangerouslySetInnerHTML={{ __html: action.description }}
/>


{this.renderTabs()}
{this.renderTabs()}


{showParams &&
action.params && (
<Params
params={action.params}
showDeprecated={this.props.showDeprecated}
showInternal={this.props.showInternal}
/>
)}
{showParams &&
action.params && (
<Params
params={action.params}
showDeprecated={this.props.showDeprecated}
showInternal={this.props.showInternal}
/>
)}


{showResponse &&
action.hasResponseExample && <ResponseExample domain={domain} action={action} />}
{showResponse &&
action.hasResponseExample && <ResponseExample domain={domain} action={action} />}


{showChangelog && <ActionChangelog changelog={action.changelog} />}
{showChangelog && <ActionChangelog changelog={action.changelog} />}
</div>
</div> </div>
); );
} }

+ 43
- 39
server/sonar-web/src/main/js/apps/web-api/components/__tests__/__snapshots__/Action-test.tsx.snap View File



exports[`should render correctly 1`] = ` exports[`should render correctly 1`] = `
<div <div
className="web-api-action"
className="boxed-group"
id="foo/foo" id="foo/foo"
> >
<header <header
className="web-api-action-header"
className="web-api-action-header boxed-group-header"
> >
<Link <Link
className="spacer-right link-no-underline" className="spacer-right link-no-underline"
</h3> </h3>
</header> </header>
<div <div
className="web-api-action-description markdown"
dangerouslySetInnerHTML={
Object {
"__html": "Foo Desc",
}
}
/>
<ul
className="web-api-action-actions tabs"
className="boxed-group-inner"
> >
<li>
<a
className=""
href="#"
onClick={[Function]}
>
api_documentation.parameters
</a>
</li>
<li>
<a
className=""
href="#"
onClick={[Function]}
>
api_documentation.response_example
</a>
</li>
<li>
<a
className=""
href="#"
onClick={[Function]}
>
api_documentation.changelog
</a>
</li>
</ul>
<div
className="web-api-action-description markdown"
dangerouslySetInnerHTML={
Object {
"__html": "Foo Desc",
}
}
/>
<ul
className="web-api-action-actions tabs"
>
<li>
<a
className=""
href="#"
onClick={[Function]}
>
api_documentation.parameters
</a>
</li>
<li>
<a
className=""
href="#"
onClick={[Function]}
>
api_documentation.response_example
</a>
</li>
<li>
<a
className=""
href="#"
onClick={[Function]}
>
api_documentation.changelog
</a>
</li>
</ul>
</div>
</div> </div>
`; `;

+ 1
- 4
server/sonar-web/src/main/js/apps/web-api/styles/web-api.css View File

} }


.web-api-domain-actions { .web-api-domain-actions {
}

.web-api-action {
padding-top: 30px;
margin-top: calc(2 * var(--gridSize));
} }


.web-api-action-title { .web-api-action-title {

+ 1
- 1
server/sonar-web/src/main/js/components/SourceViewer/styles.css View File

.source-viewer { .source-viewer {
width: 100%; width: 100%;
min-height: 200px; min-height: 200px;
border: 1px solid var(--barBorderColor);
border: 1px solid var(--gray80);
box-sizing: border-box; box-sizing: border-box;
background-color: #fff; background-color: #fff;
overflow-x: auto; overflow-x: auto;

+ 3
- 0
server/sonar-web/src/main/js/components/common/BranchStatus.css View File

.branch-status { .branch-status {
display: flex;
align-items: center;
min-width: 64px; min-width: 64px;
line-height: calc(2 * var(--gridSize));
text-align: right; text-align: right;
} }



+ 5
- 5
server/sonar-web/src/main/js/components/common/BranchStatus.tsx View File

const indicatorColor = totalIssues > 0 ? 'red' : 'green'; const indicatorColor = totalIssues > 0 ? 'red' : 'green';


return concise ? ( return concise ? (
<ul className="list-inline branch-status">
<ul className="branch-status">
<li>{totalIssues}</li> <li>{totalIssues}</li>
<li className="spacer-left"> <li className="spacer-left">
<StatusIndicator color={indicatorColor} size="small" /> <StatusIndicator color={indicatorColor} size="small" />
</li> </li>
</ul> </ul>
) : ( ) : (
<ul className="list-inline branch-status">
<ul className="branch-status">
<li className="spacer-right"> <li className="spacer-right">
<StatusIndicator color={indicatorColor} size="small" /> <StatusIndicator color={indicatorColor} size="small" />
</li> </li>
<li>
<li className="spacer-left">
{branch.status.bugs} {branch.status.bugs}
<BugIcon /> <BugIcon />
</li> </li>
<li>
<li className="spacer-left">
{branch.status.vulnerabilities} {branch.status.vulnerabilities}
<VulnerabilityIcon /> <VulnerabilityIcon />
</li> </li>
<li>
<li className="spacer-left">
{branch.status.codeSmells} {branch.status.codeSmells}
<CodeSmellIcon /> <CodeSmellIcon />
</li> </li>

+ 30
- 12
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/BranchStatus-test.tsx.snap View File



exports[`renders status of short-living branches 1`] = ` exports[`renders status of short-living branches 1`] = `
<ul <ul
className="list-inline branch-status"
className="branch-status"
> >
<li <li
className="spacer-right" className="spacer-right"
size="small" size="small"
/> />
</li> </li>
<li>
<li
className="spacer-left"
>
0 0
<BugIcon /> <BugIcon />
</li> </li>
<li>
<li
className="spacer-left"
>
0 0
<VulnerabilityIcon /> <VulnerabilityIcon />
</li> </li>
<li>
<li
className="spacer-left"
>
0 0
<CodeSmellIcon /> <CodeSmellIcon />
</li> </li>


exports[`renders status of short-living branches 2`] = ` exports[`renders status of short-living branches 2`] = `
<ul <ul
className="list-inline branch-status"
className="branch-status"
> >
<li <li
className="spacer-right" className="spacer-right"
size="small" size="small"
/> />
</li> </li>
<li>
<li
className="spacer-left"
>
0 0
<BugIcon /> <BugIcon />
</li> </li>
<li>
<li
className="spacer-left"
>
0 0
<VulnerabilityIcon /> <VulnerabilityIcon />
</li> </li>
<li>
<li
className="spacer-left"
>
1 1
<CodeSmellIcon /> <CodeSmellIcon />
</li> </li>


exports[`renders status of short-living branches 3`] = ` exports[`renders status of short-living branches 3`] = `
<ul <ul
className="list-inline branch-status"
className="branch-status"
> >
<li <li
className="spacer-right" className="spacer-right"
size="small" size="small"
/> />
</li> </li>
<li>
<li
className="spacer-left"
>
7 7
<BugIcon /> <BugIcon />
</li> </li>
<li>
<li
className="spacer-left"
>
6 6
<VulnerabilityIcon /> <VulnerabilityIcon />
</li> </li>
<li>
<li
className="spacer-left"
>
3 3
<CodeSmellIcon /> <CodeSmellIcon />
</li> </li>

+ 2
- 2
server/sonar-web/src/main/js/components/controls/Favorite.tsx View File

className?: string; className?: string;
component: string; component: string;
favorite: boolean; favorite: boolean;
qualifier: string;
} }


export default function Favorite({ favorite, component, ...other }: Props) {
export default function Favorite({ component, ...other }: Props) {
return ( return (
<FavoriteBase <FavoriteBase
{...other} {...other}
favorite={favorite}
addFavorite={() => addFavorite(component)} addFavorite={() => addFavorite(component)}
removeFavorite={() => removeFavorite(component)} removeFavorite={() => removeFavorite(component)}
/> />

+ 5
- 4
server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx View File

import FavoriteIcon from '../icons-components/FavoriteIcon'; import FavoriteIcon from '../icons-components/FavoriteIcon';
import { translate } from '../../helpers/l10n'; import { translate } from '../../helpers/l10n';


interface Props {
export interface Props {
addFavorite: () => Promise<void>; addFavorite: () => Promise<void>;
className?: string; className?: string;
favorite: boolean; favorite: boolean;
qualifier: string;
removeFavorite: () => Promise<void>; removeFavorite: () => Promise<void>;
} }




render() { render() {
const tooltip = this.state.favorite const tooltip = this.state.favorite
? translate('favorite.current')
: translate('favorite.check');
? translate('favorite.current', this.props.qualifier)
: translate('favorite.check', this.props.qualifier);
return ( return (
<Tooltip overlay={tooltip}>
<Tooltip overlay={tooltip} placement="left">
<a <a
className={classNames('display-inline-block', 'link-no-underline', this.props.className)} className={classNames('display-inline-block', 'link-no-underline', this.props.className)}
href="#" href="#"

+ 0
- 42
server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js View File

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

export default class FavoriteIssueFilter extends React.PureComponent {
static propTypes = {
favorite: PropTypes.bool.isRequired,
filter: PropTypes.shape({
id: PropTypes.string.isRequired
}).isRequired
};

render() {
return (
<FavoriteBase
favorite={this.props.favorite}
addFavorite={() => toggleIssueFilter(this.props.filter.id)}
removeFavorite={() => toggleIssueFilter(this.props.filter.id)}
/>
);
}
}

+ 13
- 7
server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx View File

import HomeIcon from '../icons-components/HomeIcon'; import HomeIcon from '../icons-components/HomeIcon';
import { CurrentUser, isLoggedIn, HomePage, isSameHomePage } from '../../app/types'; import { CurrentUser, isLoggedIn, HomePage, isSameHomePage } from '../../app/types';
import { translate } from '../../helpers/l10n'; import { translate } from '../../helpers/l10n';
import { getCurrentUser } from '../../store/rootReducer';
import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
import { setHomePage } from '../../store/users/actions'; import { setHomePage } from '../../store/users/actions';


interface StateProps { interface StateProps {
currentUser: CurrentUser; currentUser: CurrentUser;
onSonarCloud: boolean;
} }


interface DispatchProps { interface DispatchProps {
}; };


render() { render() {
const { currentPage, currentUser } = this.props;
const { currentPage, currentUser, onSonarCloud } = this.props;


if (!isLoggedIn(currentUser)) {
if (!isLoggedIn(currentUser) || !onSonarCloud) {
return null; return null;
} }


const tooltip = checked ? translate('homepage.current') : translate('homepage.check'); const tooltip = checked ? translate('homepage.current') : translate('homepage.check');


return ( return (
<Tooltip overlay={tooltip}>
<Tooltip overlay={tooltip} placement="left">
{checked ? ( {checked ? (
<span className={classNames('display-inline-block', this.props.className)}> <span className={classNames('display-inline-block', this.props.className)}>
<HomeIcon filled={checked} /> <HomeIcon filled={checked} />
} }
} }


const mapStateToProps = (state: any): StateProps => ({
currentUser: getCurrentUser(state)
});
const mapStateToProps = (state: any): StateProps => {
const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');

return {
currentUser: getCurrentUser(state),
onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
};
};


const mapDispatchToProps: DispatchProps = { setHomePage }; const mapDispatchToProps: DispatchProps = { setHomePage };



+ 1
- 1
server/sonar-web/src/main/js/components/controls/__tests__/Favorite-test.tsx View File

import Favorite from '../Favorite'; import Favorite from '../Favorite';


it('renders', () => { it('renders', () => {
expect(shallow(<Favorite component="foo" favorite={true} />)).toMatchSnapshot();
expect(shallow(<Favorite component="foo" favorite={true} qualifier="TRK" />)).toMatchSnapshot();
}); });

+ 9
- 3
server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.tsx View File

*/ */
import * as React from 'react'; import * as React from 'react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import FavoriteBase from '../FavoriteBase';
import FavoriteBase, { Props } from '../FavoriteBase';
import { click } from '../../../helpers/testUtils'; import { click } from '../../../helpers/testUtils';


it('should render favorite', () => { it('should render favorite', () => {
expect(removeFavorite).toBeCalled(); expect(removeFavorite).toBeCalled();
}); });


function renderFavoriteBase(props?: any) {
function renderFavoriteBase(props: Partial<Props> = {}) {
return shallow( return shallow(
<FavoriteBase favorite={true} addFavorite={jest.fn()} removeFavorite={jest.fn()} {...props} />
<FavoriteBase
favorite={true}
addFavorite={jest.fn()}
qualifier="TRK"
removeFavorite={jest.fn()}
{...props}
/>
); );
} }

+ 1
- 0
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Favorite-test.tsx.snap View File

<FavoriteBase <FavoriteBase
addFavorite={[Function]} addFavorite={[Function]}
favorite={true} favorite={true}
qualifier="TRK"
removeFavorite={[Function]} removeFavorite={[Function]}
/> />
`; `;

+ 4
- 4
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.tsx.snap View File



exports[`should render favorite 1`] = ` exports[`should render favorite 1`] = `
<Tooltip <Tooltip
overlay="favorite.current"
placement="bottom"
overlay="favorite.current.TRK"
placement="left"
> >
<a <a
className="display-inline-block link-no-underline" className="display-inline-block link-no-underline"


exports[`should render not favorite 1`] = ` exports[`should render not favorite 1`] = `
<Tooltip <Tooltip
overlay="favorite.check"
placement="bottom"
overlay="favorite.check.TRK"
placement="left"
> >
<a <a
className="display-inline-block link-no-underline" className="display-inline-block link-no-underline"

+ 3
- 2
server/sonar-web/src/main/js/components/nav/ContextNavBar.css View File

.navbar-context, .navbar-context,
.navbar-context .navbar-inner { .navbar-context .navbar-inner {
background-color: var(--barBackgroundColor);
background-color: #fff;
z-index: 420; z-index: 420;
} }


} }


.navbar-context-header { .navbar-context-header {
display: inline-flex;
display: flex;
align-items: center; align-items: center;
height: calc(4 * var(--gridSize)); height: calc(4 * var(--gridSize));
font-size: var(--bigFontSize); font-size: var(--bigFontSize);
top: 36px; top: 36px;
right: 0; right: 0;
padding: 0 20px; padding: 0 20px;
white-space: nowrap;
} }


.navbar-context-description { .navbar-context-description {

+ 7
- 7
server/sonar-web/src/main/js/helpers/urls.ts View File

import { omitBy, isNil } from 'lodash'; import { omitBy, isNil } from 'lodash';
import { isShortLivingBranch } from './branches'; import { isShortLivingBranch } from './branches';
import { getProfilePath } from '../apps/quality-profiles/utils'; import { getProfilePath } from '../apps/quality-profiles/utils';
import { Branch, HomePage } from '../app/types';
import { Branch, HomePage, HomePageType } from '../app/types';


interface Query { interface Query {
[x: string]: string | undefined; [x: string]: string | undefined;


export function getHomePageUrl(homepage: HomePage) { export function getHomePageUrl(homepage: HomePage) {
switch (homepage.type) { switch (homepage.type) {
case 'project':
return getProjectUrl(homepage.key!);
case 'organization':
return getOrganizationUrl(homepage.key!);
case 'my-projects':
case HomePageType.Project:
return getProjectUrl(homepage.parameter!);
case HomePageType.Organization:
return getOrganizationUrl(homepage.parameter!);
case HomePageType.MyProjects:
return '/projects'; return '/projects';
case 'my-issues':
case HomePageType.MyIssues:
return { pathname: '/issues', query: { resolved: 'false' } }; return { pathname: '/issues', query: { resolved: 'false' } };
} }



+ 22
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

# #
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
notification.channel.EmailNotificationChannel=Email notification.channel.EmailNotificationChannel=Email
notification.dispatcher.information=Receive notifications when specific types of events occur. A notification is never sent to the author of the event.
notification.dispatcher.information=A notification is never sent to the author of the event.
notification.dispatcher.ChangesOnMyIssue=Changes in issues assigned to me notification.dispatcher.ChangesOnMyIssue=Changes in issues assigned to me
notification.dispatcher.NewIssues=New issues notification.dispatcher.NewIssues=New issues
notification.dispatcher.NewAlerts=New quality gate status notification.dispatcher.NewAlerts=New quality gate status
# FAVORITE # FAVORITE
# #
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
favorite.current=This is your favorite component. Click to unset.
favorite.check=Click to mark this component as favorite.
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.

Loading…
Cancel
Save