*/
import * as React from 'react';
import { Helmet } from 'react-helmet';
+import { connect } from 'react-redux';
import * as PropTypes from 'prop-types';
-import { keyBy } from 'lodash';
import * as key from 'keymaster';
+import { keyBy } from 'lodash';
import BulkChange from './BulkChange';
import FacetsList from './FacetsList';
import PageActions from './PageActions';
Activation,
getOpen
} from '../query';
-import { searchRules, getRulesApp } from '../../../api/rules';
-import { Paging, Rule, RuleActivation } from '../../../app/types';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
-import { translate } from '../../../helpers/l10n';
-import { RawQuery } from '../../../helpers/query';
import ListFooter from '../../../components/controls/ListFooter';
import FiltersHeader from '../../../components/common/FiltersHeader';
import SearchBox from '../../../components/controls/SearchBox';
+import { searchRules, getRulesApp } from '../../../api/rules';
import { searchQualityProfiles, Profile } from '../../../api/quality-profiles';
+import { getCurrentUser, getMyOrganizations } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
+import { RawQuery } from '../../../helpers/query';
import { scrollToElement } from '../../../helpers/scrolling';
+import { Paging, Rule, RuleActivation, Organization, CurrentUser } from '../../../app/types';
import '../../../components/search-navigator.css';
import '../styles.css';
+import { hasPrivateAccess } from '../../../helpers/organizations';
const PAGE_SIZE = 100;
const LIMIT_BEFORE_LOAD_MORE = 5;
-interface Props {
+interface StateToProps {
+ currentUser: CurrentUser;
+ userOrganizations: Organization[];
+}
+
+interface OwnProps {
location: { pathname: string; query: RawQuery };
- organization?: { key: string };
+ organization: Organization | undefined;
}
+type Props = OwnProps & StateToProps;
+
interface State {
actives?: Actives;
canWrite?: boolean;
// TODO redirect to default organization's rules page
-export default class App extends React.PureComponent<Props, State> {
+export class App extends React.PureComponent<Props, State> {
mounted = false;
static contextTypes = {
componentDidMount() {
this.mounted = true;
- // $FlowFixMe
document.body.classList.add('white-page');
- // $FlowFixMe
document.documentElement.classList.add('white-page');
const footer = document.getElementById('footer');
if (footer) {
}
componentWillReceiveProps(nextProps: Props) {
- const openRule = this.getOpenRule(nextProps, this.state.rules);
- if (openRule && openRule.key !== this.state.selected) {
- this.setState({ selected: openRule.key });
- }
- this.setState({ openRule, query: parseQuery(nextProps.location.query) });
+ this.setState(({ rules, selected }) => {
+ const openRule = this.getOpenRule(nextProps, rules);
+ return {
+ openRule,
+ query: parseQuery(nextProps.location.query),
+ selected: openRule ? openRule.key : selected
+ };
+ });
}
componentDidUpdate(prevProps: Props, prevState: State) {
fetchInitialData = () => {
this.setState({ loading: true });
const organization = this.props.organization && this.props.organization.key;
- Promise.all([getRulesApp({ organization }), searchQualityProfiles({ organization })]).then(
+ Promise.all([getRulesApp({ organization }), this.fetchQualityProfiles()]).then(
([{ canWrite, repositories }, { profiles }]) => {
this.setState({
canWrite,
}, this.stopLoading);
};
+ fetchQualityProfiles = () => {
+ const { currentUser, organization, userOrganizations } = this.props;
+ if (hasPrivateAccess(currentUser, organization, userOrganizations)) {
+ return searchQualityProfiles({ organization: organization && organization.key });
+ }
+ return { profiles: [] };
+ };
+
getSelectedIndex = ({ selected, rules } = this.state) => {
const index = rules.findIndex(rule => rule.key === selected);
return index !== -1 ? index : undefined;
const { paging, rules } = this.state;
const selectedIndex = this.getSelectedIndex();
const organization = this.props.organization && this.props.organization.key;
-
+ const hideQualityProfiles = !hasPrivateAccess(
+ this.props.currentUser,
+ this.props.organization,
+ this.props.userOrganizations
+ );
return (
<>
<Suggestions suggestions="coding_rules" />
/>
<FacetsList
facets={this.state.facets}
+ hideProfileFacet={hideQualityProfiles}
onFacetToggle={this.handleFacetToggle}
onFilterChange={this.handleFilterChange}
openFacets={this.state.openFacets}
<div className="layout-page-header-panel-inner layout-page-main-header-inner">
<div className="layout-page-main-inner">
{this.state.openRule ? (
- <a href="#" className="js-back" onClick={this.handleBack}>
+ <a className="js-back" href="#" onClick={this.handleBack}>
{translate('coding_rules.return_to_list')}
</a>
) : (
<RuleDetails
allowCustomRules={!this.context.organizationsEnabled}
canWrite={this.state.canWrite}
+ hideQualityProfiles={hideQualityProfiles}
onActivate={this.handleRuleActivate}
onDeactivate={this.handleRuleDeactivate}
onDelete={this.handleRuleDelete}
}
return facets;
}
+
+const mapStateToProps = (state: any) => ({
+ currentUser: getCurrentUser(state),
+ userOrganizations: getMyOrganizations(state)
+});
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(App);
interface Props {
facets?: Facets;
+ hideProfileFacet?: boolean;
onFacetToggle: (facet: FacetKey) => void;
onFilterChange: (changes: Partial<Query>) => void;
openFacets: OpenFacets;
props.query.compareToProfile !== undefined ||
props.selectedProfile === undefined ||
!props.query.activation;
-
return (
<div className="search-navigator-facets-list">
<LanguageFacet
<TagFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
- organization={props.organization}
open={!!props.openFacets.tags}
+ organization={props.organization}
stats={props.facets && props.facets.tags}
values={props.query.tags}
/>
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
open={!!props.openFacets.repositories}
- stats={props.facets && props.facets.repositories}
referencedRepositories={props.referencedRepositories}
+ stats={props.facets && props.facets.repositories}
values={props.query.repositories}
/>
<DefaultSeverityFacet
value={props.query.template}
/>
)}
- <ProfileFacet
- activation={props.query.activation}
- compareToProfile={props.query.compareToProfile}
- languages={props.query.languages}
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.profile}
- referencedProfiles={props.referencedProfiles}
- value={props.query.profile}
- />
- <InheritanceFacet
- disabled={inheritanceDisabled}
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.inheritance}
- value={props.query.inheritance}
- />
- <ActivationSeverityFacet
- disabled={activationSeverityDisabled}
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.activationSeverities}
- stats={props.facets && props.facets.activationSeverities}
- values={props.query.activationSeverities}
- />
+ {!props.hideProfileFacet && (
+ <>
+ <ProfileFacet
+ activation={props.query.activation}
+ compareToProfile={props.query.compareToProfile}
+ languages={props.query.languages}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.profile}
+ referencedProfiles={props.referencedProfiles}
+ value={props.query.profile}
+ />
+ <InheritanceFacet
+ disabled={inheritanceDisabled}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.inheritance}
+ value={props.query.inheritance}
+ />
+ <ActivationSeverityFacet
+ disabled={activationSeverityDisabled}
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.activationSeverities}
+ stats={props.facets && props.facets.activationSeverities}
+ values={props.query.activationSeverities}
+ />
+ </>
+ )}
</div>
);
}
interface Props {
allowCustomRules?: boolean;
canWrite?: boolean;
+ hideQualityProfiles?: boolean;
onActivate: (profile: string, rule: string, activation: Activation) => void;
onDeactivate: (profile: string, rule: string) => void;
onDelete: (rule: string) => void;
return <div className="coding-rule-details" />;
}
- const { allowCustomRules, canWrite, organization, referencedProfiles } = this.props;
+ const {
+ allowCustomRules,
+ canWrite,
+ hideQualityProfiles,
+ organization,
+ referencedProfiles
+ } = this.props;
const { params = [] } = ruleDetails;
const isCustom = !!ruleDetails.templateKey;
/>
)}
- {!ruleDetails.isTemplate && (
- <RuleDetailsProfiles
- activations={this.state.actives}
- canWrite={canWrite}
- onActivate={this.handleActivate}
- onDeactivate={this.handleDeactivate}
- organization={organization}
- referencedProfiles={referencedProfiles}
- ruleDetails={ruleDetails}
- />
- )}
+ {!ruleDetails.isTemplate &&
+ !hideQualityProfiles && (
+ <RuleDetailsProfiles
+ activations={this.state.actives}
+ canWrite={canWrite}
+ onActivate={this.handleActivate}
+ onDeactivate={this.handleDeactivate}
+ organization={organization}
+ referencedProfiles={referencedProfiles}
+ ruleDetails={ruleDetails}
+ />
+ )}
{!ruleDetails.isTemplate && (
<RuleDetailsIssues organization={organization} ruleKey={ruleDetails.key} />
interface Props {
canWrite: boolean | undefined;
- hidePermalink?: boolean;
hideSimilarRulesFilter?: boolean;
onFilterChange: (changes: Partial<Query>) => void;
onTagsChange: (tags: string[]) => void;
};
render() {
- const { hidePermalink, ruleDetails } = this.props;
+ const { ruleDetails } = this.props;
const hasTypeData = !ruleDetails.isExternal || ruleDetails.type !== 'UNKNOWN';
return (
<div className="js-rule-meta">
<header className="page-header">
<div className="pull-right">
<span className="note text-middle">{ruleDetails.key}</span>
- {!ruleDetails.isExternal &&
- !hidePermalink && (
- <Link
- className="coding-rules-detail-permalink link-no-underline spacer-left text-middle"
- title={translate('permalink')}
- to={getRuleUrl(ruleDetails.key, this.props.organization)}>
- <LinkIcon />
- </Link>
- )}
+ {!ruleDetails.isExternal && (
+ <Link
+ className="coding-rules-detail-permalink link-no-underline spacer-left text-middle"
+ title={translate('permalink')}
+ to={getRuleUrl(ruleDetails.key, this.props.organization)}>
+ <LinkIcon />
+ </Link>
+ )}
{!this.props.hideSimilarRulesFilter && (
<SimilarRulesFilter onFilterChange={this.props.onFilterChange} rule={ruleDetails} />
)}
expect(onTagsChange).toBeCalledWith(['foo', 'bar']);
});
-it('should not display rule permalink', () => {
- expect(
- getWrapper({ hidePermalink: true })
- .find('.coding-rules-detail-permalink')
- .exists()
- ).toBeFalsy();
-});
-
function getWrapper(props = {}) {
return shallow(
<RuleDetailsMeta
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
- organization: { key: string } | undefined;
+ organization: string | undefined;
stats: { [x: string]: number } | undefined;
referencedUsers: { [login: string]: ReferencedUser };
}
};
handleSearch = (query: string) => {
- let organization = this.props.component && this.props.component.organization;
- if (this.props.organization && !organization) {
- organization = this.props.organization.key;
- }
- return searchAssignees(query, organization);
+ return searchAssignees(query, this.props.organization);
};
handleSelect = (option: { value: string }) => {
const displayFilesFacet = component !== undefined;
const displayAuthorFacet = !component || component.qualifier !== 'DEV';
+ const organizationKey =
+ (component && component.organization) ||
+ (this.props.organization && this.props.organization.key);
+
return (
<div className="search-navigator-facets-list">
<FacetMode facetMode={query.facetMode} onChange={this.props.onFilterChange} />
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.rules}
- organization={this.props.organization && this.props.organization.key}
+ organization={organizationKey}
referencedRules={this.props.referencedRules}
rules={query.rules}
stats={facets.rules}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.tags}
- organization={this.props.organization}
+ organization={organizationKey}
stats={facets.tags}
tags={query.tags}
/>
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.assignees}
- organization={this.props.organization}
+ organization={organizationKey}
referencedUsers={this.props.referencedUsers}
stats={facets.assignees}
/>
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
- organization: { key: string } | undefined;
+ organization: string | undefined;
stats: { [x: string]: number } | undefined;
tags: string[];
}
};
handleSearch = (query: string) => {
- let organization = this.props.component && this.props.component.organization;
- if (this.props.organization && !organization) {
- organization = this.props.organization.key;
- }
- return searchIssueTags({ organization, ps: 50, q: query }).then(tags =>
+ return searchIssueTags({ organization: this.props.organization, ps: 50, q: query }).then(tags =>
tags.map(tag => ({ label: tag, value: tag }))
);
};
import * as React from 'react';
import { connect } from 'react-redux';
import { RouterState } from 'react-router';
-import { getOrganizationByKey, getCurrentUser } from '../../../store/rootReducer';
+import {
+ getCurrentUser,
+ getMyOrganizations,
+ getOrganizationByKey
+} from '../../../store/rootReducer';
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization';
import { Organization, CurrentUser, isLoggedIn } from '../../../app/types';
import { isCurrentUserMemberOf, hasPrivateAccess } from '../../../helpers/organizations';
-import { getMyOrganizations } from '../../../store/organizations/duck';
+import {} from '../../../store/organizations/duck';
interface StateToProps {
currentUser: CurrentUser;
organization,
userOrganizations
}: Props) {
+ const hasPrivateRights = hasPrivateAccess(currentUser, organization, userOrganizations);
return (
<NavBarTabs className="navbar-context-tabs">
<li>
{translate('issues.page')}
</Link>
</li>
- {hasPrivateAccess(currentUser, organization, userOrganizations) && (
- <>
- <li>
- <Link
- activeClassName="active"
- to={`/organizations/${organization.key}/quality_profiles`}>
- {translate('quality_profiles.page')}
- </Link>
- </li>
- <li>
- <Link activeClassName="active" to={`/organizations/${organization.key}/rules`}>
- {translate('coding_rules.page')}
- </Link>
- </li>
- <li>
- <Link activeClassName="active" to={getQualityGatesUrl(organization.key)}>
- {translate('quality_gates.page')}
- </Link>
- </li>
- </>
+ {hasPrivateRights && (
+ <li>
+ <Link activeClassName="active" to={`/organizations/${organization.key}/quality_profiles`}>
+ {translate('quality_profiles.page')}
+ </Link>
+ </li>
+ )}
+ <li>
+ <Link activeClassName="active" to={`/organizations/${organization.key}/rules`}>
+ {translate('coding_rules.page')}
+ </Link>
+ </li>
+ {hasPrivateRights && (
+ <li>
+ <Link activeClassName="active" to={getQualityGatesUrl(organization.key)}>
+ {translate('quality_gates.page')}
+ </Link>
+ </li>
)}
-
{isCurrentUserMemberOf(currentUser, organization, userOrganizations) && (
<li>
<Link activeClassName="active" to={`/organizations/${organization.key}/members`}>
</Link>
</li>
)}
-
<OrganizationNavigationExtensions location={location} organization={organization} />
{organization.canAdmin && (
<OrganizationNavigationAdministration location={location} organization={organization} />
issues.page
</Link>
</li>
- <React.Fragment>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
}
- >
- quality_gates.page
- </Link>
- </li>
- </React.Fragment>
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
issues.page
</Link>
</li>
- <React.Fragment>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
}
- >
- quality_gates.page
- </Link>
- </li>
- </React.Fragment>
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
<li>
<Link
activeClassName="active"
{ indexRoute: { component: lazyLoad(() => import('../issues/components/AppContainer')) } }
]
},
+ {
+ path: 'rules',
+ component: OrganizationContainer,
+ childRoutes: codingRulesRoutes
+ },
{
component: lazyLoad(() =>
import('./components/OrganizationAccessContainer').then(lib => ({
}))
),
childRoutes: [
- {
- path: 'rules',
- component: OrganizationContainer,
- childRoutes: codingRulesRoutes
- },
{
path: 'quality_profiles',
childRoutes: qualityProfilesRoutes
import CodeSmells from '../main/CodeSmells';
import Coverage from '../main/Coverage';
import Duplications from '../main/Duplications';
-import Meta from '../meta/Meta';
+import MetaContainer from '../meta/MetaContainer';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import { getMeasuresAndMeta } from '../../../api/measures';
{this.renderMain()}
<div className="overview-sidebar page-sidebar-fixed">
- <Meta
+ <MetaContainer
branchLike={branchLike}
component={component}
history={history}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 * as React from 'react';
-import * as PropTypes from 'prop-types';
-import MetaKey from './MetaKey';
-import MetaOrganizationKey from './MetaOrganizationKey';
-import MetaLinks from './MetaLinks';
-import MetaQualityGate from './MetaQualityGate';
-import MetaQualityProfiles from './MetaQualityProfiles';
-import MetaSize from './MetaSize';
-import MetaTags from './MetaTags';
-import BadgesModal from '../badges/BadgesModal';
-import AnalysesList from '../events/AnalysesList';
-import { Visibility, Component, Metric, BranchLike } from '../../../app/types';
-import { History } from '../../../api/time-machine';
-import { translate } from '../../../helpers/l10n';
-import { MeasureEnhanced } from '../../../helpers/measures';
-import { hasPrivateAccess } from '../../../helpers/organizations';
-
-interface Props {
- branchLike?: BranchLike;
- component: Component;
- history?: History;
- measures: MeasureEnhanced[];
- metrics: { [key: string]: Metric };
- onComponentChange: (changes: {}) => void;
-}
-
-export default class Meta extends React.PureComponent<Props> {
- static contextTypes = {
- organizationsEnabled: PropTypes.bool
- };
-
- renderQualityInfos() {
- const { organizationsEnabled } = this.context;
- const { organization, qualifier, qualityProfiles, qualityGate } = this.props.component;
- const isProject = qualifier === 'TRK';
-
- if (!isProject || (organizationsEnabled && !hasPrivateAccess(organization))) {
- return null;
- }
-
- return (
- <div className="overview-meta-card">
- {qualityGate && (
- <MetaQualityGate
- organization={organizationsEnabled ? organization : undefined}
- qualityGate={qualityGate}
- />
- )}
-
- {qualityProfiles &&
- qualityProfiles.length > 0 && (
- <MetaQualityProfiles
- headerClassName={qualityGate ? 'big-spacer-top' : undefined}
- organization={organizationsEnabled ? organization : undefined}
- profiles={qualityProfiles}
- />
- )}
- </div>
- );
- }
-
- render() {
- const { organizationsEnabled } = this.context;
- const { branchLike, component, metrics } = this.props;
- const { qualifier, description, visibility } = component;
-
- const isProject = qualifier === 'TRK';
- const isApp = qualifier === 'APP';
- const isPrivate = visibility === Visibility.Private;
-
- return (
- <div className="overview-meta">
- <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 branchLike={branchLike} component={component} measures={this.props.measures} />
- </div>
-
- <AnalysesList
- branchLike={branchLike}
- component={component}
- history={this.props.history}
- metrics={metrics}
- qualifier={component.qualifier}
- />
-
- {this.renderQualityInfos()}
-
- {isProject && <MetaLinks component={component} />}
-
- <div className="overview-meta-card">
- <MetaKey componentKey={component.key} qualifier={component.qualifier} />
- {organizationsEnabled && <MetaOrganizationKey organization={component.organization} />}
- </div>
-
- {!isPrivate &&
- (isProject || isApp) && (
- <BadgesModal
- branchLike={branchLike}
- metrics={metrics}
- project={component.key}
- qualifier={component.qualifier}
- />
- )}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import * as PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import MetaKey from './MetaKey';
+import MetaOrganizationKey from './MetaOrganizationKey';
+import MetaLinks from './MetaLinks';
+import MetaQualityGate from './MetaQualityGate';
+import MetaQualityProfiles from './MetaQualityProfiles';
+import MetaSize from './MetaSize';
+import MetaTags from './MetaTags';
+import BadgesModal from '../badges/BadgesModal';
+import AnalysesList from '../events/AnalysesList';
+import {
+ Visibility,
+ Component,
+ Metric,
+ BranchLike,
+ CurrentUser,
+ Organization
+} from '../../../app/types';
+import { History } from '../../../api/time-machine';
+import { translate } from '../../../helpers/l10n';
+import { MeasureEnhanced } from '../../../helpers/measures';
+import { hasPrivateAccess } from '../../../helpers/organizations';
+import {
+ getCurrentUser,
+ getMyOrganizations,
+ getOrganizationByKey
+} from '../../../store/rootReducer';
+
+interface StateToProps {
+ currentUser: CurrentUser;
+ organization?: Organization;
+ userOrganizations: Organization[];
+}
+
+interface OwnProps {
+ branchLike?: BranchLike;
+ component: Component;
+ history?: History;
+ measures: MeasureEnhanced[];
+ metrics: { [key: string]: Metric };
+ onComponentChange: (changes: {}) => void;
+}
+
+type Props = OwnProps & StateToProps;
+
+export class Meta extends React.PureComponent<Props> {
+ static contextTypes = {
+ organizationsEnabled: PropTypes.bool
+ };
+
+ renderQualityInfos() {
+ const { organizationsEnabled } = this.context;
+ const { component, currentUser, organization, userOrganizations } = this.props;
+ const { qualifier, qualityProfiles, qualityGate } = component;
+ const isProject = qualifier === 'TRK';
+
+ if (
+ !isProject ||
+ (organizationsEnabled && !hasPrivateAccess(currentUser, organization, userOrganizations))
+ ) {
+ return null;
+ }
+
+ return (
+ <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>
+ );
+ }
+
+ render() {
+ const { organizationsEnabled } = this.context;
+ const { branchLike, component, metrics } = this.props;
+ const { qualifier, description, visibility } = component;
+
+ const isProject = qualifier === 'TRK';
+ const isApp = qualifier === 'APP';
+ const isPrivate = visibility === Visibility.Private;
+
+ return (
+ <div className="overview-meta">
+ <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 branchLike={branchLike} component={component} measures={this.props.measures} />
+ </div>
+
+ <AnalysesList
+ branchLike={branchLike}
+ component={component}
+ history={this.props.history}
+ metrics={metrics}
+ qualifier={component.qualifier}
+ />
+
+ {this.renderQualityInfos()}
+
+ {isProject && <MetaLinks component={component} />}
+
+ <div className="overview-meta-card">
+ <MetaKey componentKey={component.key} qualifier={component.qualifier} />
+ {organizationsEnabled && <MetaOrganizationKey organization={component.organization} />}
+ </div>
+
+ {!isPrivate &&
+ (isProject || isApp) && (
+ <BadgesModal
+ branchLike={branchLike}
+ metrics={metrics}
+ project={component.key}
+ qualifier={component.qualifier}
+ />
+ )}
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = (state: any, { component }: OwnProps) => ({
+ currentUser: getCurrentUser(state),
+ organization: getOrganizationByKey(state, component.organization),
+ userOrganizations: getMyOrganizations(state)
+});
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(Meta);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
-import React from 'react';
-import MarkdownTips from '../MarkdownTips';
-
-it('should render the tips', () => {
- expect(shallow(<MarkdownTips />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import MarkdownTips from '../MarkdownTips';
+
+it('should render the tips', () => {
+ expect(shallow(<MarkdownTips />)).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render the tips 1`] = `
-<div
- className="markdown-tips"
->
- <a
- className="little-spacer-right"
- href="#"
- onClick={[Function]}
- >
- markdown.helplink
- </a>
- :
- <span
- className="spacer-left"
- >
- *
- bold
- *
- </span>
- <span
- className="spacer-left"
- >
- \`\`
- code
- \`\`
- </span>
- <span
- className="spacer-left"
- >
- *
- bulleted_point
- </span>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render the tips 1`] = `
+<div
+ className="markdown-tips"
+>
+ <a
+ className="little-spacer-right"
+ href="#"
+ onClick={[Function]}
+ >
+ markdown.helplink
+ </a>
+ :
+ <span
+ className="spacer-left"
+ >
+ *
+ bold
+ *
+ </span>
+ <span
+ className="spacer-left"
+ >
+ \`\`
+ code
+ \`\`
+ </span>
+ <span
+ className="spacer-left"
+ >
+ *
+ bulleted_point
+ </span>
+</div>
+`;
*/
import * as React from 'react';
import { keyBy } from 'lodash';
-import { getRuleDetails, getRulesApp } from '../../api/rules';
-import { RuleDetails } from '../../app/types';
import DeferredSpinner from '../common/DeferredSpinner';
import RuleDetailsMeta from '../../apps/coding-rules/components/RuleDetailsMeta';
import RuleDetailsDescription from '../../apps/coding-rules/components/RuleDetailsDescription';
+import { getRuleDetails, getRulesApp } from '../../api/rules';
+import { RuleDetails } from '../../app/types';
import '../../apps/coding-rules/styles.css';
-import { hasPrivateAccess } from '../../helpers/organizations';
interface Props {
onLoad: (details: { name: string }) => void;
<>
<RuleDetailsMeta
canWrite={false}
- hidePermalink={!hasPrivateAccess(organizationKey)}
hideSimilarRulesFilter={true}
onFilterChange={this.noOp}
onTagsChange={this.noOp}
import { shallow } from 'enzyme';
import WorkspaceRuleDetails from '../WorkspaceRuleDetails';
import { waitAndUpdate } from '../../../helpers/testUtils';
-import { OrganizationSubscription, Visibility } from '../../../app/types';
-import { hasPrivateAccess } from '../../../helpers/organizations';
-
-jest.mock('../../../helpers/organizations', () => ({
- hasPrivateAccess: jest.fn().mockReturnValue(true)
-}));
jest.mock('../../../api/rules', () => ({
getRulesApp: jest.fn(() =>
getRuleDetails: jest.fn(() => Promise.resolve({ rule: { key: 'foo', name: 'Foo' } }))
}));
-const organization = {
- key: 'foo',
- name: 'Foo',
- projectVisibility: Visibility.Public,
- subscription: OrganizationSubscription.Paid
-};
-
-beforeEach(() => {
- (hasPrivateAccess as jest.Mock<any>).mockClear();
-});
-
it('should render', async () => {
const wrapper = shallow(
<WorkspaceRuleDetails onLoad={jest.fn()} organizationKey={undefined} ruleKey="foo" />
await waitAndUpdate(wrapper);
expect(onLoad).toBeCalledWith({ name: 'Foo' });
});
-
-it('should render without permalink', async () => {
- (hasPrivateAccess as jest.Mock<any>).mockReturnValueOnce(false);
- const wrapper = shallow(
- <WorkspaceRuleDetails onLoad={jest.fn()} organizationKey={organization.key} ruleKey="foo" />
- );
-
- await waitAndUpdate(wrapper);
- expect(wrapper.find('RuleDetailsMeta').prop('hidePermalink')).toBeTruthy();
-});
<React.Fragment>
<RuleDetailsMeta
canWrite={false}
- hidePermalink={false}
hideSimilarRulesFilter={true}
onFilterChange={[Function]}
onTagsChange={[Function]}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { hasPrivateAccess, isCurrentUserMemberOf } from '../organizations';
-import { getCurrentUser, getMyOrganizations } from '../../store/rootReducer';
import { OrganizationSubscription } from '../../app/types';
-jest.mock('../../app/utils/getStore', () => ({
- default: () => ({
- getState: jest.fn()
- })
-}));
+const org = { key: 'foo', name: 'Foo', subscription: OrganizationSubscription.Paid };
+const adminOrg = { key: 'bar', name: 'Bar', canAdmin: true };
+const randomOrg = { key: 'bar', name: 'Bar' };
-jest.mock('../../store/rootReducer', () => ({
- getCurrentUser: jest.fn().mockReturnValue({
- isLoggedIn: true,
- login: 'luke',
- name: 'Skywalker',
- showOnboardingTutorial: false
- }),
- getMyOrganizations: jest.fn().mockReturnValue([])
-}));
-
-const organization = {
- key: 'foo',
- name: 'Foo',
- subscription: OrganizationSubscription.Paid
+const loggedIn = {
+ isLoggedIn: true,
+ login: 'luke',
+ name: 'Skywalker'
};
-
const loggedOut = { isLoggedIn: false };
-beforeEach(() => {
- (getCurrentUser as jest.Mock<any>).mockClear();
- (getMyOrganizations as jest.Mock<any>).mockClear();
-});
-
describe('isCurrentUserMemberOf', () => {
it('should be a member', () => {
- expect(isCurrentUserMemberOf({ key: 'bar', name: 'Bar', canAdmin: true })).toBeTruthy();
-
- (getMyOrganizations as jest.Mock<any>).mockReturnValueOnce([organization]);
- expect(isCurrentUserMemberOf(organization)).toBeTruthy();
+ expect(isCurrentUserMemberOf(loggedIn, adminOrg, [])).toBeTruthy();
+ expect(isCurrentUserMemberOf(loggedIn, org, [org])).toBeTruthy();
});
it('should not be a member', () => {
- expect(isCurrentUserMemberOf(undefined)).toBeFalsy();
- expect(isCurrentUserMemberOf(organization)).toBeFalsy();
-
- (getMyOrganizations as jest.Mock<any>).mockReturnValueOnce([{ key: 'bar', name: 'Bar' }]);
- expect(isCurrentUserMemberOf(organization)).toBeFalsy();
-
- (getCurrentUser as jest.Mock<any>).mockReturnValueOnce(loggedOut);
- expect(isCurrentUserMemberOf(organization)).toBeFalsy();
+ expect(isCurrentUserMemberOf(loggedIn, undefined, [])).toBeFalsy();
+ expect(isCurrentUserMemberOf(loggedIn, org, [])).toBeFalsy();
+ expect(isCurrentUserMemberOf(loggedIn, org, [randomOrg])).toBeFalsy();
+ expect(isCurrentUserMemberOf(loggedOut, org, [org])).toBeFalsy();
});
});
describe('hasPrivateAccess', () => {
it('should have access', () => {
- expect(hasPrivateAccess({ key: 'bar', name: 'Bar' })).toBeTruthy();
-
- (getMyOrganizations as jest.Mock<any>).mockReturnValueOnce([organization]);
- expect(hasPrivateAccess(organization)).toBeTruthy();
+ expect(hasPrivateAccess(loggedIn, randomOrg, [])).toBeTruthy();
+ expect(hasPrivateAccess(loggedIn, org, [org])).toBeTruthy();
});
it('should not have access', () => {
- expect(hasPrivateAccess(organization)).toBeFalsy();
+ expect(hasPrivateAccess(loggedIn, org, [])).toBeFalsy();
});
});
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Organization, isLoggedIn, OrganizationSubscription, CurrentUser } from '../app/types';
-import getStore from '../app/utils/getStore';
-import { Organization, isLoggedIn, OrganizationSubscription } from '../app/types';
-import { getCurrentUser, getMyOrganizations, getOrganizationByKey } from '../store/rootReducer';
-
-function getRealOrganization(
- organization?: Organization | string,
- state?: any
-): Organization | undefined {
- if (typeof organization === 'string') {
- state = state || getStore().getState();
- return getOrganizationByKey(state, organization);
- }
-
- return organization;
-}
-
-function isPaidOrganization(organization: Organization | undefined): boolean {
+export function isPaidOrganization(organization: Organization | undefined): boolean {
return Boolean(organization && organization.subscription === OrganizationSubscription.Paid);
}
-export function hasPrivateAccess(organization: Organization | string | undefined): boolean {
- const realOrg = getRealOrganization(organization);
- return !isPaidOrganization(realOrg) || isCurrentUserMemberOf(realOrg);
+export function hasPrivateAccess(
+ currentUser: CurrentUser,
+ organization: Organization | undefined,
+ userOrganizations: Organization[]
+): boolean {
+ return (
+ !isPaidOrganization(organization) ||
+ isCurrentUserMemberOf(currentUser, organization, userOrganizations)
+ );
}
-export function isCurrentUserMemberOf(organization: Organization | string | undefined): boolean {
- const state = getStore().getState();
- const currentUser = getCurrentUser(state);
- const userOrganizations = getMyOrganizations(state);
- const realOrg = getRealOrganization(organization, state);
+export function isCurrentUserMemberOf(
+ currentUser: CurrentUser,
+ organization: Organization | undefined,
+ userOrganizations: Organization[]
+): boolean {
return Boolean(
- realOrg &&
+ organization &&
isLoggedIn(currentUser) &&
- (realOrg.canAdmin || userOrganizations.some(org => org.key === realOrg.key))
+ (organization.canAdmin || userOrganizations.some(org => org.key === organization.key))
);
}