Просмотр исходного кода

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

tags/7.5
Stas Vilchik 6 лет назад
Родитель
Сommit
e497662d7c
Аккаунт пользователя с таким Email не найден

+ 2
- 0
server/sonar-web/src/main/js/app/theme.js Просмотреть файл



contextNavHeightRaw: 9 * grid, contextNavHeightRaw: 9 * grid,


pagePadding: '20px',

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



+ 2
- 2
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx Просмотреть файл

const header = translate('overview.badges.title'); const header = translate('overview.badges.title');
const fullBadgeOptions = { branch, project, ...badgeOptions }; const fullBadgeOptions = { branch, project, ...badgeOptions };
return ( return (
<>
<div className="overview-meta-card">
<button className="js-project-badges" onClick={this.handleOpen}> <button className="js-project-badges" onClick={this.handleOpen}>
{translate('overview.badges.get_badge')} {translate('overview.badges.get_badge')}
</button> </button>
</footer> </footer>
</Modal> </Modal>
)} )}
</>
</div>
); );
} }
} }

+ 4
- 2
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap Просмотреть файл

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


exports[`should display the modal after click 1`] = ` exports[`should display the modal after click 1`] = `
<React.Fragment>
<div
className="overview-meta-card"
>
<button <button
className="js-project-badges" className="js-project-badges"
onClick={[Function]} onClick={[Function]}
> >
overview.badges.get_badge overview.badges.get_badge
</button> </button>
</React.Fragment>
</div>
`; `;


exports[`should display the modal after click 2`] = ` exports[`should display the modal after click 2`] = `

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx Просмотреть файл

</div> </div>
</div> </div>


<div className="page-sidebar-fixed">
<div className="overview-sidebar page-sidebar-fixed">
<Meta <Meta
branch={branchName} branch={branchName}
component={component} component={component}

+ 3
- 1
server/sonar-web/src/main/js/apps/overview/events/AnalysesList.tsx Просмотреть файл



