: 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 />
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';
<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">
metrics: Array<Metric>
};
-const PAGE_SIZE = 5;
+const PAGE_SIZE = 3;
export default class AnalysesList extends React.PureComponent {
mounted: boolean;
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';
);
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>
);
}
*/
// @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';
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>
);
}
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,
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';
import PreviewGraphTooltips from '../PreviewGraphTooltips';
import { DEFAULT_GRAPH } from '../../../projectActivity/utils';
-const SERIES_OVERVIEW = [
+const SERIES_ISSUES = [
{
name: 'code_smells',
data: [
graphWidth: 150,
metrics: METRICS,
selectedDate: new Date('2011-10-01T22:01:00.000Z'),
- series: SERIES_OVERVIEW,
+ series: SERIES_ISSUES,
tooltipIdx: 0,
tooltipPos: 25
};
// 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>
`;
<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>
`;
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';
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>
);
}
</div>
<div className="overview-domain-measure-label">
{getMetricName('effort')}
- {this.props.renderHistoryLink('sqale_rating')}
+ {this.props.renderHistoryLink('sqale_index')}
</div>
</div>
</div>
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,
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) => {
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 => {
{isProject && <MetaTags component={component} />}
+ {isProject && <AnalysesList project={component.key} history={history} router={router} />}
+
{shouldShowQualityGate && <MetaQualityGate gate={qualityGate} />}
{shouldShowQualityProfiles &&
<MetaKey component={component} />
{shouldShowOrganizationKey && <MetaOrganizationKey component={component} />}
-
- {isProject && <AnalysesList project={component.key} history={history} router={router} />}
</div>
);
};
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';
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>
);
}
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>
);
}
}
Object {
"category": "QUALITY_PROFILE",
"key": "AVwQF7zXl-nNFgFWOJ3W",
- "name": "Changes in 'Default - SonarSource conventions' (Java)",
+ "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
},
],
"key": "AVwQF7kwl-nNFgFWOJ3V",
Object {
"category": "QUALITY_PROFILE",
"key": "AVxZtC-N7841nF4RNEMJ",
- "name": "Changes in 'Default - SonarSource conventions' (Java)",
+ "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
},
],
"key": "AVxZtCpH7841nF4RNEMI",
Object {
"category": "QUALITY_PROFILE",
"key": "AVxZtC-N7841nF4RNEMJ",
- "name": "Changes in 'Default - SonarSource conventions' (Java)",
+ "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
},
],
"key": "AVxZtCpH7841nF4RNEMI",
Object {
"category": "QUALITY_PROFILE",
"key": "AVwQF7zXl-nNFgFWOJ3W",
- "name": "Changes in 'Default - SonarSource conventions' (Java)",
+ "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
},
],
"key": "AVwQF7kwl-nNFgFWOJ3V",
Object {
"category": "QUALITY_PROFILE",
"key": "AVxZtC-N7841nF4RNEMJ",
- "name": "Changes in 'Default - SonarSource conventions' (Java)",
+ "name": "Changes in \\"Default - SonarSource conventions\\" (Java)",
},
],
"key": "AVxZtCpH7841nF4RNEMI",
{
key: 'AVxZtC-N7841nF4RNEMJ',
category: 'QUALITY_PROFILE',
- name: "Changes in 'Default - SonarSource conventions' (Java)"
+ name: 'Changes in "Default - SonarSource conventions" (Java)'
}
]
},
{
key: 'AVwQF7zXl-nNFgFWOJ3W',
category: 'QUALITY_PROFILE',
- name: "Changes in 'Default - SonarSource conventions' (Java)"
+ name: 'Changes in "Default - SonarSource conventions" (Java)'
}
]
},
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;
}
};
import React from 'react';
import { shallow } from 'enzyme';
import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList';
+import { DEFAULT_GRAPH } from '../../utils';
const ANALYSES = [
{
deleteAnalysis: () => {},
deleteEvent: () => {},
loading: false,
- query: { category: '', graph: 'issues', project: 'org.sonarsource.sonarqube:sonarqube' },
+ query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
updateQuery: () => {}
};
import React from 'react';
import { shallow } from 'enzyme';
import ProjectActivityApp from '../ProjectActivityApp';
+import { DEFAULT_GRAPH } from '../../utils';
const ANALYSES = [
{
]
}
],
- query: { category: '', graph: 'issues', project: 'org.sonarsource.sonarqube:sonarqube' },
+ query: { category: '', graph: DEFAULT_GRAPH, project: 'org.sonarsource.sonarqube:sonarqube' },
updateQuery: () => {}
};
</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
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;
}
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 =>
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.
*/
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.$;
$("#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;
+ }
}
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;
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;
public ExpectedException expectedException = ExpectedException.none();
@Rule
- public UserRule userRule = UserRule.from(orchestrator);
+ public Tester tester = new Tester(orchestrator);
private Navigation nav = Navigation.create(orchestrator);
@Before
public void deleteAnalysisData() throws SQLException {
orchestrator.resetData();
- adminUser = userRule.createAdminUser();
+ adminUser = tester.users().generateAdministrator().getLogin();
}
@Test
.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);
}
private int count(String condition) {
return orchestrator.getDatabase().countSql("select count(1) from " + condition);
}
-
}