aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx68
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx60
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap136
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/routes.ts10
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx (renamed from server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx)47
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.tsx (renamed from server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.js)2
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.tsx.snap (renamed from server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.js.snap)0
-rw-r--r--server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx27
-rw-r--r--server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/organizations-test.ts59
-rw-r--r--server/sonar-web/src/main/js/helpers/organizations.ts45
21 files changed, 297 insertions, 303 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
index 04a60d87c0a..19f179aca95 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
@@ -19,9 +19,10 @@
*/
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';
@@ -43,27 +44,36 @@ import {
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;
@@ -81,7 +91,7 @@ interface State {
// 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 = {
@@ -103,9 +113,7 @@ export default class App extends React.PureComponent<Props, State> {
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) {
@@ -116,11 +124,14 @@ export default class App extends React.PureComponent<Props, State> {
}
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) {
@@ -219,7 +230,7 @@ export default class App extends React.PureComponent<Props, 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,
@@ -283,6 +294,14 @@ export default class App extends React.PureComponent<Props, State> {
}, 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;
@@ -464,7 +483,11 @@ export default class App extends React.PureComponent<Props, State> {
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" />
@@ -488,6 +511,7 @@ export default class App extends React.PureComponent<Props, State> {
/>
<FacetsList
facets={this.state.facets}
+ hideProfileFacet={hideQualityProfiles}
onFacetToggle={this.handleFacetToggle}
onFilterChange={this.handleFilterChange}
openFacets={this.state.openFacets}
@@ -509,7 +533,7 @@ export default class App extends React.PureComponent<Props, State> {
<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>
) : (
@@ -537,6 +561,7 @@ export default class App extends React.PureComponent<Props, State> {
<RuleDetails
allowCustomRules={!this.context.organizationsEnabled}
canWrite={this.state.canWrite}
+ hideQualityProfiles={hideQualityProfiles}
onActivate={this.handleRuleActivate}
onDeactivate={this.handleRuleDeactivate}
onDelete={this.handleRuleDelete}
@@ -603,3 +628,10 @@ function parseFacets(rawFacets: { property: string; values: { count: number; val
}
return facets;
}
+
+const mapStateToProps = (state: any) => ({
+ currentUser: getCurrentUser(state),
+ userOrganizations: getMyOrganizations(state)
+});
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
index e384fcb6e16..9eb8edb79ed 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
@@ -34,6 +34,7 @@ import { Profile } from '../../../api/quality-profiles';
interface Props {
facets?: Facets;
+ hideProfileFacet?: boolean;
onFacetToggle: (facet: FacetKey) => void;
onFilterChange: (changes: Partial<Query>) => void;
openFacets: OpenFacets;
@@ -55,7 +56,6 @@ export default function FacetsList(props: Props) {
props.query.compareToProfile !== undefined ||
props.selectedProfile === undefined ||
!props.query.activation;
-
return (
<div className="search-navigator-facets-list">
<LanguageFacet
@@ -75,8 +75,8 @@ export default function FacetsList(props: Props) {
<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}
/>
@@ -84,8 +84,8 @@ export default function FacetsList(props: Props) {
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
@@ -116,31 +116,35 @@ export default function FacetsList(props: Props) {
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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
index 21481a1f240..4521cc0d810 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
@@ -38,6 +38,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
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;
@@ -148,7 +149,13 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
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;
@@ -225,17 +232,18 @@ export default class RuleDetails extends React.PureComponent<Props, State> {
/>
)}
- {!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} />
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
index def6cd3f6d5..110956c04fb 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
@@ -39,7 +39,6 @@ import { PopupPlacement } from '../../../components/ui/popups';
interface Props {
canWrite: boolean | undefined;
- hidePermalink?: boolean;
hideSimilarRulesFilter?: boolean;
onFilterChange: (changes: Partial<Query>) => void;
onTagsChange: (tags: string[]) => void;
@@ -231,22 +230,21 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> {
};
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} />
)}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx
index fd496d8f03e..1380e6c0bfc 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx
@@ -85,14 +85,6 @@ it('should edit tags', () => {
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
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx
index b4896bafea6..26d0204509e 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx
@@ -38,7 +38,7 @@ export interface Props {
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 };
}
@@ -77,11 +77,7 @@ export default class AssigneeFacet extends React.PureComponent<Props> {
};
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 }) => {
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
index c850ffa0750..77611af9140 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
@@ -63,6 +63,10 @@ export default class Sidebar extends React.PureComponent<Props> {
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} />
@@ -124,7 +128,7 @@ export default class Sidebar extends React.PureComponent<Props> {
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}
@@ -136,7 +140,7 @@ export default class Sidebar extends React.PureComponent<Props> {
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.tags}
- organization={this.props.organization}
+ organization={organizationKey}
stats={facets.tags}
tags={query.tags}
/>
@@ -200,7 +204,7 @@ export default class Sidebar extends React.PureComponent<Props> {
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
open={!!openFacets.assignees}
- organization={this.props.organization}
+ organization={organizationKey}
referencedUsers={this.props.referencedUsers}
stats={facets.assignees}
/>
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx
index dab0d43d298..91e30bf9de5 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx
@@ -38,7 +38,7 @@ interface Props {
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[];
}
@@ -74,11 +74,7 @@ export default class TagFacet extends React.PureComponent<Props> {
};
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 }))
);
};
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx
index 2b5e9f301d4..4cef029f3df 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx
@@ -20,11 +20,15 @@
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;
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx
index 1fda2012f13..07ee53cd586 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx
@@ -47,6 +47,7 @@ export function OrganizationNavigationMenu({
organization,
userOrganizations
}: Props) {
+ const hasPrivateRights = hasPrivateAccess(currentUser, organization, userOrganizations);
return (
<NavBarTabs className="navbar-context-tabs">
<li>
@@ -64,28 +65,25 @@ export function OrganizationNavigationMenu({
{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`}>
@@ -93,7 +91,6 @@ export function OrganizationNavigationMenu({
</Link>
</li>
)}
-
<OrganizationNavigationExtensions location={location} organization={organization} />
{organization.canAdmin && (
<OrganizationNavigationAdministration location={location} organization={organization} />
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap
index a51bf8cc513..1177b7604b7 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap
@@ -31,42 +31,40 @@ exports[`renders 1`] = `
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"
@@ -125,42 +123,40 @@ exports[`renders for admin 1`] = `
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"
diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.ts b/server/sonar-web/src/main/js/apps/organizations/routes.ts
index 18aa7c3f4ba..24cd7999dcd 100644
--- a/server/sonar-web/src/main/js/apps/organizations/routes.ts
+++ b/server/sonar-web/src/main/js/apps/organizations/routes.ts
@@ -54,6 +54,11 @@ const routes = [
]
},
{
+ path: 'rules',
+ component: OrganizationContainer,
+ childRoutes: codingRulesRoutes
+ },
+ {
component: lazyLoad(() =>
import('./components/OrganizationAccessContainer').then(lib => ({
default: lib.OrganizationMembersAccess
@@ -74,11 +79,6 @@ const routes = [
),
childRoutes: [
{
- path: 'rules',
- component: OrganizationContainer,
- childRoutes: codingRulesRoutes
- },
- {
path: 'quality_profiles',
childRoutes: qualityProfilesRoutes
},
diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
index df32445c412..402725bdc71 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
@@ -27,7 +27,7 @@ import BugsAndVulnerabilities from '../main/BugsAndVulnerabilities';
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';
@@ -257,7 +257,7 @@ export class OverviewApp extends React.PureComponent<Props, State> {
{this.renderMain()}
<div className="overview-sidebar page-sidebar-fixed">
- <Meta
+ <MetaContainer
branchLike={branchLike}
component={component}
history={history}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
index 5eb22f4735d..62ee5647128 100644
--- a/server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
@@ -19,6 +19,7 @@
*/
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';
@@ -28,13 +29,31 @@ 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 {
+ 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 Props {
+interface StateToProps {
+ currentUser: CurrentUser;
+ organization?: Organization;
+ userOrganizations: Organization[];
+}
+
+interface OwnProps {
branchLike?: BranchLike;
component: Component;
history?: History;
@@ -43,17 +62,23 @@ interface Props {
onComponentChange: (changes: {}) => void;
}
-export default class Meta extends React.PureComponent<Props> {
+type Props = OwnProps & StateToProps;
+
+export 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 { component, currentUser, organization, userOrganizations } = this.props;
+ const { qualifier, qualityProfiles, qualityGate } = component;
const isProject = qualifier === 'TRK';
- if (!isProject || (organizationsEnabled && !hasPrivateAccess(organization))) {
+ if (
+ !isProject ||
+ (organizationsEnabled && !hasPrivateAccess(currentUser, organization, userOrganizations))
+ ) {
return null;
}
@@ -61,7 +86,7 @@ export default class Meta extends React.PureComponent<Props> {
<div className="overview-meta-card">
{qualityGate && (
<MetaQualityGate
- organization={organizationsEnabled ? organization : undefined}
+ organization={organizationsEnabled ? component.organization : undefined}
qualityGate={qualityGate}
/>
)}
@@ -70,7 +95,7 @@ export default class Meta extends React.PureComponent<Props> {
qualityProfiles.length > 0 && (
<MetaQualityProfiles
headerClassName={qualityGate ? 'big-spacer-top' : undefined}
- organization={organizationsEnabled ? organization : undefined}
+ organization={organizationsEnabled ? component.organization : undefined}
profiles={qualityProfiles}
/>
)}
@@ -130,3 +155,11 @@ export default class Meta extends React.PureComponent<Props> {
);
}
}
+
+const mapStateToProps = (state: any, { component }: OwnProps) => ({
+ currentUser: getCurrentUser(state),
+ organization: getOrganizationByKey(state, component.organization),
+ userOrganizations: getMyOrganizations(state)
+});
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(Meta);
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.js b/server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.tsx
index 48da22f6eb0..5b0dfe18c18 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.js
+++ b/server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.tsx
@@ -17,8 +17,8 @@
* 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 React from 'react';
import MarkdownTips from '../MarkdownTips';
it('should render the tips', () => {
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.js.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.tsx.snap
index baeceb6aa90..baeceb6aa90 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.js.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.tsx.snap
diff --git a/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx
index 647f4a4fade..a1a62dcb3e5 100644
--- a/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx
+++ b/server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx
@@ -19,13 +19,12 @@
*/
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;
@@ -96,7 +95,6 @@ export default class WorkspaceRuleDetails extends React.PureComponent<Props, Sta
<>
<RuleDetailsMeta
canWrite={false}
- hidePermalink={!hasPrivateAccess(organizationKey)}
hideSimilarRulesFilter={true}
onFilterChange={this.noOp}
onTagsChange={this.noOp}
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx
index e755c5686d5..13ff053fcc3 100644
--- a/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx
+++ b/server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx
@@ -21,12 +21,6 @@ import * as React from 'react';
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(() =>
@@ -35,17 +29,6 @@ jest.mock('../../../api/rules', () => ({
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" />
@@ -64,13 +47,3 @@ it('should call back on load', async () => {
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();
-});
diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap
index 566b4a80db2..3b316292cb5 100644
--- a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap
@@ -15,7 +15,6 @@ exports[`should render 2`] = `
<React.Fragment>
<RuleDetailsMeta
canWrite={false}
- hidePermalink={false}
hideSimilarRulesFilter={true}
onFilterChange={[Function]}
onTagsChange={[Function]}
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/organizations-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/organizations-test.ts
index 17429129955..a483c995c6c 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/organizations-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/organizations-test.ts
@@ -18,67 +18,40 @@
* 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();
});
});
diff --git a/server/sonar-web/src/main/js/helpers/organizations.ts b/server/sonar-web/src/main/js/helpers/organizations.ts
index 8489cb9ed12..3d57db7ff3b 100644
--- a/server/sonar-web/src/main/js/helpers/organizations.ts
+++ b/server/sonar-web/src/main/js/helpers/organizations.ts
@@ -17,40 +17,31 @@
* 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))
);
}