Browse Source

SONAR-10375 Project's homepage sidebar improvements (#3032)

tags/7.5
Stas Vilchik 6 years ago
parent
commit
e497662d7c
No account linked to committer's email address

+ 2
- 0
server/sonar-web/src/main/js/app/theme.js View File

@@ -70,6 +70,8 @@ module.exports = {

contextNavHeightRaw: 9 * grid,

pagePadding: '20px',

// different
defaultShadow: '0 6px 12px rgba(0, 0, 0, 0.175)',


+ 2
- 2
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx View File

@@ -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>
);
}
}

+ 4
- 2
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap View File

@@ -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`] = `

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

@@ -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}

+ 3
- 1
server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx View File

@@ -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}

+ 33
- 34
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx View File

@@ -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 &&

server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.js → server/sonar-web/src/main/js/apps/overview/meta/MetaKey.tsx View File

@@ -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;
}

+ 2
- 0
server/sonar-web/src/main/js/apps/overview/meta/MetaLinks.tsx View File

@@ -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>

server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js → server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.tsx View File

@@ -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;
}

server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.js → server/sonar-web/src/main/js/apps/overview/meta/MetaQualityGate.tsx View File

@@ -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;
}

server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js → server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.tsx View File

@@ -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);

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx View File

@@ -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()}

+ 2
- 2
server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx View File

@@ -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"

+ 5
- 5
server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap View File

@@ -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}

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

@@ -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 {

+ 13
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -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

Loading…
Cancel
Save