@@ -21,32 +21,16 @@ import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
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> | |||
); | |||
} |
@@ -27,35 +27,19 @@ interface Props { | |||
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> | |||
<GlobalFooterContainer hideLoggedInInfo={this.props.hideLoggedInInfo} /> | |||
</div> | |||
); | |||
} | |||
<GlobalFooterContainer hideLoggedInInfo={props.hideLoggedInInfo} /> | |||
</div> | |||
); | |||
} |
@@ -20,34 +20,18 @@ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
export default class ExtensionNotFound extends React.PureComponent { | |||
componentDidMount() { | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.add('dashboard-page'); | |||
} | |||
} | |||
componentWillUnmount() { | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.remove('dashboard-page'); | |||
} | |||
} | |||
render() { | |||
return ( | |||
<div id="bd" className="page-wrapper-simple"> | |||
<div id="nonav" className="page-simple"> | |||
<h2 className="big-spacer-bottom">The page you were looking for does not exist.</h2> | |||
<p className="spacer-bottom"> | |||
You may have mistyped the address or the page may have moved. | |||
</p> | |||
<p> | |||
<Link to="/">Go back to the homepage</Link> | |||
</p> | |||
</div> | |||
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> | |||
); | |||
} |
@@ -1,9 +1,9 @@ | |||
.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)); | |||
line-height: 16px; | |||
font-size: var(--baseFontSize); | |||
} | |||
.navbar-context-meta-branch-menu-item { |
@@ -18,8 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import ComponentNavBranch from './ComponentNavBranch'; | |||
import ComponentNavBreadcrumbs from './ComponentNavBreadcrumbs'; | |||
import ComponentNavHeader from './ComponentNavHeader'; | |||
import ComponentNavMeta from './ComponentNavMeta'; | |||
import ComponentNavMenu from './ComponentNavMenu'; | |||
import ComponentNavBgTaskNotif from './ComponentNavBgTaskNotif'; | |||
@@ -111,16 +110,13 @@ export default class ComponentNav extends React.PureComponent<Props, State> { | |||
id="context-navigation" | |||
height={notifComponent ? theme.contextNavHeightRaw + 20 : theme.contextNavHeightRaw} | |||
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} /> | |||
<ComponentNavMenu | |||
branch={this.props.currentBranch} |
@@ -20,7 +20,8 @@ | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
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 { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer'; | |||
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar'; | |||
@@ -36,46 +37,16 @@ interface StateProps { | |||
} | |||
interface OwnProps { | |||
branches: Branch[]; | |||
component: Component; | |||
currentBranch?: Branch; | |||
location?: any; | |||
} | |||
interface Props extends StateProps, OwnProps {} | |||
export function ComponentNavBreadcrumbs(props: Props) { | |||
export function ComponentNavHeader(props: 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 ( | |||
<header className="navbar-context-header"> | |||
@@ -83,29 +54,60 @@ export function ComponentNavBreadcrumbs(props: Props) { | |||
title={component.name} | |||
organization={organization && shouldOrganizationBeDisplayed ? organization : undefined} | |||
/> | |||
{organization && | |||
shouldOrganizationBeDisplayed && <OrganizationAvatar organization={organization} />} | |||
{organization && | |||
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' && ( | |||
<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> | |||
); | |||
} | |||
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 => ({ | |||
organization: | |||
ownProps.component.organization && getOrganizationByKey(state, ownProps.component.organization), | |||
shouldOrganizationBeDisplayed: areThereCustomOrganizations(state) | |||
}); | |||
export default connect(mapStateToProps)(ComponentNavBreadcrumbs); | |||
export default connect(mapStateToProps)(ComponentNavHeader); |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
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 DateTimeFormatter from '../../../../components/intl/DateTimeFormatter'; | |||
import Favorite from '../../../../components/controls/Favorite'; | |||
@@ -60,14 +60,22 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) { | |||
{isLoggedIn(currentUser) && | |||
mainBranch && ( | |||
<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 | |||
className="spacer-left" | |||
currentPage={{ type: 'project', key: component.key }} | |||
currentPage={{ type: HomePageType.Project, parameter: component.key }} | |||
/> | |||
</div> | |||
)} | |||
{shortBranch && <BranchStatus branch={branch!} />} | |||
{shortBranch && ( | |||
<div className="navbar-context-meta-secondary"> | |||
<BranchStatus branch={branch!} /> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -29,9 +29,9 @@ jest.mock('../ComponentNavMeta', () => ({ | |||
} | |||
})); | |||
jest.mock('../ComponentNavBreadcrumbs', () => ({ | |||
jest.mock('../ComponentNavHeader', () => ({ | |||
// eslint-disable-next-line | |||
default: function ComponentNavBreadcrumbs() { | |||
default: function ComponentNavHeader() { | |||
return null; | |||
} | |||
})); |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { ComponentNavBreadcrumbs } from '../ComponentNavBreadcrumbs'; | |||
import { ComponentNavHeader } from '../ComponentNavHeader'; | |||
import { Visibility } from '../../../../types'; | |||
it('should not render breadcrumbs with one element', () => { | |||
@@ -32,7 +32,7 @@ it('should not render breadcrumbs with one element', () => { | |||
visibility: 'public' | |||
}; | |||
const result = shallow( | |||
<ComponentNavBreadcrumbs component={component} shouldOrganizationBeDisplayed={false} /> | |||
<ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} /> | |||
); | |||
expect(result).toMatchSnapshot(); | |||
}); | |||
@@ -52,7 +52,8 @@ it('should render organization', () => { | |||
projectVisibility: Visibility.Public | |||
}; | |||
const result = shallow( | |||
<ComponentNavBreadcrumbs | |||
<ComponentNavHeader | |||
branches={[]} | |||
component={component} | |||
organization={organization} | |||
shouldOrganizationBeDisplayed={true} | |||
@@ -71,7 +72,7 @@ it('renders private badge', () => { | |||
visibility: 'private' | |||
}; | |||
const result = shallow( | |||
<ComponentNavBreadcrumbs component={component} shouldOrganizationBeDisplayed={false} /> | |||
<ComponentNavHeader branches={[]} component={component} shouldOrganizationBeDisplayed={false} /> | |||
); | |||
expect(result.find('PrivateBadge')).toHaveLength(1); | |||
}); |
@@ -27,7 +27,8 @@ exports[`renders 1`] = ` | |||
/> | |||
} | |||
> | |||
<ComponentNavBreadcrumbs | |||
<ComponentNavHeader | |||
branches={Array []} | |||
component={ | |||
Object { | |||
"breadcrumbs": Array [ | |||
@@ -43,6 +44,7 @@ exports[`renders 1`] = ` | |||
"qualifier": "TRK", | |||
} | |||
} | |||
location={Object {}} | |||
/> | |||
<ComponentNavMeta | |||
component={ |
@@ -1,98 +0,0 @@ | |||
// 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> | |||
`; |
@@ -0,0 +1,104 @@ | |||
// 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> | |||
`; |
@@ -38,20 +38,24 @@ exports[`renders status of short-living branch 1`] = ` | |||
date="2017-01-02T00:00:00.000Z" | |||
/> | |||
</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> | |||
`; |
@@ -17,6 +17,10 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.white-page { | |||
background-color: #fff !important; | |||
} | |||
.global-container { | |||
display: flex; | |||
flex-direction: column; | |||
@@ -150,7 +154,8 @@ | |||
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; | |||
} | |||
@@ -26,7 +26,7 @@ | |||
html, | |||
body { | |||
background-color: #fff; | |||
background-color: var(--barBackgroundColor); | |||
} | |||
body { |
@@ -485,6 +485,7 @@ a:hover > .icon-radio { | |||
stroke-width: 1.41421356; | |||
stroke-opacity: 1; | |||
fill-opacity: 0; | |||
vector-effect: non-scaling-stroke; | |||
transition: all 0.2s ease; | |||
} | |||
@@ -31,11 +31,10 @@ | |||
display: none !important; | |||
} | |||
.dashboard-page, | |||
.dashboard-page body { | |||
background-color: #fff; | |||
html, | |||
body { | |||
background-color: #fff !important; | |||
} | |||
.widget thead, | |||
.widget tfoot { | |||
display: table-row-group; |
@@ -17,11 +17,6 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.dashboard-page, | |||
.dashboard-page body { | |||
background-color: var(--barBackgroundColor); | |||
} | |||
.tabs { | |||
height: 20px; | |||
border-bottom: 1px solid #ddd; |
@@ -62,13 +62,15 @@ export interface Extension { | |||
name: string; | |||
} | |||
export interface Breadcrumb { | |||
key: string; | |||
name: string; | |||
qualifier: string; | |||
} | |||
export interface Component { | |||
analysisDate?: string; | |||
breadcrumbs: Array<{ | |||
key: string; | |||
name: string; | |||
qualifier: string; | |||
}>; | |||
breadcrumbs: Breadcrumb[]; | |||
configuration?: ComponentConfiguration; | |||
description?: string; | |||
extensions?: Extension[]; | |||
@@ -141,13 +143,20 @@ export interface CurrentUser { | |||
showOnboardingTutorial?: boolean; | |||
} | |||
export enum HomePageType { | |||
Project = 'PROJECT', | |||
Organization = 'ORGANIZATION', | |||
MyProjects = 'MY_PROJECTS', | |||
MyIssues = 'MY_ISSUES' | |||
} | |||
export interface HomePage { | |||
key?: string; | |||
type: string; | |||
parameter?: string; | |||
type: HomePageType; | |||
} | |||
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 { |
@@ -76,11 +76,15 @@ class AboutApp extends React.PureComponent { | |||
window.location = 'https://about.sonarcloud.io'; | |||
} else { | |||
this.loadData(); | |||
// $FlowFixMe | |||
document.body.classList.add('white-page'); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
// $FlowFixMe | |||
document.body.classList.remove('white-page'); | |||
} | |||
loadProjects() { |
@@ -8,7 +8,7 @@ | |||
padding-top: 20px; | |||
padding-bottom: 20px; | |||
border-bottom: 1px solid var(--barBorderColor); | |||
background-color: var(--barBackgroundColor); | |||
background-color: #fff; | |||
} | |||
.account-nav { |
@@ -70,10 +70,10 @@ export default class Password extends Component { | |||
const { success, errors } = this.state; | |||
return ( | |||
<section> | |||
<section className="boxed-group"> | |||
<h2 className="spacer-bottom">{translate('my_profile.password.title')}</h2> | |||
<form onSubmit={this.handleChangePassword}> | |||
<form className="boxed-group-inner" onSubmit={this.handleChangePassword}> | |||
{success && ( | |||
<div className="alert alert-success">{translate('my_profile.password.changed')}</div> | |||
)} |
@@ -31,11 +31,7 @@ function Security(props) { | |||
return ( | |||
<div className="account-body account-container"> | |||
<Helmet title={translate('my_account.security')} /> | |||
<Tokens user={user} /> | |||
{user.local && <hr className="account-separator" />} | |||
{user.local && <Password user={user} />} | |||
</div> | |||
); |
@@ -46,30 +46,32 @@ type Props = { | |||
function GlobalNotifications(props /*: Props */) { | |||
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> | |||
); | |||
} |
@@ -40,13 +40,8 @@ class Notifications extends React.PureComponent { | |||
return ( | |||
<div className="account-body account-container"> | |||
<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 /> | |||
<hr className="account-separator" /> | |||
<Projects /> | |||
</div> | |||
); |
@@ -113,30 +113,32 @@ class Projects extends React.PureComponent { | |||
const allProjects = [...this.props.projects, ...this.state.addedProjects]; | |||
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> | |||
</section> | |||
); |
@@ -1,69 +1,73 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should match snapshot 1`] = ` | |||
<section> | |||
<h2 | |||
className="spacer-bottom" | |||
> | |||
<section | |||
className="boxed-group" | |||
> | |||
<h2> | |||
my_profile.overall_notifications.title | |||
</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> | |||
`; |
@@ -10,14 +10,11 @@ exports[`should match snapshot 1`] = ` | |||
title="my_account.notifications" | |||
/> | |||
<p | |||
className="big-spacer-bottom" | |||
className="alert alert-info" | |||
> | |||
notification.dispatcher.information | |||
</p> | |||
<Connect(GlobalNotifications) /> | |||
<hr | |||
className="account-separator" | |||
/> | |||
<Connect(Projects) /> | |||
</div> | |||
`; |
@@ -1,178 +1,190 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render projects 1`] = ` | |||
<section> | |||
<h2 | |||
className="spacer-bottom" | |||
> | |||
<section | |||
className="boxed-group" | |||
> | |||
<h2> | |||
my_profile.per_project_notifications.title | |||
</h2> | |||
<Connect(ProjectNotifications) | |||
key="foo" | |||
project={ | |||
Object { | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
/> | |||
<Connect(ProjectNotifications) | |||
key="bar" | |||
project={ | |||
Object { | |||
"key": "bar", | |||
"name": "Bar", | |||
} | |||
} | |||
/> | |||
<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 { | |||
"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> | |||
</section> | |||
`; | |||
exports[`should render projects 2`] = ` | |||
<section> | |||
<h2 | |||
className="spacer-bottom" | |||
> | |||
<section | |||
className="boxed-group" | |||
> | |||
<h2> | |||
my_profile.per_project_notifications.title | |||
</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 | |||
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 { | |||
"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> | |||
</section> | |||
`; | |||
exports[`should render projects 3`] = ` | |||
<section> | |||
<h2 | |||
className="spacer-bottom" | |||
> | |||
<section | |||
className="boxed-group" | |||
> | |||
<h2> | |||
my_profile.per_project_notifications.title | |||
</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 | |||
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 { | |||
"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> | |||
</section> | |||
`; |
@@ -21,12 +21,17 @@ import * as React from 'react'; | |||
import { sortBy } from 'lodash'; | |||
import OrganizationCard from './OrganizationCard'; | |||
import { Organization } from '../../../app/types'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
organizations: Organization[]; | |||
} | |||
export default function OrganizationsList({ organizations }: Props) { | |||
if (organizations.length === 0) { | |||
return <div>{translate('my_account.organizations.no_results')}</div>; | |||
} | |||
return ( | |||
<ul className="account-projects-list"> | |||
{sortBy(organizations, organization => organization.name.toLocaleLowerCase()).map( |
@@ -87,29 +87,22 @@ class UserOrganizations extends React.PureComponent<Props, State> { | |||
<div className="account-body account-container"> | |||
<Helmet title={translate('my_account.organizations')} /> | |||
<header className="page-header"> | |||
<h2 className="page-title">{translate('my_account.organizations')}</h2> | |||
<div className="boxed-group"> | |||
{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> | |||
)} | |||
{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 && ( | |||
<CreateOrganizationForm |
@@ -45,29 +45,31 @@ function Profile(props /*: Props */) { | |||
return ( | |||
<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> | |||
)} | |||
{!!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> | |||
); | |||
} |
@@ -1,77 +1,81 @@ | |||
<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> </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> | |||
<td colspan="3"> | |||
<span class="note">{{t 'users.no_tokens'}}</span> | |||
</td> | |||
<th>{{t 'name'}}</th> | |||
<th class="text-right">{{t 'created'}}</th> | |||
<th> </th> | |||
</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> |
@@ -49,34 +49,36 @@ export default class Tasks extends React.PureComponent { | |||
}); | |||
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> </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> </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> </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> </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> | |||
); | |||
} | |||
} |
@@ -200,7 +200,9 @@ export default class App extends React.PureComponent<Props, State> { | |||
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 ( | |||
<div className="page page-limited"> |
@@ -182,12 +182,14 @@ export default class Search extends React.PureComponent<Props, State> { | |||
{loading && <i className="spinner spacer-left" />} | |||
{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> | |||
); |
@@ -44,6 +44,9 @@ class CodingRulesAppContainer extends React.PureComponent { | |||
*/ | |||
componentDidMount() { | |||
// $FlowFixMe | |||
document.body.classList.add('white-page'); | |||
if (this.props.appState.organizationsEnabled && !this.props.params.organizationKey) { | |||
// redirect to organization-level rules page | |||
this.props.router.replace( | |||
@@ -62,6 +65,9 @@ class CodingRulesAppContainer extends React.PureComponent { | |||
} | |||
componentWillUnmount() { | |||
// $FlowFixMe | |||
document.body.classList.remove('white-page'); | |||
if (this.stop) { | |||
this.stop(); | |||
} |
@@ -74,6 +74,8 @@ export default class App extends React.PureComponent { | |||
componentDidMount() { | |||
this.mounted = true; | |||
// $FlowFixMe | |||
document.body.classList.add('white-page'); | |||
this.props.fetchMetrics(); | |||
this.fetchMeasures(this.props); | |||
key.setScope('measures-files'); | |||
@@ -95,6 +97,8 @@ export default class App extends React.PureComponent { | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
// $FlowFixMe | |||
document.body.classList.remove('white-page'); | |||
key.deleteScope('measures-files'); | |||
const footer = document.getElementById('footer'); | |||
if (footer) { |
@@ -1,12 +1,14 @@ | |||
<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> </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> </th> | |||
</tr> | |||
</thead> | |||
<tbody></tbody> | |||
</table> | |||
</div> |
@@ -1,4 +1,4 @@ | |||
<div> | |||
<div class="boxed-group boxed-group-inner"> | |||
{{#isNull organization}} | |||
<div class="panel panel-vertical js-anyone"> | |||
<div class="display-inline-block text-top width-20"> |
@@ -1,4 +1,4 @@ | |||
<div class="panel panel-vertical bordered-bottom spacer-bottom"> | |||
<div class="big-spacer-bottom"> | |||
<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"> | |||
<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;"> |
@@ -151,6 +151,9 @@ export default class App extends React.PureComponent { | |||
return; | |||
} | |||
// $FlowFixMe | |||
document.body.classList.add('white-page'); | |||
const footer = document.getElementById('footer'); | |||
if (footer) { | |||
footer.classList.add('page-footer-with-sidebar'); | |||
@@ -206,6 +209,9 @@ export default class App extends React.PureComponent { | |||
componentWillUnmount() { | |||
this.detachShortcuts(); | |||
// $FlowFixMe | |||
document.body.classList.remove('white-page'); | |||
const footer = document.getElementById('footer'); | |||
if (footer) { | |||
footer.classList.remove('page-footer-with-sidebar'); |
@@ -22,6 +22,7 @@ import React from 'react'; | |||
import IssuesCounter from './IssuesCounter'; | |||
import ReloadButton from './ReloadButton'; | |||
/*:: import type { Paging } from '../utils'; */ | |||
import { HomePageType } from '../../../app/types'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import HomePageSelect from '../../../components/controls/HomePageSelect'; | |||
import { translate } from '../../../helpers/l10n'; | |||
@@ -74,7 +75,10 @@ export default class PageActions extends React.PureComponent { | |||
</div> | |||
{this.props.canSetHome && ( | |||
<HomePageSelect className="huge-spacer-left" currentPage={{ type: 'my-issues' }} /> | |||
<HomePageSelect | |||
className="huge-spacer-left" | |||
currentPage={{ type: HomePageType.MyIssues }} | |||
/> | |||
)} | |||
</div> | |||
); |
@@ -79,7 +79,7 @@ export default class PluginsList extends React.PureComponent<Props> { | |||
render() { | |||
return ( | |||
<div id="marketplace-plugins"> | |||
<div className="boxed-group boxed-group-inner" id="marketplace-plugins"> | |||
<ul> | |||
{this.props.plugins.map(plugin => ( | |||
<li key={plugin.key} className="panel panel-vertical"> |
@@ -48,7 +48,7 @@ export default class Search extends React.PureComponent<Props> { | |||
} | |||
]; | |||
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"> | |||
<RadioToggle | |||
name="marketplace-filter" |
@@ -12,7 +12,6 @@ | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: space-between; | |||
background-color: #f3f3f3; | |||
margin-left: 8px; | |||
margin-right: 8px; | |||
} |
@@ -1,5 +1,5 @@ | |||
<div class="page page-limited"> | |||
<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> |
@@ -38,20 +38,22 @@ export default class MembersList extends React.PureComponent { | |||
render() { | |||
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> | |||
); | |||
} | |||
} |
@@ -107,87 +107,89 @@ class OrganizationEdit extends React.PureComponent { | |||
<h1 className="page-title">{title}</h1> | |||
</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> | |||
{!!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> | |||
<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 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 className="modal-field"> | |||
<button type="submit" disabled={this.state.loading}> | |||
{translate('save')} | |||
</button> | |||
{this.state.loading && <i className="spinner spacer-left" />} | |||
</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> | |||
); | |||
} |
@@ -1,44 +1,48 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
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> | |||
`; |
@@ -18,118 +18,122 @@ exports[`smoke test 1`] = ` | |||
organization.edit | |||
</h1> | |||
</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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
className="modal-field-description" | |||
className="modal-field" | |||
> | |||
organization.url.description | |||
<button | |||
disabled={false} | |||
type="submit" | |||
> | |||
save | |||
</button> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<button | |||
disabled={false} | |||
type="submit" | |||
> | |||
save | |||
</button> | |||
</div> | |||
</form> | |||
</form> | |||
</div> | |||
</div> | |||
`; | |||
@@ -151,133 +155,137 @@ exports[`smoke test 2`] = ` | |||
organization.edit | |||
</h1> | |||
</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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
className="little-spacer-bottom" | |||
className="modal-field-description" | |||
> | |||
organization.avatar.preview | |||
: | |||
organization.description.description | |||
</div> | |||
<img | |||
alt="" | |||
height={30} | |||
src="foo-avatar-image" | |||
/> | |||
</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 | |||
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 | |||
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 | |||
className="modal-field-description" | |||
className="modal-field" | |||
> | |||
organization.url.description | |||
<button | |||
disabled={false} | |||
type="submit" | |||
> | |||
save | |||
</button> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<button | |||
disabled={false} | |||
type="submit" | |||
> | |||
save | |||
</button> | |||
</div> | |||
</form> | |||
</form> | |||
</div> | |||
</div> | |||
`; | |||
@@ -299,135 +307,139 @@ exports[`smoke test 3`] = ` | |||
organization.edit | |||
</h1> | |||
</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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
className="little-spacer-bottom" | |||
className="modal-field-description" | |||
> | |||
organization.avatar.preview | |||
: | |||
organization.description.description | |||
</div> | |||
<img | |||
alt="" | |||
height={30} | |||
src="foo-avatar-image" | |||
/> | |||
</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 | |||
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 | |||
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 | |||
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 | |||
className="modal-field" | |||
> | |||
<button | |||
disabled={true} | |||
type="submit" | |||
> | |||
save | |||
</button> | |||
<i | |||
className="spinner spacer-left" | |||
/> | |||
</div> | |||
</form> | |||
</form> | |||
</div> | |||
</div> | |||
`; |
@@ -18,15 +18,21 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
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 { translate } from '../../../helpers/l10n'; | |||
import { getGlobalSettingValue } from '../../../store/rootReducer'; | |||
interface Props { | |||
interface StateProps { | |||
onSonarCloud: boolean; | |||
} | |||
interface Props extends StateProps { | |||
organization: Organization; | |||
} | |||
export default function OrganizationNavigationMeta({ organization }: Props) { | |||
export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props) { | |||
return ( | |||
<div className="navbar-context-meta"> | |||
{organization.url != null && ( | |||
@@ -41,9 +47,23 @@ export default function OrganizationNavigationMeta({ organization }: Props) { | |||
<div className="text-muted"> | |||
<strong>{translate('organization.key')}:</strong> {organization.key} | |||
</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> | |||
); | |||
} | |||
const mapStateToProps = (state: any): StateProps => { | |||
const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); | |||
return { | |||
onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true') | |||
}; | |||
}; | |||
export default connect(mapStateToProps)(OrganizationNavigationMeta); |
@@ -19,13 +19,14 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import OrganizationNavigationMeta from '../OrganizationNavigationMeta'; | |||
import { OrganizationNavigationMeta } from '../OrganizationNavigationMeta'; | |||
import { Visibility } from '../../../../app/types'; | |||
it('renders', () => { | |||
expect( | |||
shallow( | |||
<OrganizationNavigationMeta | |||
onSonarCloud={true} | |||
organization={{ | |||
key: 'foo', | |||
name: 'Foo', |
@@ -14,7 +14,7 @@ exports[`render 1`] = ` | |||
} | |||
} | |||
/> | |||
<OrganizationNavigationMeta | |||
<Connect(OrganizationNavigationMeta) | |||
organization={ | |||
Object { | |||
"key": "foo", |
@@ -20,8 +20,8 @@ exports[`renders 1`] = ` | |||
<Connect(HomePageSelect) | |||
currentPage={ | |||
Object { | |||
"key": "foo", | |||
"type": "organization", | |||
"parameter": "foo", | |||
"type": "ORGANIZATION", | |||
} | |||
} | |||
/> |
@@ -68,10 +68,6 @@ export default class OverviewApp extends React.PureComponent { | |||
componentDidMount() { | |||
this.mounted = true; | |||
const domElement = document.querySelector('html'); | |||
if (domElement) { | |||
domElement.classList.add('dashboard-page'); | |||
} | |||
this.loadMeasures().then(this.loadHistory); | |||
} | |||
@@ -86,10 +82,6 @@ export default class OverviewApp extends React.PureComponent { | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
const domElement = document.querySelector('html'); | |||
if (domElement) { | |||
domElement.classList.remove('dashboard-page'); | |||
} | |||
} | |||
loadMeasures() { |
@@ -44,10 +44,12 @@ export default class List extends React.PureComponent { | |||
)); | |||
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> | |||
); | |||
} | |||
} |
@@ -92,14 +92,16 @@ export default class HoldersList extends React.PureComponent<Props> { | |||
)); | |||
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> | |||
); | |||
} | |||
} |
@@ -50,10 +50,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
componentDidMount() { | |||
this.mounted = true; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.add('dashboard-page'); | |||
} | |||
this.fetchData(); | |||
} | |||
@@ -65,10 +61,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.remove('dashboard-page'); | |||
} | |||
} | |||
fetchData() { |
@@ -101,7 +101,7 @@ class Key extends React.PureComponent { | |||
)} | |||
{hasModules && ( | |||
<div> | |||
<div className="boxed-group boxed-group-inner"> | |||
<div className="big-spacer-bottom"> | |||
<ul className="tabs"> | |||
<li> |
@@ -54,10 +54,12 @@ export default class Table extends React.PureComponent { | |||
)); | |||
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> | |||
); | |||
} | |||
} |
@@ -94,8 +94,6 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
componentDidMount() { | |||
this.mounted = true; | |||
const elem = document.querySelector('html'); | |||
elem && elem.classList.add('dashboard-page'); | |||
if (this.shouldRedirect()) { | |||
const newQuery = { ...this.state.query, graph: getGraph() }; | |||
if (isCustomGraph(newQuery.graph)) { | |||
@@ -129,8 +127,6 @@ export default class ProjectActivityAppContainer extends React.PureComponent { | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
const elem = document.querySelector('html'); | |||
elem && elem.classList.remove('dashboard-page'); | |||
} | |||
addCustomEvent = (analysis /*: string */, name /*: string */, category /*: ?string */) => |
@@ -119,26 +119,30 @@ export default class App extends React.PureComponent<Props, State> { | |||
{this.renderBranchLifeTime()} | |||
</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> | |||
); | |||
} |
@@ -41,69 +41,73 @@ exports[`renders sorted list of branches 1`] = ` | |||
/> | |||
</p> | |||
</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> | |||
`; |
@@ -44,16 +44,18 @@ export default function Table(props: Props) { | |||
)); | |||
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> </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> </th> | |||
</tr> | |||
</thead> | |||
<tbody>{profileRows}</tbody> | |||
</table> | |||
</div> | |||
); | |||
} |
@@ -1,32 +1,65 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
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 { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 17, | |||
@@ -35,44 +68,26 @@ exports[`renders 1`] = ` | |||
"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 { | |||
"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 { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 17, | |||
@@ -81,21 +96,10 @@ exports[`renders 1`] = ` | |||
"languageName": "js", | |||
"name": "foo-js", | |||
"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> | |||
`; |
@@ -70,11 +70,6 @@ export default class AllProjects extends React.PureComponent<Props, State> { | |||
componentDidMount() { | |||
this.mounted = true; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.add('dashboard-page'); | |||
} | |||
if (this.props.isFavorite && !isLoggedIn(this.props.currentUser)) { | |||
handleRequiredAuthentication(); | |||
return; | |||
@@ -95,11 +90,6 @@ export default class AllProjects extends React.PureComponent<Props, State> { | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.remove('dashboard-page'); | |||
} | |||
const footer = document.getElementById('footer'); | |||
if (footer) { | |||
footer.classList.remove('page-footer-with-sidebar'); |
@@ -23,7 +23,7 @@ import SearchFilterContainer from '../filters/SearchFilterContainer'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import PerspectiveSelect from './PerspectiveSelect'; | |||
import ProjectsSortingSelect from './ProjectsSortingSelect'; | |||
import { CurrentUser, isLoggedIn } from '../../../app/types'; | |||
import { CurrentUser, isLoggedIn, HomePageType } from '../../../app/types'; | |||
import HomePageSelect from '../../../components/controls/HomePageSelect'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { RawQuery } from '../../../helpers/query'; | |||
@@ -101,12 +101,12 @@ export default function PageHeader(props: Props) { | |||
)} | |||
</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> | |||
); | |||
} |
@@ -49,6 +49,7 @@ export default function ProjectCardLeak({ organization, project }: Props) { | |||
className="spacer-right" | |||
component={project.key} | |||
favorite={project.isFavorite} | |||
qualifier="TRK" | |||
/> | |||
)} | |||
<h2 className="project-card-name"> |
@@ -48,6 +48,7 @@ export default function ProjectCardOverall({ organization, project }: Props) { | |||
className="spacer-right" | |||
component={project.key} | |||
favorite={project.isFavorite} | |||
qualifier="TRK" | |||
/> | |||
)} | |||
<h2 className="project-card-name"> |
@@ -50,32 +50,34 @@ export default class Projects extends React.PureComponent<Props> { | |||
render() { | |||
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> | |||
); | |||
} | |||
} |
@@ -1,67 +1,71 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
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> | |||
`; |
@@ -37,6 +37,8 @@ export default class QualityGatesApp extends Component { | |||
componentDidMount() { | |||
this.fetchQualityGates(); | |||
// $FlowFixMe | |||
document.body.classList.add('white-page'); | |||
const footer = document.getElementById('footer'); | |||
if (footer) { | |||
footer.classList.add('page-footer-with-sidebar'); | |||
@@ -44,6 +46,8 @@ export default class QualityGatesApp extends Component { | |||
} | |||
componentWillUnmount() { | |||
// $FlowFixMe | |||
document.body.classList.remove('white-page'); | |||
const footer = document.getElementById('footer'); | |||
if (footer) { | |||
footer.classList.remove('page-footer-with-sidebar'); |
@@ -43,13 +43,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
mounted: boolean; | |||
state: State = { loading: true }; | |||
componentWillMount() { | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.add('dashboard-page'); | |||
} | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
this.loadData(); | |||
@@ -57,10 +50,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.remove('dashboard-page'); | |||
} | |||
} | |||
fetchProfiles() { |
@@ -47,10 +47,6 @@ export default class App extends React.PureComponent { | |||
state /*: State */ = { loaded: false }; | |||
componentDidMount() { | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.add('dashboard-page'); | |||
} | |||
const componentKey = this.props.component ? this.props.component.key : null; | |||
this.props.fetchSettings(componentKey).then(() => this.setState({ loaded: true })); | |||
} | |||
@@ -62,13 +58,6 @@ export default class App extends React.PureComponent { | |||
} | |||
} | |||
componentWillUnmount() { | |||
const html = document.querySelector('html'); | |||
if (html) { | |||
html.classList.remove('dashboard-page'); | |||
} | |||
} | |||
render() { | |||
if (!this.state.loaded) { | |||
return null; |
@@ -38,31 +38,33 @@ export default function UsersList({ | |||
users | |||
}: Props) { | |||
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"> </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"> </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> | |||
); | |||
} |
@@ -1,64 +1,68 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
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> | |||
`; |
@@ -120,7 +120,7 @@ export default class Action extends React.PureComponent<Props, State> { | |||
); | |||
} | |||
return <hr />; | |||
return null; | |||
} | |||
render() { | |||
@@ -130,8 +130,8 @@ export default class Action extends React.PureComponent<Props, State> { | |||
const actionKey = getActionKey(domain.path, action.key); | |||
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 | |||
to={{ pathname: '/web_api/' + actionKey }} | |||
className="spacer-right link-no-underline"> | |||
@@ -161,26 +161,28 @@ export default class Action extends React.PureComponent<Props, State> { | |||
)} | |||
</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> | |||
); | |||
} |
@@ -91,11 +91,11 @@ exports[`should display the response example 1`] = ` | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="web-api-action" | |||
className="boxed-group" | |||
id="foo/foo" | |||
> | |||
<header | |||
className="web-api-action-header" | |||
className="web-api-action-header boxed-group-header" | |||
> | |||
<Link | |||
className="spacer-right link-no-underline" | |||
@@ -118,43 +118,47 @@ exports[`should render correctly 1`] = ` | |||
</h3> | |||
</header> | |||
<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> | |||
`; |
@@ -26,10 +26,7 @@ | |||
} | |||
.web-api-domain-actions { | |||
} | |||
.web-api-action { | |||
padding-top: 30px; | |||
margin-top: calc(2 * var(--gridSize)); | |||
} | |||
.web-api-action-title { |
@@ -20,7 +20,7 @@ | |||
.source-viewer { | |||
width: 100%; | |||
min-height: 200px; | |||
border: 1px solid var(--barBorderColor); | |||
border: 1px solid var(--gray80); | |||
box-sizing: border-box; | |||
background-color: #fff; | |||
overflow-x: auto; |
@@ -1,5 +1,8 @@ | |||
.branch-status { | |||
display: flex; | |||
align-items: center; | |||
min-width: 64px; | |||
line-height: calc(2 * var(--gridSize)); | |||
text-align: right; | |||
} | |||
@@ -44,26 +44,26 @@ export default function BranchStatus({ branch, concise = false }: Props) { | |||
const indicatorColor = totalIssues > 0 ? 'red' : 'green'; | |||
return concise ? ( | |||
<ul className="list-inline branch-status"> | |||
<ul className="branch-status"> | |||
<li>{totalIssues}</li> | |||
<li className="spacer-left"> | |||
<StatusIndicator color={indicatorColor} size="small" /> | |||
</li> | |||
</ul> | |||
) : ( | |||
<ul className="list-inline branch-status"> | |||
<ul className="branch-status"> | |||
<li className="spacer-right"> | |||
<StatusIndicator color={indicatorColor} size="small" /> | |||
</li> | |||
<li> | |||
<li className="spacer-left"> | |||
{branch.status.bugs} | |||
<BugIcon /> | |||
</li> | |||
<li> | |||
<li className="spacer-left"> | |||
{branch.status.vulnerabilities} | |||
<VulnerabilityIcon /> | |||
</li> | |||
<li> | |||
<li className="spacer-left"> | |||
{branch.status.codeSmells} | |||
<CodeSmellIcon /> | |||
</li> |
@@ -16,7 +16,7 @@ exports[`renders status of long-living branches 2`] = ` | |||
exports[`renders status of short-living branches 1`] = ` | |||
<ul | |||
className="list-inline branch-status" | |||
className="branch-status" | |||
> | |||
<li | |||
className="spacer-right" | |||
@@ -26,15 +26,21 @@ exports[`renders status of short-living branches 1`] = ` | |||
size="small" | |||
/> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
0 | |||
<BugIcon /> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
0 | |||
<VulnerabilityIcon /> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
0 | |||
<CodeSmellIcon /> | |||
</li> | |||
@@ -43,7 +49,7 @@ exports[`renders status of short-living branches 1`] = ` | |||
exports[`renders status of short-living branches 2`] = ` | |||
<ul | |||
className="list-inline branch-status" | |||
className="branch-status" | |||
> | |||
<li | |||
className="spacer-right" | |||
@@ -53,15 +59,21 @@ exports[`renders status of short-living branches 2`] = ` | |||
size="small" | |||
/> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
0 | |||
<BugIcon /> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
0 | |||
<VulnerabilityIcon /> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
1 | |||
<CodeSmellIcon /> | |||
</li> | |||
@@ -70,7 +82,7 @@ exports[`renders status of short-living branches 2`] = ` | |||
exports[`renders status of short-living branches 3`] = ` | |||
<ul | |||
className="list-inline branch-status" | |||
className="branch-status" | |||
> | |||
<li | |||
className="spacer-right" | |||
@@ -80,15 +92,21 @@ exports[`renders status of short-living branches 3`] = ` | |||
size="small" | |||
/> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
7 | |||
<BugIcon /> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
6 | |||
<VulnerabilityIcon /> | |||
</li> | |||
<li> | |||
<li | |||
className="spacer-left" | |||
> | |||
3 | |||
<CodeSmellIcon /> | |||
</li> |
@@ -25,13 +25,13 @@ interface Props { | |||
className?: string; | |||
component: string; | |||
favorite: boolean; | |||
qualifier: string; | |||
} | |||
export default function Favorite({ favorite, component, ...other }: Props) { | |||
export default function Favorite({ component, ...other }: Props) { | |||
return ( | |||
<FavoriteBase | |||
{...other} | |||
favorite={favorite} | |||
addFavorite={() => addFavorite(component)} | |||
removeFavorite={() => removeFavorite(component)} | |||
/> |
@@ -23,10 +23,11 @@ import Tooltip from './Tooltip'; | |||
import FavoriteIcon from '../icons-components/FavoriteIcon'; | |||
import { translate } from '../../helpers/l10n'; | |||
interface Props { | |||
export interface Props { | |||
addFavorite: () => Promise<void>; | |||
className?: string; | |||
favorite: boolean; | |||
qualifier: string; | |||
removeFavorite: () => Promise<void>; | |||
} | |||
@@ -83,10 +84,10 @@ export default class FavoriteBase extends React.PureComponent<Props, State> { | |||
render() { | |||
const tooltip = this.state.favorite | |||
? translate('favorite.current') | |||
: translate('favorite.check'); | |||
? translate('favorite.current', this.props.qualifier) | |||
: translate('favorite.check', this.props.qualifier); | |||
return ( | |||
<Tooltip overlay={tooltip}> | |||
<Tooltip overlay={tooltip} placement="left"> | |||
<a | |||
className={classNames('display-inline-block', 'link-no-underline', this.props.className)} | |||
href="#" |
@@ -1,42 +0,0 @@ | |||
/* | |||
* 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)} | |||
/> | |||
); | |||
} | |||
} |
@@ -24,11 +24,12 @@ import Tooltip from './Tooltip'; | |||
import HomeIcon from '../icons-components/HomeIcon'; | |||
import { CurrentUser, isLoggedIn, HomePage, isSameHomePage } from '../../app/types'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { getCurrentUser } from '../../store/rootReducer'; | |||
import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer'; | |||
import { setHomePage } from '../../store/users/actions'; | |||
interface StateProps { | |||
currentUser: CurrentUser; | |||
onSonarCloud: boolean; | |||
} | |||
interface DispatchProps { | |||
@@ -48,9 +49,9 @@ class HomePageSelect extends React.PureComponent<Props> { | |||
}; | |||
render() { | |||
const { currentPage, currentUser } = this.props; | |||
const { currentPage, currentUser, onSonarCloud } = this.props; | |||
if (!isLoggedIn(currentUser)) { | |||
if (!isLoggedIn(currentUser) || !onSonarCloud) { | |||
return null; | |||
} | |||
@@ -59,7 +60,7 @@ class HomePageSelect extends React.PureComponent<Props> { | |||
const tooltip = checked ? translate('homepage.current') : translate('homepage.check'); | |||
return ( | |||
<Tooltip overlay={tooltip}> | |||
<Tooltip overlay={tooltip} placement="left"> | |||
{checked ? ( | |||
<span className={classNames('display-inline-block', this.props.className)}> | |||
<HomeIcon filled={checked} /> | |||
@@ -81,9 +82,14 @@ class HomePageSelect extends React.PureComponent<Props> { | |||
} | |||
} | |||
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 }; | |||
@@ -22,5 +22,5 @@ import { shallow } from 'enzyme'; | |||
import Favorite from '../Favorite'; | |||
it('renders', () => { | |||
expect(shallow(<Favorite component="foo" favorite={true} />)).toMatchSnapshot(); | |||
expect(shallow(<Favorite component="foo" favorite={true} qualifier="TRK" />)).toMatchSnapshot(); | |||
}); |
@@ -19,7 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import FavoriteBase from '../FavoriteBase'; | |||
import FavoriteBase, { Props } from '../FavoriteBase'; | |||
import { click } from '../../../helpers/testUtils'; | |||
it('should render favorite', () => { | |||
@@ -46,8 +46,14 @@ it('should remove favorite', () => { | |||
expect(removeFavorite).toBeCalled(); | |||
}); | |||
function renderFavoriteBase(props?: any) { | |||
function renderFavoriteBase(props: Partial<Props> = {}) { | |||
return shallow( | |||
<FavoriteBase favorite={true} addFavorite={jest.fn()} removeFavorite={jest.fn()} {...props} /> | |||
<FavoriteBase | |||
favorite={true} | |||
addFavorite={jest.fn()} | |||
qualifier="TRK" | |||
removeFavorite={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -4,6 +4,7 @@ exports[`renders 1`] = ` | |||
<FavoriteBase | |||
addFavorite={[Function]} | |||
favorite={true} | |||
qualifier="TRK" | |||
removeFavorite={[Function]} | |||
/> | |||
`; |
@@ -2,8 +2,8 @@ | |||
exports[`should render favorite 1`] = ` | |||
<Tooltip | |||
overlay="favorite.current" | |||
placement="bottom" | |||
overlay="favorite.current.TRK" | |||
placement="left" | |||
> | |||
<a | |||
className="display-inline-block link-no-underline" | |||
@@ -19,8 +19,8 @@ exports[`should render favorite 1`] = ` | |||
exports[`should render not favorite 1`] = ` | |||
<Tooltip | |||
overlay="favorite.check" | |||
placement="bottom" | |||
overlay="favorite.check.TRK" | |||
placement="left" | |||
> | |||
<a | |||
className="display-inline-block link-no-underline" |
@@ -1,6 +1,6 @@ | |||
.navbar-context, | |||
.navbar-context .navbar-inner { | |||
background-color: var(--barBackgroundColor); | |||
background-color: #fff; | |||
z-index: 420; | |||
} | |||
@@ -14,7 +14,7 @@ | |||
} | |||
.navbar-context-header { | |||
display: inline-flex; | |||
display: flex; | |||
align-items: center; | |||
height: calc(4 * var(--gridSize)); | |||
font-size: var(--bigFontSize); | |||
@@ -48,6 +48,7 @@ | |||
top: 36px; | |||
right: 0; | |||
padding: 0 20px; | |||
white-space: nowrap; | |||
} | |||
.navbar-context-description { |
@@ -21,7 +21,7 @@ import { stringify } from 'querystring'; | |||
import { omitBy, isNil } from 'lodash'; | |||
import { isShortLivingBranch } from './branches'; | |||
import { getProfilePath } from '../apps/quality-profiles/utils'; | |||
import { Branch, HomePage } from '../app/types'; | |||
import { Branch, HomePage, HomePageType } from '../app/types'; | |||
interface Query { | |||
[x: string]: string | undefined; | |||
@@ -174,13 +174,13 @@ export function getOrganizationUrl(organization: string) { | |||
export function getHomePageUrl(homepage: HomePage) { | |||
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'; | |||
case 'my-issues': | |||
case HomePageType.MyIssues: | |||
return { pathname: '/issues', query: { resolved: 'false' } }; | |||
} | |||
@@ -1318,7 +1318,7 @@ email_configuration.test.email_was_sent_to_x=Email was sent to {0} | |||
# | |||
#------------------------------------------------------------------------------ | |||
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.NewIssues=New issues | |||
notification.dispatcher.NewAlerts=New quality gate status | |||
@@ -2736,5 +2736,24 @@ homepage.check=Check to make the current page your homepage | |||
# 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. |