]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10945 Hide QP in rules pages
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 6 Jul 2018 09:44:51 +0000 (11:44 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 11 Jul 2018 18:21:22 +0000 (20:21 +0200)
* SONAR-11003 Always set organization parameter in api/rules/search
* SONAR-11002 Show rules and hide quality profiles inside rules page

24 files changed:
server/sonar-web/src/main/js/apps/coding-rules/components/App.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/RuleDetailsMeta-test.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/AssigneeFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.tsx
server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap
server/sonar-web/src/main/js/apps/organizations/routes.ts
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx [deleted file]
server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.js [deleted file]
server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.js.snap [deleted file]
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/workspace/WorkspaceRuleDetails.tsx
server/sonar-web/src/main/js/components/workspace/__tests__/WorkspaceRuleDetails-test.tsx
server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceRuleDetails-test.tsx.snap
server/sonar-web/src/main/js/helpers/__tests__/organizations-test.ts
server/sonar-web/src/main/js/helpers/organizations.ts

index 04a60d87c0ad92ffcd2c31ff51f7f486d93414eb..19f179aca95d01659fff1de1907538d3ef734612 100644 (file)
  */
 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);
index e384fcb6e16c773f9962e7af6bec9325da69dc63..9eb8edb79ed39a9c7ea61ce9abd3396f5225ff8e 100644 (file)
@@ -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>
   );
 }
index 21481a1f240a1e646cea6f872a69552a515b5227..4521cc0d8103fafda701da35343584e057cfea9e 100644 (file)
@@ -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} />
index def6cd3f6d5ffeb22154b3a2433e127accbb4eba..110956c04fb00b4108da17cb39177f852cb4304c 100644 (file)
@@ -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} />
             )}
index fd496d8f03ea8d32319ee954359afc5f9e415f54..1380e6c0bfc060ff1735f6a4f95b58e8f09a908e 100644 (file)
@@ -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
index b4896bafea6fb9bffd7fd09be0c8b7dd3b924fb8..26d0204509e3bb73b35fe59b3a3eb3b3c28d4669 100644 (file)
@@ -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 }) => {
index c850ffa07507d00355bc12c9cbe8fdabc094e4ee..77611af9140e5ddeaa59583be11c6d0b1747a6fd 100644 (file)
@@ -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}
           />
index dab0d43d2982876da338935173eecad74acd2afc..91e30bf9de57f668b5ce0e9c0524521c984a83d8 100644 (file)
@@ -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 }))
     );
   };
index 2b5e9f301d43a055a9034beec22868ed3e5fea59..4cef029f3dfc0786c24425825aea6f918f233541 100644 (file)
 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;
index 1fda2012f13e329e11859aef6103b97be8da38af..07ee53cd586b187fb8c6e52b86f126d5baad9b9c 100644 (file)
@@ -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} />
index a51bf8cc5131d3ba64631ee6b137263caebd33ef..1177b7604b75cbfa75bba0549603d46061a38d9e 100644 (file)
@@ -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"
index 18aa7c3f4ba0133cb1f5953ee9d68287b0108b36..24cd7999dcd7bca59ad0b033d86e9dacb35323e9 100644 (file)
@@ -53,6 +53,11 @@ const routes = [
           { indexRoute: { component: lazyLoad(() => import('../issues/components/AppContainer')) } }
         ]
       },
+      {
+        path: 'rules',
+        component: OrganizationContainer,
+        childRoutes: codingRulesRoutes
+      },
       {
         component: lazyLoad(() =>
           import('./components/OrganizationAccessContainer').then(lib => ({
@@ -73,11 +78,6 @@ const routes = [
           }))
         ),
         childRoutes: [
-          {
-            path: 'rules',
-            component: OrganizationContainer,
-            childRoutes: codingRulesRoutes
-          },
           {
             path: 'quality_profiles',
             childRoutes: qualityProfilesRoutes
index df32445c4121aec77ba57f5778efefa6943cbebe..402725bdc7133eb345b817e895ea7c0b9c2ec51c 100644 (file)
@@ -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/Meta.tsx
deleted file mode 100644 (file)
index 5eb22f4..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx b/server/sonar-web/src/main/js/apps/overview/meta/MetaContainer.tsx
new file mode 100644 (file)
index 0000000..62ee564
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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);
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.js
deleted file mode 100644 (file)
index 48da22f..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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();
-});
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/MarkdownTips-test.tsx
new file mode 100644 (file)
index 0000000..5b0dfe1
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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();
+});
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.js.snap
deleted file mode 100644 (file)
index baeceb6..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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>
-`;
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MarkdownTips-test.tsx.snap
new file mode 100644 (file)
index 0000000..baeceb6
--- /dev/null
@@ -0,0 +1,36 @@
+// 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>
+`;
index 647f4a4fade250aeba0cdb67a2b778ba5dd00790..a1a62dcb3e55ea210e59a7dfc54a51b77f5e752d 100644 (file)
  */
 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}
index e755c5686d5fbf0bf7a890ae796ca118d66d3401..13ff053fcc3cba0f48be0d4f6ea1c9a25dcc2c31 100644 (file)
@@ -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();
-});
index 566b4a80db2a73b99dcb67dfd253a081c5848ad8..3b316292cb5d361ebef0a2533ed87dc3f38a2ade 100644 (file)
@@ -15,7 +15,6 @@ exports[`should render 2`] = `
   <React.Fragment>
     <RuleDetailsMeta
       canWrite={false}
-      hidePermalink={false}
       hideSimilarRulesFilter={true}
       onFilterChange={[Function]}
       onTagsChange={[Function]}
index 17429129955a86041a2c006c6171067fb4c7426f..a483c995c6cb520e776a7bf03279d6956903e50d 100644 (file)
  * 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();
   });
 });
index 8489cb9ed12881332ef641e6675657a3c51aaf31..3d57db7ff3ba3775d677c79ed00216e1fd3dc4f2 100644 (file)
  * 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))
   );
 }