]> source.dussan.org Git - sonarqube.git/commitdiff
Fix merge conflict with branch-6.5
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 24 Jul 2017 07:13:46 +0000 (09:13 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 24 Jul 2017 07:13:46 +0000 (09:13 +0200)
24 files changed:
server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.js
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.js
server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
server/sonar-web/src/main/js/apps/overview/events/Analysis.js
server/sonar-web/src/main/js/apps/overview/events/Event.js
server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js
server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js
server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Analysis-test.js.snap
server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/Event-test.js.snap
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
server/sonar-web/src/main/js/apps/overview/main/enhance.js
server/sonar-web/src/main/js/apps/overview/meta/Meta.js
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.js
server/sonar-web/src/main/js/apps/projects-admin/search.js
server/sonar-web/src/main/js/apps/projects-admin/views/BulkApplyTemplateView.js
server/sonar-web/src/main/js/helpers/storage.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties
tests/src/test/java/org/sonarqube/pageobjects/ProjectsManagementPage.java
tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectAdministrationTest.java

index dc2af1733aaf0086ce3c6e2ad2b8ba6d732a7240..6d3f53657ef4daf00f82212df530070e478a92fe 100644 (file)
@@ -81,7 +81,7 @@ export default function LeakPeriodLegend({ period }: { period: Period }) {
     : translateWithParameters('overview.started_on_x', momentDate.format('LL'));
 
   return (
-    <Tooltip overlay={tooltip} placement="bottom">
+    <Tooltip overlay={tooltip} placement="top">
       <div className="overview-legend">
         {translateWithParameters('overview.leak_period_x', leakPeriodLabel)}
         <br />
index bbde489216a451e48422ca5e7f91178200f21560..79ab68e793c37eb106aaa4da4572db6cc19c537a 100644 (file)
@@ -32,7 +32,6 @@ import { getMeasuresAndMeta } from '../../../api/measures';
 import { getAllTimeMachineData } from '../../../api/time-machine';
 import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
 import { getLeakPeriod } from '../../../helpers/periods';
-import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
 import { getCustomGraph, getGraph } from '../../../helpers/storage';
 import { METRICS, HISTORY_METRICS_LIST } from '../utils';
 import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils';
@@ -148,14 +147,12 @@ export default class OverviewApp extends React.PureComponent {
           <div className="overview-main page-main">
             <QualityGate component={component} measures={measures} />
 
-            <TooltipsContainer>
-              <div className="overview-domains-list">
-                <BugsAndVulnerabilities {...domainProps} />
-                <CodeSmells {...domainProps} />
-                <Coverage {...domainProps} />
-                <Duplications {...domainProps} />
-              </div>
-            </TooltipsContainer>
+            <div className="overview-domains-list">
+              <BugsAndVulnerabilities {...domainProps} />
+              <CodeSmells {...domainProps} />
+              <Coverage {...domainProps} />
+              <Duplications {...domainProps} />
+            </div>
           </div>
 
           <div className="page-sidebar-fixed">
index cf8aa197c0ac415d19a2c7c1c06f9844aaee4eb4..c1b78936fce033c3071aa50a0212ad5779291394 100644 (file)
@@ -40,7 +40,7 @@ type State = {
   metrics: Array<Metric>
 };
 
-const PAGE_SIZE = 5;
+const PAGE_SIZE = 3;
 
 export default class AnalysesList extends React.PureComponent {
   mounted: boolean;
index 916341a20003e548e146d087ee853ca10f475e53..ba7fc7715d153d233266f120a6667bc9c7194759 100644 (file)
@@ -22,7 +22,6 @@ import React from 'react';
 import { sortBy } from 'lodash';
 import Event from './Event';
 import FormattedDate from '../../../components/ui/FormattedDate';
-import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
 import { translate } from '../../../helpers/l10n';
 import type { Analysis as AnalysisType, Event as EventType } from '../../projectActivity/types';
 
@@ -37,22 +36,20 @@ export default function Analysis(props: { analysis: AnalysisType }) {
   );
 
   return (
-    <TooltipsContainer>
-      <li className="overview-analysis">
-        <div className="small little-spacer-bottom">
-          <strong>
-            <FormattedDate date={analysis.date} format="LL" />
-          </strong>
-        </div>
+    <li className="overview-analysis">
+      <div className="small little-spacer-bottom">
+        <strong>
+          <FormattedDate date={analysis.date} format="LL" />
+        </strong>
+      </div>
 
-        {sortedEvents.length > 0
-          ? <div className="project-activity-events">
-              {sortedEvents.map(event => <Event event={event} key={event.key} />)}
-            </div>
-          : <span className="note">
-              {translate('project_activity.project_analyzed')}
-            </span>}
-      </li>
-    </TooltipsContainer>
+      {sortedEvents.length > 0
+        ? <div className="project-activity-events">
+            {sortedEvents.map(event => <Event event={event} key={event.key} />)}
+          </div>
+        : <span className="note">
+            {translate('project_activity.project_analyzed')}
+          </span>}
+    </li>
   );
 }
index bb79518b167b4e0bddc20756b4dc2f17170f4f17..13dd7b0d23fda2233e138fc7740920c53d041f4a 100644 (file)
@@ -19,7 +19,7 @@
  */
 // @flow
 import React from 'react';
-import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
+import Tooltip from '../../../components/controls/Tooltip';
 import type { Event as EventType } from '../../projectActivity/types';
 import { translate } from '../../../helpers/l10n';
 
@@ -36,14 +36,12 @@ export default function Event(props: { event: EventType }) {
 
   return (
     <div className="overview-analysis-event">
-      <TooltipsContainer>
-        <span>
-          <span className="note">{translate('event.category', event.category)}:</span>{' '}
-          <strong title={event.description} data-toggle="tooltip">
-            {event.name}
-          </strong>
-        </span>
-      </TooltipsContainer>
+      <span className="note">{translate('event.category', event.category)}:</span>{' '}
+      <Tooltip overlay={event.description}>
+        <strong>
+          {event.name}
+        </strong>
+      </Tooltip>
     </div>
   );
 }
index 2f84509a196cc6c7186762b3e010493d07012e7b..20538c6a4d1579fa91280f5c01a78feff47fb3c8 100644 (file)
@@ -21,6 +21,8 @@
 import React from 'react';
 import { minBy } from 'lodash';
 import { AutoSizer } from 'react-virtualized';
+import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
+import PreviewGraphTooltips from './PreviewGraphTooltips';
 import {
   DEFAULT_GRAPH,
   getDisplayedHistoryMetrics,
@@ -29,8 +31,6 @@ import {
   splitSeriesInGraphs
 } from '../../projectActivity/utils';
 import { getCustomGraph, getGraph } from '../../../helpers/storage';
-import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
-import PreviewGraphTooltips from './PreviewGraphTooltips';
 import { formatMeasure, getShortType } from '../../../helpers/measures';
 import type { Serie } from '../../../components/charts/AdvancedTimeline';
 import type { History, Metric } from '../types';
index a26d4d7daf20df9c1ff72fa6630bf0d6b795247a..2953cb69037a378f0495a44a1069c91c83349d33 100644 (file)
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
 import PreviewGraphTooltips from '../PreviewGraphTooltips';
 import { DEFAULT_GRAPH } from '../../../projectActivity/utils';
 
-const SERIES_OVERVIEW = [
+const SERIES_ISSUES = [
   {
     name: 'code_smells',
     data: [
@@ -79,7 +79,7 @@ const DEFAULT_PROPS = {
   graphWidth: 150,
   metrics: METRICS,
   selectedDate: new Date('2011-10-01T22:01:00.000Z'),
-  series: SERIES_OVERVIEW,
+  series: SERIES_ISSUES,
   tooltipIdx: 0,
   tooltipPos: 25
 };
index d7a40d0bf7185095cd22bd535d20ddf96ac4febf..e37d21f47d99699e98ec50e578a0b402b424f5ae 100644 (file)
@@ -1,42 +1,40 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`should sort the events with version first 1`] = `
-<TooltipsContainer>
-  <li
-    className="overview-analysis"
+<li
+  className="overview-analysis"
+>
+  <div
+    className="small little-spacer-bottom"
   >
-    <div
-      className="small little-spacer-bottom"
-    >
-      <strong>
-        <FormattedDate
-          date="2017-06-10T16:10:59+0200"
-          format="LL"
-        />
-      </strong>
-    </div>
-    <div
-      className="project-activity-events"
-    >
-      <Event
-        event={
-          Object {
-            "category": "VERSION",
-            "key": "2",
-            "name": "6.5-SNAPSHOT",
-          }
-        }
+    <strong>
+      <FormattedDate
+        date="2017-06-10T16:10:59+0200"
+        format="LL"
       />
-      <Event
-        event={
-          Object {
-            "category": "OTHER",
-            "key": "1",
-            "name": "test",
-          }
+    </strong>
+  </div>
+  <div
+    className="project-activity-events"
+  >
+    <Event
+      event={
+        Object {
+          "category": "VERSION",
+          "key": "2",
+          "name": "6.5-SNAPSHOT",
         }
-      />
-    </div>
-  </li>
-</TooltipsContainer>
+      }
+    />
+    <Event
+      event={
+        Object {
+          "category": "OTHER",
+          "key": "1",
+          "name": "test",
+        }
+      }
+    />
+  </div>
+</li>
 `;
index b04b1393fc830d4b6ae92090dbd9b72f2015bcb2..6ff6f67c30d55644565ab6aaabad4c0c21d95704 100644 (file)
@@ -12,21 +12,19 @@ exports[`should render an event correctly 1`] = `
 <div
   className="overview-analysis-event"
 >
-  <TooltipsContainer>
-    <span>
-      <span
-        className="note"
-      >
-        event.category.OTHER
-        :
-      </span>
-       
-      <strong
-        data-toggle="tooltip"
-      >
-        test
-      </strong>
-    </span>
-  </TooltipsContainer>
+  <span
+    className="note"
+  >
+    event.category.OTHER
+    :
+  </span>
+   
+  <Tooltip
+    placement="bottom"
+  >
+    <strong>
+      test
+    </strong>
+  </Tooltip>
 </div>
 `;
index 07bd2ae83ec47784d585b0667cb859eeb8a2641c..2c5a5e9289c7a8c55e647795aba75f1d173b20c2 100644 (file)
@@ -20,6 +20,7 @@
 import moment from 'moment';
 import React from 'react';
 import { Link } from 'react-router';
+import Tooltip from '../../../components/controls/Tooltip';
 import enhance from './enhance';
 import { getMetricName } from '../helpers/metrics';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -46,11 +47,11 @@ class CodeSmells extends React.PureComponent {
     const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate);
 
     return (
-      <Link to={getComponentIssuesUrl(component.key, params)}>
-        <span title={tooltip} data-toggle="tooltip">
+      <Tooltip overlay={tooltip} placement="top">
+        <Link to={getComponentIssuesUrl(component.key, params)}>
           {formatMeasure(value, 'SHORT_WORK_DUR')}
-        </span>
-      </Link>
+        </Link>
+      </Tooltip>
     );
   }
 
@@ -122,7 +123,7 @@ class CodeSmells extends React.PureComponent {
               </div>
               <div className="overview-domain-measure-label">
                 {getMetricName('effort')}
-                {this.props.renderHistoryLink('sqale_rating')}
+                {this.props.renderHistoryLink('sqale_index')}
               </div>
             </div>
           </div>
index a884d22332af8b2262f53abc88013411f5de9eab..7c465b46c27cdd5c72d5af373aa7d0f406f19315 100644 (file)
@@ -24,6 +24,7 @@ import { DrilldownLink } from '../../../components/shared/drilldown-link';
 import HistoryIcon from '../../../components/icons-components/HistoryIcon';
 import Rating from './../../../components/ui/Rating';
 import Timeline from '../components/Timeline';
+import Tooltip from '../../../components/controls/Tooltip';
 import {
   formatMeasure,
   formatMeasureVariation,
@@ -126,11 +127,16 @@ export default function enhance(ComposedComponent) {
       const value = this.getValue(measure);
       const title = getRatingTooltip(metricKey, value);
       return (
-        <div className="overview-domain-measure-sup" title={title} data-toggle="tooltip">
-          <DrilldownLink className="link-no-underline" component={component.key} metric={metricKey}>
-            <Rating value={value} />
-          </DrilldownLink>
-        </div>
+        <Tooltip overlay={title} placement="top">
+          <div className="overview-domain-measure-sup">
+            <DrilldownLink
+              className="link-no-underline"
+              component={component.key}
+              metric={metricKey}>
+              <Rating value={value} />
+            </DrilldownLink>
+          </div>
+        </Tooltip>
       );
     };
     renderIssues = (metric, type) => {
@@ -147,11 +153,11 @@ export default function enhance(ComposedComponent) {
       const formattedAnalysisDate = moment(component.analysisDate).format('LLL');
       const tooltip = translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate);
       return (
-        <Link to={getComponentIssuesUrl(component.key, params)}>
-          <span title={tooltip} data-toggle="tooltip">
+        <Tooltip overlay={tooltip} placement="top">
+          <Link to={getComponentIssuesUrl(component.key, params)}>
             {formatMeasure(value, 'SHORT_INT')}
-          </span>
-        </Link>
+          </Link>
+        </Tooltip>
       );
     };
     renderHistoryLink = metricKey => {
index db9b0cb523c7774caa07731a150eebc0cf9235bc..1a57590524f605c3cb06257d5ed445f378854ee3 100644 (file)
@@ -56,6 +56,8 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route
 
       {isProject && <MetaTags component={component} />}
 
+      {isProject && <AnalysesList project={component.key} history={history} router={router} />}
+
       {shouldShowQualityGate && <MetaQualityGate gate={qualityGate} />}
 
       {shouldShowQualityProfiles &&
@@ -70,8 +72,6 @@ const Meta = ({ component, history, measures, areThereCustomOrganizations, route
       <MetaKey component={component} />
 
       {shouldShowOrganizationKey && <MetaOrganizationKey component={component} />}
-
-      {isProject && <AnalysesList project={component.key} history={history} router={router} />}
     </div>
   );
 };
index a6e9545967056ec8e3a1504dcc1525e065e9f965..d5762751007ff5ee0fe0d3eb3fe3a0e6a3a96bfd 100644 (file)
@@ -21,7 +21,7 @@
 import React from 'react';
 import { connect } from 'react-redux';
 import { Link } from 'react-router';
-import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
+import Tooltip from '../../../components/controls/Tooltip';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getQualityProfileUrl } from '../../../helpers/urls';
 import { searchRules } from '../../../api/rules';
@@ -105,13 +105,11 @@ class MetaQualityProfiles extends React.PureComponent {
     if (count > 0) {
       const tooltip = translateWithParameters('overview.deprecated_profile', count);
       return (
-        <li
-          key={profile.key}
-          className="overview-deprecated-rules"
-          title={tooltip}
-          data-toggle="tooltip">
-          {inner}
-        </li>
+        <Tooltip key={profile.key} overlay={tooltip}>
+          <li className="overview-deprecated-rules">
+            {inner}
+          </li>
+        </Tooltip>
       );
     }
 
@@ -126,17 +124,15 @@ class MetaQualityProfiles extends React.PureComponent {
     const { profiles } = this.props;
 
     return (
-      <TooltipsContainer>
-        <div className="overview-meta-card">
-          <h4 className="overview-meta-header">
-            {translate('overview.quality_profiles')}
-          </h4>
-
-          <ul className="overview-meta-list">
-            {profiles.map(profile => this.renderProfile(profile))}
-          </ul>
-        </div>
-      </TooltipsContainer>
+      <div className="overview-meta-card">
+        <h4 className="overview-meta-header">
+          {translate('overview.quality_profiles')}
+        </h4>
+
+        <ul className="overview-meta-list">
+          {profiles.map(profile => this.renderProfile(profile))}
+        </ul>
+      </div>
     );
   }
 }
index 356f168475d56b324643be691dcca08bac09ae9b..b93c34bc6eb9b10c0b42f7d82f3d453378c5933f 100644 (file)
@@ -69,7 +69,7 @@ Array [
             Object {
               "category": "QUALITY_PROFILE",
               "key": "AVwQF7zXl-nNFgFWOJ3W",
-              "name": "Changes in 'Default - SonarSource conventions' (Java)",
+              "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
             },
           ],
           "key": "AVwQF7kwl-nNFgFWOJ3V",
@@ -82,7 +82,7 @@ Array [
             Object {
               "category": "QUALITY_PROFILE",
               "key": "AVxZtC-N7841nF4RNEMJ",
-              "name": "Changes in 'Default - SonarSource conventions' (Java)",
+              "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
             },
           ],
           "key": "AVxZtCpH7841nF4RNEMI",
@@ -130,7 +130,7 @@ Array [
             Object {
               "category": "QUALITY_PROFILE",
               "key": "AVxZtC-N7841nF4RNEMJ",
-              "name": "Changes in 'Default - SonarSource conventions' (Java)",
+              "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
             },
           ],
           "key": "AVxZtCpH7841nF4RNEMI",
@@ -195,7 +195,7 @@ Array [
             Object {
               "category": "QUALITY_PROFILE",
               "key": "AVwQF7zXl-nNFgFWOJ3W",
-              "name": "Changes in 'Default - SonarSource conventions' (Java)",
+              "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
             },
           ],
           "key": "AVwQF7kwl-nNFgFWOJ3V",
@@ -208,7 +208,7 @@ Array [
             Object {
               "category": "QUALITY_PROFILE",
               "key": "AVxZtC-N7841nF4RNEMJ",
-              "name": "Changes in 'Default - SonarSource conventions' (Java)",
+              "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
             },
           ],
           "key": "AVxZtCpH7841nF4RNEMI",
index 54cae93e0eec24235d4bcd1e39b7294ba0d7c5cf..77fde4653861972cc6f15cca2c297883edb4102f 100644 (file)
@@ -35,7 +35,7 @@ const ANALYSES = [
       {
         key: 'AVxZtC-N7841nF4RNEMJ',
         category: 'QUALITY_PROFILE',
-        name: "Changes in 'Default - SonarSource conventions' (Java)"
+        name: 'Changes in "Default - SonarSource conventions" (Java)'
       }
     ]
   },
@@ -48,7 +48,7 @@ const ANALYSES = [
       {
         key: 'AVwQF7zXl-nNFgFWOJ3W',
         category: 'QUALITY_PROFILE',
-        name: "Changes in 'Default - SonarSource conventions' (Java)"
+        name: 'Changes in "Default - SonarSource conventions" (Java)'
       }
     ]
   },
index 837e2fd91ffe7f62ab6a38022281f7d23f7a9655..1f9a5f2ab91f8bbfcc407ff3e42e62bafc033b70 100644 (file)
@@ -270,9 +270,12 @@ class ProjectActivityAppContainer extends React.PureComponent {
         key => key !== 'id' && locationQuery[key] !== ''
       );
 
-      // if there is no filter, but there are saved preferences in the localStorage
       const graph = getGraph();
-      return !filtered && graph != null && graph !== DEFAULT_GRAPH;
+      const emptyCustomGraph = isCustomGraph(graph) && getCustomGraph().length <= 0;
+
+      // if there is no filter, but there are saved preferences in the localStorage
+      // also don't redirect to custom if there is no metrics selected for it
+      return !filtered && graph != null && graph !== DEFAULT_GRAPH && !emptyCustomGraph;
     }
   };
 
index 7a6660d6a962b840efe39e60df9271935314a6ab..147fdeca7d611bf86b062b2ece807703f19baaee 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList';
+import { DEFAULT_GRAPH } from '../../utils';
 
 const ANALYSES = [
   {
@@ -77,7 +78,7 @@ const DEFAULT_PROPS = {
   deleteAnalysis: () => {},
   deleteEvent: () => {},
   loading: false,
-  query: { category: '', graph: 'issues', project: 'org.sonarsource.sonarqube:sonarqube' },
+  query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
   updateQuery: () => {}
 };
 
index d5c40161288d127316872002a0f90372a0f8bdee..6f3655c42ec68f85d96b1d477f808cf306392194 100644 (file)
@@ -20,6 +20,7 @@
 import React from 'react';
 import { shallow } from 'enzyme';
 import ProjectActivityApp from '../ProjectActivityApp';
+import { DEFAULT_GRAPH } from '../../utils';
 
 const ANALYSES = [
   {
@@ -80,7 +81,7 @@ const DEFAULT_PROPS = {
       ]
     }
   ],
-  query: { category: '', graph: 'issues', project: 'org.sonarsource.sonarqube:sonarqube' },
+  query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
   updateQuery: () => {}
 };
 
index 15340c641040ebf6e81dc5824d9ef4f66ce9c00a..ee8d522e2ad15dc07ab103ee046ba84294f40bab 100644 (file)
@@ -156,7 +156,9 @@ export default class Search extends React.PureComponent {
                 </form>
               </td>
               <td className="thin nowrap text-middle">
-                <button className="spacer-right" onClick={this.bulkApplyTemplate}>
+                <button
+                  className="spacer-right js-bulk-apply-permission-template"
+                  onClick={this.bulkApplyTemplate}>
                   {translate('permission_templates.bulk_apply_permission_template')}
                 </button>
                 <button
index ee11a8d1eda91f175cc20001767631fbdcc2b3e4..5dd9e8ab54c90409ec30320fc8da85484f18d701 100644 (file)
@@ -73,8 +73,8 @@ export default ModalForm.extend({
     const { selection } = this.options;
     let lastRequest = Promise.resolve();
 
-    selection.forEach(projectId => {
-      const data = { templateId: permissionTemplate, projectId };
+    selection.forEach(projectKey => {
+      const data = { templateId: permissionTemplate, projectKey };
       if (this.options.organization) {
         data.organization = this.options.organization.key;
       }
index 977f184443e4145914e9355910aee17d6d5da01b..763aa3d64b10ece137ea32472e9d3af0c7fec2ce 100644 (file)
@@ -66,8 +66,10 @@ export const getSort = () => window.localStorage.getItem(PROJECTS_SORT);
 
 export const saveCustomGraph = (metrics: ?Array<string>) =>
   save(PROJECT_ACTIVITY_GRAPH_CUSTOM, metrics ? metrics.join(',') : '');
-export const getCustomGraph = (): Array<string> =>
-  (window.localStorage.getItem(PROJECT_ACTIVITY_GRAPH_CUSTOM) || '').split(',');
+export const getCustomGraph = (): Array<string> => {
+  const customGraphs = window.localStorage.getItem(PROJECT_ACTIVITY_GRAPH_CUSTOM);
+  return customGraphs ? customGraphs.split(',') : [];
+};
 
 export const saveGraph = (graph: ?string) => save(PROJECT_ACTIVITY_GRAPH, graph);
 export const getGraph = (): string =>
index 1b33eaf446435b41b70ea17c3afd4cd16b04e742..8ae5c9f0070970155a96a7f8164c3bad2d62d612 100644 (file)
@@ -1533,7 +1533,7 @@ quality_profiles.updated_=Updated:
 quality_profiles.used_=Used:
 quality_profiles.built_in=Built-in
 quality_profiles.built_in.description.1=This quality profile is provided by a plugin.
-quality_profiles.built_in.description.2=It can be automatically updated when a new version of this plugin is deployed and changes its definition.
+quality_profiles.built_in.description.2=It will automatically be updated when a new version of the supplying plugin changes its definition.
 quality_profiles.extends_built_in=Because it inherits from a built-in quality profile, this quality profile can be automatically updated when a new version of the corresponding plugin is deployed.
 
 
index ea5ff5c06db622d949c3c62eae5177ce7a814750..eded1c27bfc5db58886f38485a96fba29a80c7ac 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonarqube.pageobjects;
 
+import com.codeborne.selenide.CollectionCondition;
+
 import static com.codeborne.selenide.Condition.exist;
 import static com.codeborne.selenide.Condition.text;
 import static com.codeborne.selenide.Selenide.$;
@@ -48,4 +50,14 @@ public class ProjectsManagementPage {
     $("#create-project-submit").submit();
     return this;
   }
+
+  public ProjectsManagementPage bulkApplyPermissionTemplate(String template) {
+    $(".js-bulk-apply-permission-template").should(exist).click();
+    $(".modal .select2-choice").should(exist).click();
+    $$(".select2-results li")
+      .shouldHave(CollectionCondition.sizeGreaterThan(0))
+      .findBy(text("foo-template")).should(exist).click();
+    $(".modal .js-apply").should(exist).click();
+    return this;
+  }
 }
index 7d46eb61e7cd4950eae6118a82d7a0b279843b0e..89598145a91d211169bdcc608071bc950aa18a4e 100644 (file)
@@ -21,6 +21,7 @@ package org.sonarqube.tests.projectAdministration;
 
 import com.sonar.orchestrator.Orchestrator;
 import com.sonar.orchestrator.build.SonarScanner;
+import org.sonarqube.pageobjects.ProjectsManagementPage;
 import org.sonarqube.tests.Category1Suite;
 import java.io.UnsupportedEncodingException;
 import java.sql.SQLException;
@@ -40,7 +41,10 @@ import org.sonar.wsclient.base.HttpException;
 import org.sonar.wsclient.user.UserParameters;
 import org.sonarqube.pageobjects.Navigation;
 import org.sonarqube.pageobjects.settings.SettingsPage;
-import util.user.UserRule;
+import org.sonarqube.tests.Tester;
+import org.sonarqube.ws.client.permission.AddUserToTemplateWsRequest;
+import org.sonarqube.ws.client.permission.CreateTemplateWsRequest;
+import org.sonarqube.ws.client.permission.UsersWsRequest;
 
 import static org.apache.commons.lang.time.DateUtils.addDays;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -61,7 +65,7 @@ public class ProjectAdministrationTest {
   public ExpectedException expectedException = ExpectedException.none();
 
   @Rule
-  public UserRule userRule = UserRule.from(orchestrator);
+  public Tester tester = new Tester(orchestrator);
 
   private Navigation nav = Navigation.create(orchestrator);
 
@@ -72,7 +76,7 @@ public class ProjectAdministrationTest {
   @Before
   public void deleteAnalysisData() throws SQLException {
     orchestrator.resetData();
-    adminUser = userRule.createAdminUser();
+    adminUser = tester.users().generateAdministrator().getLogin();
   }
 
   @Test
@@ -197,6 +201,25 @@ public class ProjectAdministrationTest {
       .assertSettingDisplayed("sonar.coverage.exclusions");
   }
 
+  @Test
+  public void bulk_apply_permission_template() {
+    String project = tester.projects().generate(null).getKey();
+    String user = tester.users().generate().getLogin();
+    tester.wsClient().permissions().createTemplate(new CreateTemplateWsRequest().setName("foo-template"));
+    tester.wsClient().permissions().addUserToTemplate(
+      new AddUserToTemplateWsRequest()
+        .setPermission("admin")
+        .setTemplateName("foo-template")
+        .setLogin(user));
+    ProjectsManagementPage page = nav.logIn().submitCredentials(adminUser).openProjectsManagement();
+    page.shouldHaveProject(project);
+    page.bulkApplyPermissionTemplate("foo-template");
+    assertThat(tester.wsClient().permissions().users(new UsersWsRequest()
+      .setProjectKey(project)
+      .setPermission("admin")
+    ).getUsers(0).getLogin()).isEqualTo(user);
+  }
+
   private void scanSampleWithDate(String date) {
     scanSample(date, null);
   }
@@ -216,5 +239,4 @@ public class ProjectAdministrationTest {
   private int count(String condition) {
     return orchestrator.getDatabase().countSql("select count(1) from " + condition);
   }
-
 }