@@ -70,6 +70,8 @@ module.exports = { | |||
contextNavHeightRaw: 9 * grid, | |||
pagePadding: '20px', | |||
// different | |||
defaultShadow: '0 6px 12px rgba(0, 0, 0, 0.175)', | |||
@@ -65,7 +65,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> { | |||
const header = translate('overview.badges.title'); | |||
const fullBadgeOptions = { branch, project, ...badgeOptions }; | |||
return ( | |||
<> | |||
<div className="overview-meta-card"> | |||
<button className="js-project-badges" onClick={this.handleOpen}> | |||
{translate('overview.badges.get_badge')} | |||
</button> | |||
@@ -106,7 +106,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> { | |||
</footer> | |||
</Modal> | |||
)} | |||
</> | |||
</div> | |||
); | |||
} | |||
} |
@@ -1,14 +1,16 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display the modal after click 1`] = ` | |||
<React.Fragment> | |||
<div | |||
className="overview-meta-card" | |||
> | |||
<button | |||
className="js-project-badges" | |||
onClick={[Function]} | |||
> | |||
overview.badges.get_badge | |||
</button> | |||
</React.Fragment> | |||
</div> | |||
`; | |||
exports[`should display the modal after click 2`] = ` |
@@ -191,7 +191,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
</div> | |||
</div> | |||
<div className="page-sidebar-fixed"> | |||
<div className="overview-sidebar page-sidebar-fixed"> | |||
<Meta | |||
branch={branchName} | |||
component={component} |
@@ -116,7 +116,9 @@ export default class AnalysesList extends React.PureComponent<Props, State> { | |||
return ( | |||
<div className="overview-meta-card"> | |||
<h4 className="overview-meta-header">{translate('project_activity.page')}</h4> | |||
<h4 className="overview-meta-header"> | |||
{translate('overview.project_activity', this.props.component.qualifier)} | |||
</h4> | |||
<PreviewGraph | |||
branch={this.props.branch} |
@@ -30,6 +30,7 @@ import BadgesModal from '../badges/BadgesModal'; | |||
import AnalysesList from '../events/AnalysesList'; | |||
import { Visibility, Component, Metric } from '../../../app/types'; | |||
import { History } from '../../../api/time-machine'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { MeasureEnhanced } from '../../../helpers/measures'; | |||
interface Props { | |||
@@ -55,25 +56,18 @@ export default class Meta extends React.PureComponent<Props> { | |||
const isProject = qualifier === 'TRK'; | |||
const isPrivate = visibility === Visibility.Private; | |||
const hasDescription = !!description; | |||
const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0; | |||
const hasQualityGate = !!qualityGate; | |||
const shouldShowQualityProfiles = isProject && hasQualityProfiles; | |||
const shouldShowQualityGate = isProject && hasQualityGate; | |||
const hasOrganization = component.organization != null && organizationsEnabled; | |||
return ( | |||
<div className="overview-meta"> | |||
{hasDescription && ( | |||
<div className="overview-meta-card overview-meta-description">{description}</div> | |||
)} | |||
<MetaSize branch={branch} component={component} measures={this.props.measures} /> | |||
{isProject && ( | |||
<MetaTags component={component} onComponentChange={this.props.onComponentChange} /> | |||
)} | |||
<div className="overview-meta-card"> | |||
<h4 className="overview-meta-header"> | |||
{translate('overview.about_this_project', qualifier)} | |||
</h4> | |||
{description !== undefined && <p className="overview-meta-description">{description}</p>} | |||
{isProject && ( | |||
<MetaTags component={component} onComponentChange={this.props.onComponentChange} /> | |||
)} | |||
<MetaSize branch={branch} component={component} measures={this.props.measures} /> | |||
</div> | |||
<AnalysesList | |||
branch={branch} | |||
@@ -83,27 +77,32 @@ export default class Meta extends React.PureComponent<Props> { | |||
qualifier={component.qualifier} | |||
/> | |||
{shouldShowQualityGate && ( | |||
<MetaQualityGate | |||
gate={qualityGate} | |||
organization={hasOrganization && component.organization} | |||
/> | |||
)} | |||
{shouldShowQualityProfiles && ( | |||
<MetaQualityProfiles | |||
component={component} | |||
customOrganizations={organizationsEnabled} | |||
organization={component.organization} | |||
profiles={qualityProfiles} | |||
/> | |||
{isProject && ( | |||
<div className="overview-meta-card"> | |||
{qualityGate && ( | |||
<MetaQualityGate | |||
organization={organizationsEnabled ? component.organization : undefined} | |||
qualityGate={qualityGate} | |||
/> | |||
)} | |||
{qualityProfiles && | |||
qualityProfiles.length > 0 && ( | |||
<MetaQualityProfiles | |||
headerClassName={qualityGate ? 'big-spacer-top' : undefined} | |||
organization={organizationsEnabled ? component.organization : undefined} | |||
profiles={qualityProfiles} | |||
/> | |||
)} | |||
</div> | |||
)} | |||
{isProject && <MetaLinks component={component} />} | |||
<MetaKey component={component} /> | |||
{hasOrganization && <MetaOrganizationKey component={component} />} | |||
<div className="overview-meta-card"> | |||
<MetaKey componentKey={component.key} qualifier={component.qualifier} /> | |||
{organizationsEnabled && <MetaOrganizationKey organization={component.organization} />} | |||
</div> | |||
{onSonarCloud && | |||
isProject && |
@@ -17,22 +17,23 @@ | |||
* 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 * as React from 'react'; | |||
import ClipboardButton from '../../../components/controls/ClipboardButton'; | |||
import { translate } from '../../../helpers/l10n'; | |||
const MetaOrganizationKey = ({ component }) => { | |||
interface Props { | |||
componentKey: string; | |||
qualifier: string; | |||
} | |||
export default function MetaKey({ componentKey, qualifier }: Props) { | |||
return ( | |||
<div className="overview-meta-card"> | |||
<h4 className="overview-meta-header">{translate('organization_key')}</h4> | |||
<input | |||
className="overview-key" | |||
type="text" | |||
value={component.organization} | |||
readOnly={true} | |||
onClick={e => e.target.select()} | |||
/> | |||
</div> | |||
<> | |||
<h4 className="overview-meta-header">{translate('overview.project_key', qualifier)}</h4> | |||
<div className="display-flex-center"> | |||
<input className="overview-key" type="text" value={componentKey} readOnly={true} /> | |||
<ClipboardButton className="little-spacer-left" copyValue={componentKey} /> | |||
</div> | |||
</> | |||
); | |||
}; | |||
export default MetaOrganizationKey; | |||
} |
@@ -22,6 +22,7 @@ import MetaLink from './MetaLink'; | |||
import { getProjectLinks, ProjectLink } from '../../../api/projectLinks'; | |||
import { orderLinks } from '../../project-admin/links/utils'; | |||
import { LightComponent } from '../../../app/types'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
component: LightComponent; | |||
@@ -71,6 +72,7 @@ export default class MetaLinks extends React.PureComponent<Props, State> { | |||
return ( | |||
<div className="overview-meta-card"> | |||
<h4 className="overview-meta-header">{translate('overview.external_links')}</h4> | |||
<ul className="overview-meta-list"> | |||
{orderedLinks.map(link => <MetaLink key={link.id} link={link} />)} | |||
</ul> |
@@ -17,22 +17,22 @@ | |||
* 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 * as React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import ClipboardButton from '../../../components/controls/ClipboardButton'; | |||
const MetaKey = ({ component }) => { | |||
interface Props { | |||
organization: string; | |||
} | |||
export default function MetaOrganizationKey({ organization }: Props) { | |||
return ( | |||
<div className="overview-meta-card"> | |||
<h4 className="overview-meta-header">{translate('key')}</h4> | |||
<input | |||
className="overview-key" | |||
type="text" | |||
value={component.key} | |||
readOnly={true} | |||
onClick={e => e.target.select()} | |||
/> | |||
</div> | |||
<> | |||
<h4 className="overview-meta-header big-spacer-top">{translate('organization_key')}</h4> | |||
<div className="display-flex-center"> | |||
<input className="overview-key" type="text" value={organization} readOnly={true} /> | |||
<ClipboardButton className="little-spacer-left" copyValue={organization} /> | |||
</div> | |||
</> | |||
); | |||
}; | |||
export default MetaKey; | |||
} |
@@ -17,26 +17,29 @@ | |||
* 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 * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getQualityGateUrl } from '../../../helpers/urls'; | |||
const MetaQualityGate = ({ gate, organization }) => { | |||
interface Props { | |||
organization?: string; | |||
qualityGate: { isDefault?: boolean; key: string; name: string }; | |||
} | |||
export default function MetaQualityGate({ qualityGate, organization }: Props) { | |||
return ( | |||
<div className="overview-meta-card"> | |||
<> | |||
<h4 className="overview-meta-header">{translate('overview.quality_gate')}</h4> | |||
<ul className="overview-meta-list"> | |||
<li> | |||
{gate.isDefault && ( | |||
{qualityGate.isDefault && ( | |||
<span className="note spacer-right">{'(' + translate('default') + ')'}</span> | |||
)} | |||
<Link to={getQualityGateUrl(gate.key, organization)}>{gate.name}</Link> | |||
<Link to={getQualityGateUrl(qualityGate.key, organization)}>{qualityGate.name}</Link> | |||
</li> | |||
</ul> | |||
</div> | |||
</> | |||
); | |||
}; | |||
export default MetaQualityGate; | |||
} |
@@ -17,31 +17,33 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import { Link } from 'react-router'; | |||
import * as classNames from 'classnames'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getQualityProfileUrl } from '../../../helpers/urls'; | |||
import { searchRules } from '../../../api/rules'; | |||
import { getLanguages } from '../../../store/rootReducer'; | |||
class MetaQualityProfiles extends React.PureComponent { | |||
/*:: mounted: boolean; */ | |||
interface StateProps { | |||
languages: { [key: string]: { name: string } }; | |||
} | |||
/*:: props: { | |||
component: { organization: string }, | |||
customOrganizations: boolean, | |||
languages: { [string]: { name: string } }, | |||
organization: string | void; | |||
profiles: Array<{ key: string, language: string, name: string }> | |||
}; | |||
*/ | |||
interface OwnProps { | |||
headerClassName?: string; | |||
organization?: string; | |||
profiles: { key: string; language: string; name: string }[]; | |||
} | |||
state = { | |||
deprecatedByKey: {} | |||
}; | |||
interface State { | |||
deprecatedByKey: { [key: string]: number }; | |||
} | |||
class MetaQualityProfiles extends React.PureComponent<StateProps & OwnProps, State> { | |||
mounted: boolean; | |||
state: State = { deprecatedByKey: {} }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
@@ -59,7 +61,7 @@ class MetaQualityProfiles extends React.PureComponent { | |||
Promise.all(requests).then( | |||
responses => { | |||
if (this.mounted) { | |||
const deprecatedByKey = {}; | |||
const deprecatedByKey: { [key: string]: number } = {}; | |||
responses.forEach((count, i) => { | |||
const profileKey = this.props.profiles[i].key; | |||
deprecatedByKey[profileKey] = count; | |||
@@ -71,7 +73,7 @@ class MetaQualityProfiles extends React.PureComponent { | |||
); | |||
} | |||
loadDeprecatedRulesForProfile(profileKey) { | |||
loadDeprecatedRulesForProfile(profileKey: string) { | |||
const data = { | |||
activation: 'true', | |||
organization: this.props.organization, | |||
@@ -82,18 +84,16 @@ class MetaQualityProfiles extends React.PureComponent { | |||
return searchRules(data).then(r => r.total); | |||
} | |||
getDeprecatedRulesCount(profile) { | |||
getDeprecatedRulesCount(profile: { key: string }) { | |||
const count = this.state.deprecatedByKey[profile.key]; | |||
return count || 0; | |||
} | |||
renderProfile(profile) { | |||
renderProfile(profile: { key: string; language: string; name: string }) { | |||
const languageFromStore = this.props.languages[profile.language]; | |||
const languageName = languageFromStore ? languageFromStore.name : profile.language; | |||
const path = this.props.customOrganizations | |||
? getQualityProfileUrl(profile.name, profile.language, this.props.component.organization) | |||
: getQualityProfileUrl(profile.name, profile.language); | |||
const path = getQualityProfileUrl(profile.name, profile.language, this.props.organization); | |||
const inner = ( | |||
<div className="text-ellipsis"> | |||
@@ -117,22 +117,24 @@ class MetaQualityProfiles extends React.PureComponent { | |||
} | |||
render() { | |||
const { profiles } = this.props; | |||
const { headerClassName, profiles } = this.props; | |||
return ( | |||
<div className="overview-meta-card"> | |||
<h4 className="overview-meta-header">{translate('overview.quality_profiles')}</h4> | |||
<> | |||
<h4 className={classNames('overview-meta-header', headerClassName)}> | |||
{translate('overview.quality_profiles')} | |||
</h4> | |||
<ul className="overview-meta-list"> | |||
{profiles.map(profile => this.renderProfile(profile))} | |||
</ul> | |||
</div> | |||
</> | |||
); | |||
} | |||
} | |||
const mapStateToProps = state => ({ | |||
const mapStateToProps = (state: any) => ({ | |||
languages: getLanguages(state) | |||
}); | |||
export default connect(mapStateToProps)(MetaQualityProfiles); | |||
export default connect<StateProps, {}, OwnProps>(mapStateToProps)(MetaQualityProfiles); |
@@ -89,7 +89,7 @@ export default class MetaSize extends React.PureComponent<Props> { | |||
} | |||
return ( | |||
<div id="overview-size" className="overview-meta-card"> | |||
<div className="big-spacer-top" id="overview-size"> | |||
{this.props.component.qualifier === 'APP' && this.renderProjects()} | |||
{this.renderLoC(ncloc)} | |||
{this.renderLoCDistribution()} |
@@ -99,7 +99,7 @@ export default class MetaTags extends React.PureComponent<Props, State> { | |||
if (this.canUpdateTags()) { | |||
return ( | |||
<div className="overview-meta-card overview-meta-tags" ref={card => (this.card = card)}> | |||
<div className="big-spacer-top overview-meta-tags" ref={card => (this.card = card)}> | |||
<button | |||
className="button-link" | |||
onClick={this.handleClick} | |||
@@ -120,7 +120,7 @@ export default class MetaTags extends React.PureComponent<Props, State> { | |||
); | |||
} else { | |||
return ( | |||
<div className="overview-meta-card overview-meta-tags"> | |||
<div className="big-spacer-top overview-meta-tags"> | |||
<TagsList | |||
allowUpdate={false} | |||
className="note" |
@@ -2,7 +2,7 @@ | |||
exports[`should open the tag selector on click 1`] = ` | |||
<div | |||
className="overview-meta-card overview-meta-tags" | |||
className="big-spacer-top overview-meta-tags" | |||
> | |||
<button | |||
className="button-link" | |||
@@ -23,7 +23,7 @@ exports[`should open the tag selector on click 1`] = ` | |||
exports[`should open the tag selector on click 2`] = ` | |||
<div | |||
className="overview-meta-card overview-meta-tags" | |||
className="big-spacer-top overview-meta-tags" | |||
> | |||
<button | |||
className="button-link" | |||
@@ -62,7 +62,7 @@ exports[`should open the tag selector on click 2`] = ` | |||
exports[`should open the tag selector on click 3`] = ` | |||
<div | |||
className="overview-meta-card overview-meta-tags" | |||
className="big-spacer-top overview-meta-tags" | |||
> | |||
<button | |||
className="button-link" | |||
@@ -83,7 +83,7 @@ exports[`should open the tag selector on click 3`] = ` | |||
exports[`should render with tags and admin rights 1`] = ` | |||
<div | |||
className="overview-meta-card overview-meta-tags" | |||
className="big-spacer-top overview-meta-tags" | |||
> | |||
<button | |||
className="button-link" | |||
@@ -104,7 +104,7 @@ exports[`should render with tags and admin rights 1`] = ` | |||
exports[`should render without tags and admin rights 1`] = ` | |||
<div | |||
className="overview-meta-card overview-meta-tags" | |||
className="big-spacer-top overview-meta-tags" | |||
> | |||
<TagsList | |||
allowUpdate={false} |
@@ -26,6 +26,16 @@ | |||
transition: transform 0.5s ease, opacity 0.5s ease; | |||
} | |||
.overview-sidebar { | |||
margin-top: calc(-1 * var(--pagePadding)); | |||
margin-bottom: calc(-1 * var(--pagePadding)); | |||
margin-left: var(--pagePadding); | |||
padding-left: calc(var(--pagePadding) - 1px); | |||
padding-top: var(--pagePadding); | |||
padding-bottom: var(--pagePadding); | |||
border-left: 1px solid var(--barBorderColor); | |||
} | |||
/* | |||
* Title | |||
*/ | |||
@@ -308,15 +318,23 @@ | |||
.overview-meta-card { | |||
min-width: 200px; | |||
padding-bottom: 20px; | |||
box-sizing: border-box; | |||
} | |||
.overview-meta-card + .overview-meta-card { | |||
margin-top: calc(2 * var(--gridSize)); | |||
padding-top: calc(2 * var(--gridSize) - 1px); | |||
border-top: 1px solid var(--barBorderColor); | |||
} | |||
.overview-meta-description { | |||
margin-top: calc(-0.5 * var(--gridSize)); | |||
line-height: 1.5; | |||
color: var(--secondFontColor); | |||
} | |||
.overview-meta-header { | |||
margin-bottom: calc(0.5 * var(--gridSize)); | |||
color: var(--baseFontColor); | |||
} | |||
@@ -365,9 +383,7 @@ | |||
} | |||
.overview-analysis + .overview-analysis { | |||
margin-top: 8px; | |||
padding-top: 8px; | |||
border-top: 1px solid var(--barBorderColor); | |||
margin-top: calc(2 * var(--gridSize)); | |||
} | |||
.overview-analysis-graph { |
@@ -2287,6 +2287,19 @@ overview.last_analysis_x=last analysis {0} | |||
overview.started_on_x=Started on {0} | |||
overview.last_analysis_on_x=Last analysis on {0} | |||
overview.on_new_code=On New Code | |||
overview.about_this_project.APP=About This Application | |||
overview.about_this_project.TRK=About This Project | |||
overview.about_this_project.BRC=About This Sub-Project | |||
overview.about_this_project.DIR=About This Directory | |||
overview.project_activity.APP=Application Activity | |||
overview.project_activity.TRK=Project Activity | |||
overview.project_activity.BRC=Sub-Project Activity | |||
overview.project_activity.DIR=Directory Activity | |||
overview.external_links=External Links | |||
overview.project_key.APP=Application Key | |||
overview.project_key.TRK=Project Key | |||
overview.project_key.BRC=Sub-Project Key | |||
overview.project_key.DIR=Directory Key | |||
overview.metric.code_smells=Code Smells | |||
overview.metric.new_code_smells=New Code Smells |