return ( return (
<div className="overview-meta-card"> <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 <PreviewGraph
branch={this.props.branch} branch={this.props.branch}

+ 33
- 34
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx Просмотреть файл

import AnalysesList from '../events/AnalysesList'; import AnalysesList from '../events/AnalysesList';
import { Visibility, Component, Metric } from '../../../app/types'; import { Visibility, Component, Metric } from '../../../app/types';
import { History } from '../../../api/time-machine'; import { History } from '../../../api/time-machine';
import { translate } from '../../../helpers/l10n';
import { MeasureEnhanced } from '../../../helpers/measures'; import { MeasureEnhanced } from '../../../helpers/measures';


interface Props { interface Props {
const isProject = qualifier === 'TRK'; const isProject = qualifier === 'TRK';
const isPrivate = visibility === Visibility.Private; 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 ( return (
<div className="overview-meta"> <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 <AnalysesList
branch={branch} branch={branch}
qualifier={component.qualifier} 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} />} {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 && {onSonarCloud &&
isProject && isProject &&

server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.js → server/sonar-web/src/main/js/apps/overview/meta/MetaKey.tsx Просмотреть файл

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import React from 'react';
import * as React from 'react';
import ClipboardButton from '../../../components/controls/ClipboardButton';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';


const MetaOrganizationKey = ({ component }) => {
interface Props {
componentKey: string;
qualifier: string;
}

export default function MetaKey({ componentKey, qualifier }: Props) {
return ( 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 Просмотреть файл

import { getProjectLinks, ProjectLink } from '../../../api/projectLinks'; import { getProjectLinks, ProjectLink } from '../../../api/projectLinks';
import { orderLinks } from '../../project-admin/links/utils'; import { orderLinks } from '../../project-admin/links/utils';
import { LightComponent } from '../../../app/types'; import { LightComponent } from '../../../app/types';
import { translate } from '../../../helpers/l10n';


interface Props { interface Props {
component: LightComponent; component: LightComponent;


return ( return (
<div className="overview-meta-card"> <div className="overview-meta-card">
<h4 className="overview-meta-header">{translate('overview.external_links')}</h4>
<ul className="overview-meta-list"> <ul className="overview-meta-list">
{orderedLinks.map(link => <MetaLink key={link.id} link={link} />)} {orderedLinks.map(link => <MetaLink key={link.id} link={link} />)}
</ul> </ul>

server/sonar-web/src/main/js/apps/overview/meta/MetaKey.js → server/sonar-web/src/main/js/apps/overview/meta/MetaOrganizationKey.tsx Просмотреть файл

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import React from 'react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n'; 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 ( 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 Просмотреть файл

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { getQualityGateUrl } from '../../../helpers/urls'; 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 ( return (
<div className="overview-meta-card">
<>
<h4 className="overview-meta-header">{translate('overview.quality_gate')}</h4> <h4 className="overview-meta-header">{translate('overview.quality_gate')}</h4>


<ul className="overview-meta-list"> <ul className="overview-meta-list">
<li> <li>
{gate.isDefault && (
{qualityGate.isDefault && (
<span className="note spacer-right">{'(' + translate('default') + ')'}</span> <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> </li>
</ul> </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 Просмотреть файл

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
// @flow
import React from 'react';
import * as React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import * as classNames from 'classnames';
import Tooltip from '../../../components/controls/Tooltip'; import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n'; import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getQualityProfileUrl } from '../../../helpers/urls'; import { getQualityProfileUrl } from '../../../helpers/urls';
import { searchRules } from '../../../api/rules'; import { searchRules } from '../../../api/rules';
import { getLanguages } from '../../../store/rootReducer'; 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() { componentDidMount() {
this.mounted = true; this.mounted = true;
Promise.all(requests).then( Promise.all(requests).then(
responses => { responses => {
if (this.mounted) { if (this.mounted) {
const deprecatedByKey = {};
const deprecatedByKey: { [key: string]: number } = {};
responses.forEach((count, i) => { responses.forEach((count, i) => {
const profileKey = this.props.profiles[i].key; const profileKey = this.props.profiles[i].key;
deprecatedByKey[profileKey] = count; deprecatedByKey[profileKey] = count;
); );
} }


loadDeprecatedRulesForProfile(profileKey) {
loadDeprecatedRulesForProfile(profileKey: string) {
const data = { const data = {
activation: 'true', activation: 'true',
organization: this.props.organization, organization: this.props.organization,
return searchRules(data).then(r => r.total); return searchRules(data).then(r => r.total);
} }


getDeprecatedRulesCount(profile) {
getDeprecatedRulesCount(profile: { key: string }) {
const count = this.state.deprecatedByKey[profile.key]; const count = this.state.deprecatedByKey[profile.key];
return count || 0; return count || 0;
} }


renderProfile(profile) {
renderProfile(profile: { key: string; language: string; name: string }) {
const languageFromStore = this.props.languages[profile.language]; const languageFromStore = this.props.languages[profile.language];
const languageName = languageFromStore ? languageFromStore.name : 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 = ( const inner = (
<div className="text-ellipsis"> <div className="text-ellipsis">
} }


render() { render() {
const { profiles } = this.props;
const { headerClassName, profiles } = this.props;


return ( 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"> <ul className="overview-meta-list">
{profiles.map(profile => this.renderProfile(profile))} {profiles.map(profile => this.renderProfile(profile))}
</ul> </ul>
</div>
</>
); );
} }
} }


const mapStateToProps = state => ({
const mapStateToProps = (state: any) => ({
languages: getLanguages(state) 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 Просмотреть файл

} }


return ( 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.props.component.qualifier === 'APP' && this.renderProjects()}
{this.renderLoC(ncloc)} {this.renderLoC(ncloc)}
{this.renderLoCDistribution()} {this.renderLoCDistribution()}

+ 2
- 2
server/sonar-web/src/main/js/apps/overview/meta/MetaTags.tsx Просмотреть файл



if (this.canUpdateTags()) { if (this.canUpdateTags()) {
return ( 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 <button
className="button-link" className="button-link"
onClick={this.handleClick} onClick={this.handleClick}
); );
} else { } else {
return ( return (
<div className="overview-meta-card overview-meta-tags">
<div className="big-spacer-top overview-meta-tags">
<TagsList <TagsList
allowUpdate={false} allowUpdate={false}
className="note" className="note"

+ 5
- 5
server/sonar-web/src/main/js/apps/overview/meta/__tests__/__snapshots__/MetaTags-test.tsx.snap Просмотреть файл



exports[`should open the tag selector on click 1`] = ` exports[`should open the tag selector on click 1`] = `
<div <div
className="overview-meta-card overview-meta-tags"
className="big-spacer-top overview-meta-tags"
> >
<button <button
className="button-link" className="button-link"


exports[`should open the tag selector on click 2`] = ` exports[`should open the tag selector on click 2`] = `
<div <div
className="overview-meta-card overview-meta-tags"
className="big-spacer-top overview-meta-tags"
> >
<button <button
className="button-link" className="button-link"


exports[`should open the tag selector on click 3`] = ` exports[`should open the tag selector on click 3`] = `
<div <div
className="overview-meta-card overview-meta-tags"
className="big-spacer-top overview-meta-tags"
> >
<button <button
className="button-link" className="button-link"


exports[`should render with tags and admin rights 1`] = ` exports[`should render with tags and admin rights 1`] = `
<div <div
className="overview-meta-card overview-meta-tags"
className="big-spacer-top overview-meta-tags"
> >
<button <button
className="button-link" className="button-link"


exports[`should render without tags and admin rights 1`] = ` exports[`should render without tags and admin rights 1`] = `
<div <div
className="overview-meta-card overview-meta-tags"
className="big-spacer-top overview-meta-tags"
> >
<TagsList <TagsList
allowUpdate={false} allowUpdate={false}

+ 20
- 4
server/sonar-web/src/main/js/apps/overview/styles.css Просмотреть файл

transition: transform 0.5s ease, opacity 0.5s ease; 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 * Title
*/ */


.overview-meta-card { .overview-meta-card {
min-width: 200px; min-width: 200px;
padding-bottom: 20px;
box-sizing: border-box; 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 { .overview-meta-description {
margin-top: calc(-0.5 * var(--gridSize));
line-height: 1.5; line-height: 1.5;
color: var(--secondFontColor);
} }


.overview-meta-header { .overview-meta-header {
margin-bottom: calc(0.5 * var(--gridSize));
color: var(--baseFontColor); color: var(--baseFontColor);
} }


} }


.overview-analysis + .overview-analysis { .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 { .overview-analysis-graph {

+ 13
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Просмотреть файл

overview.started_on_x=Started on {0} overview.started_on_x=Started on {0}
overview.last_analysis_on_x=Last analysis on {0} overview.last_analysis_on_x=Last analysis on {0}
overview.on_new_code=On New Code 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.code_smells=Code Smells
overview.metric.new_code_smells=New Code Smells overview.metric.new_code_smells=New Code Smells

Загрузка…
Отмена
Сохранить