}
export function searchProjectTags(data?: { ps?: number; q?: string }): Promise<any> {
- return getJSON('/api/project_tags/search', data);
+ return getJSON('/api/project_tags/search', data).catch(throwGlobalError);
}
export function setProjectTags(data: { project: string; tags: string }): Promise<void> {
*/
import { getJSON, postJSON, post, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
+import { Paging } from '../app/types';
-interface GetProjectActivityResponse {
- analyses: any[];
- paging: {
- total: number;
- pageIndex: number;
- pageSize: number;
- };
+export interface Event {
+ key: string;
+ name: string;
+ category: string;
+ description?: string;
+}
+
+export interface Analysis {
+ key: string;
+ date: string;
+ events: Event[];
}
export function getProjectActivity(data: {
category?: string;
p?: number;
ps?: number;
-}): Promise<GetProjectActivityResponse> {
+}): Promise<{ analyses: Analysis[]; paging: Paging }> {
return getJSON('/api/project_analyses/search', data).catch(throwGlobalError);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, post, postJSON } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
-export function getProjectLinks(projectKey: string): Promise<any> {
+export interface ProjectLink {
+ id: string;
+ name: string;
+ type: string;
+ url: string;
+}
+
+export function getProjectLinks(projectKey: string): Promise<ProjectLink[]> {
const url = '/api/project_links/search';
const data = { projectKey };
- return getJSON(url, data).then(r => r.links);
+ return getJSON(url, data).then(r => r.links, throwGlobalError);
}
export function deleteLink(linkId: string): Promise<void> {
*/
import { getJSON, post, postJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
+import { Metric } from '../app/types';
export interface ConditionBase {
error: string;
return post('/api/qualitygates/deselect', data).catch(throwGlobalError);
}
+export interface ConditionAnalysis {
+ comparator: string;
+ errorThreshold?: string;
+ metric: string;
+ periodIndex?: number;
+ onLeak?: boolean;
+ status: string;
+ value: string;
+ warningThreshold?: string;
+}
+
+export interface ApplicationProject {
+ key: string;
+ name: string;
+ status: string;
+ conditions: ConditionAnalysis[];
+}
+
+export interface ApplicationQualityGate {
+ metrics: Metric[];
+ projects: ApplicationProject[];
+ status: string;
+}
+
export function getApplicationQualityGate(data: {
application: string;
organization?: string;
-}): Promise<void | Response> {
+}): Promise<ApplicationQualityGate> {
return getJSON('/api/qualitygates/application_status', data).catch(throwGlobalError);
}
*/
import { getJSON } from '../helpers/request';
import { Paging } from '../app/types';
+import throwGlobalError from '../app/utils/throwGlobalError';
export interface HistoryItem {
date: Date;
metrics: metrics.join(),
ps: 1000,
...other
- });
+ }).catch(throwGlobalError);
}
export function getAllTimeMachineData(
interface State {
branches: Branch[];
loading: boolean;
- component: Component | null;
+ component?: Component;
currentTask?: Task;
isInProgress?: boolean;
isPending?: boolean;
constructor(props: Props) {
super(props);
- this.state = { branches: [], loading: true, component: null };
+ this.state = { branches: [], loading: true };
}
componentDidMount() {
qualifier: string;
}
-export interface Component {
+export interface LightComponent {
+ key: string;
+ organization: string;
+ qualifier: string;
+}
+
+export interface Component extends LightComponent {
analysisDate?: string;
breadcrumbs: Breadcrumb[];
configuration?: ComponentConfiguration;
description?: string;
extensions?: Extension[];
isFavorite?: boolean;
- key: string;
name: string;
- organization: string;
path?: string;
- qualifier: string;
refKey?: string;
+ qualityProfiles?: { key: string; language: string; name: string }[];
+ qualityGate?: { isDefault?: boolean; key: string; name: string };
+ tags?: string[];
version?: string;
visibility?: string;
}
domain?: string;
hidden?: boolean;
key: string;
- name?: string;
+ name: string;
qualitative?: boolean;
type: string;
}
return <span />;
}
- return (
- <Measure measure={{ ...measure, metric: { key: finalMetricKey, type: finalMetricType } }} />
- );
+ return <Measure value={measure.value} metricKey={finalMetricKey} metricType={finalMetricType} />;
}
export default function MeasureHeader(props /*: Props*/) {
const { branch, component, leakPeriod, measure, secondaryMeasure } = props;
- const metric = measure.metric;
+ const { metric } = measure;
const isDiff = isDiffMetric(metric.key);
return (
<div className="measure-details-header big-spacer-bottom">
<span className="measure-details-value spacer-left">
<strong>
{isDiff ? (
- <Measure className="domain-measures-leak" measure={measure} metric={metric} />
+ <Measure
+ className="domain-measures-leak"
+ value={measure.leak}
+ metricKey={metric.key}
+ metricType={metric.type}
+ />
) : (
- <Measure measure={measure} metric={metric} />
+ <Measure value={measure.value} metricKey={metric.key} metricType={metric.type} />
)}
</strong>
</span>
>
<strong>
<Measure
- measure={
- Object {
- "leak": "0.0",
- "metric": Object {
- "key": "reliability_rating",
- "name": "Reliability Rating",
- "type": "RATING",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "0.0",
- },
- ],
- "value": "3.0",
- }
- }
- metric={
- Object {
- "key": "reliability_rating",
- "name": "Reliability Rating",
- "type": "RATING",
- }
- }
+ metricKey="reliability_rating"
+ metricType="RATING"
+ value="3.0"
/>
</strong>
</span>
<strong>
<Measure
className="domain-measures-leak"
- measure={
- Object {
- "leak": "3.0",
- "metric": Object {
- "key": "new_reliability_rating",
- "name": "Reliability Rating on New Code",
- "type": "RATING",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "3.0",
- },
- ],
- }
- }
- metric={
- Object {
- "key": "new_reliability_rating",
- "name": "Reliability Rating on New Code",
- "type": "RATING",
- }
- }
+ metricKey="new_reliability_rating"
+ metricType="RATING"
+ value="3.0"
/>
</strong>
</span>
{otherMeasures.map(measure => (
<MeasureCell
key={measure.metric.key}
- component={{
- ...component,
- value: measure.value,
- leak: measure.leak
- }}
+ component={component}
+ measure={measure}
metric={measure.metric}
/>
))}
// @flow
import React from 'react';
import Measure from '../../../components/measure/Measure';
+import { isDiffMetric } from '../../../helpers/measures';
/*:: import type { Component } from '../types'; */
/*:: import type { Metric } from '../../../store/metrics/actions'; */
return (
<td className="thin nowrap text-right">
<span id={`component-measures-component-measure-${component.key}-${metric.key}`}>
- <Measure measure={{ metric, value: component.value, leak: component.leak }} />
+ <Measure
+ value={isDiffMetric(metric.key) ? component.leak : component.value}
+ metricKey={metric.key}
+ metricType={metric.type}
+ />
</span>
</td>
);
<div
id={`measure-${measure.metric.key}-leak`}
className="domain-measures-value domain-measures-leak">
- <Measure measure={measure} />
+ <Measure
+ value={measure.leak}
+ metricKey={measure.metric.key}
+ metricType={measure.metric.type}
+ />
</div>
);
}
return (
<div id={`measure-${measure.metric.key}-value`} className="domain-measures-value">
- <Measure measure={measure} />
+ <Measure
+ value={measure.value}
+ metricKey={measure.metric.key}
+ metricType={measure.metric.type}
+ />
</div>
);
}
id="measure-new_bugs-leak"
>
<Measure
- measure={
- Object {
- "leak": "5",
- "metric": Object {
- "domain": "Reliability",
- "key": "new_bugs",
- "name": "New Bugs",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "5",
- },
- ],
- }
- }
+ metricKey="new_bugs"
+ metricType="INT"
+ value="5"
/>
</div>
`;
id="measure-bugs-value"
>
<Measure
- measure={
- Object {
- "leak": "5",
- "metric": Object {
- "domain": "Reliability",
- "key": "bugs",
- "name": "Bugs",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "5",
- },
- ],
- "value": "5",
- }
- }
+ metricKey="bugs"
+ metricType="INT"
+ value="5"
/>
</div>
`;
import './styles.css';
interface Props {
- branch: string;
+ branch?: string;
project: string;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import OverviewApp from './OverviewApp';
+import EmptyOverview from './EmptyOverview';
+import { getBranchName, isShortLivingBranch } from '../../../helpers/branches';
+import { getProjectBranchUrl, getCodeUrl } from '../../../helpers/urls';
+import { Branch, Component } from '../../../app/types';
+
+interface Props {
+ branch?: Branch;
+ component: Component;
+ isInProgress?: boolean;
+ isPending?: boolean;
+ onComponentChange: (changes: Partial<Component>) => void;
+}
+
+export default class App extends React.PureComponent<Props> {
+ static contextTypes = {
+ router: PropTypes.object
+ };
+
+ componentDidMount() {
+ const { branch, component } = this.props;
+
+ if (this.isPortfolio()) {
+ this.context.router.replace({
+ pathname: '/portfolio',
+ query: { id: component.key }
+ });
+ } else if (this.isFile()) {
+ this.context.router.replace(
+ getCodeUrl(component.breadcrumbs[0].key, getBranchName(branch), component.key)
+ );
+ } else if (isShortLivingBranch(branch)) {
+ this.context.router.replace(getProjectBranchUrl(component.key, branch));
+ }
+ }
+
+ isPortfolio = () => ['VW', 'SVW'].includes(this.props.component.qualifier);
+
+ isFile = () => ['FIL', 'UTS'].includes(this.props.component.qualifier);
+
+ render() {
+ const { branch, component } = this.props;
+
+ if (this.isPortfolio() || this.isFile() || isShortLivingBranch(branch)) {
+ return null;
+ }
+
+ if (!component.analysisDate) {
+ return (
+ <EmptyOverview
+ component={component.key}
+ showWarning={!this.props.isPending && !this.props.isInProgress}
+ />
+ );
+ }
+
+ return (
+ <OverviewApp
+ branch={branch}
+ component={component}
+ onComponentChange={this.props.onComponentChange}
+ />
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { uniq } from 'lodash';
-import QualityGate from '../qualityGate/QualityGate';
-import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate';
-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 throwGlobalError from '../../../app/utils/throwGlobalError';
-import { getMeasuresAndMeta } from '../../../api/measures';
-import { getAllTimeMachineData } from '../../../api/time-machine';
-import { parseDate } from '../../../helpers/dates';
-import { enhanceMeasuresWithMetrics } from '../../../helpers/measures';
-import { getLeakPeriod } from '../../../helpers/periods';
-import { getCustomGraph, getGraph } from '../../../helpers/storage';
-import { METRICS, HISTORY_METRICS_LIST } from '../utils';
-import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils';
-import { getBranchName } from '../../../helpers/branches';
-/*:: import type { Component, History, MeasuresList, Period } from '../types'; */
-import '../styles.css';
-
-/*::
-type Props = {
- branch?: { name: string },
- component: Component,
- onComponentChange: {} => void
-};
-*/
-
-/*::
-type State = {
- history?: History,
- historyStartDate?: Date,
- loading: boolean,
- measures: MeasuresList,
- periods?: Array<Period>
-};
-*/
-
-export default class OverviewApp extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = {
- loading: true,
- measures: []
- };
-
- componentDidMount() {
- this.mounted = true;
- this.loadMeasures().then(this.loadHistory);
- }
-
- componentDidUpdate(prevProps /*: Props */) {
- if (
- this.props.component.key !== prevProps.component.key ||
- this.props.branch !== prevProps.branch
- ) {
- this.loadMeasures().then(this.loadHistory);
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- loadMeasures() {
- const { branch, component } = this.props;
- this.setState({ loading: true });
-
- return getMeasuresAndMeta(component.key, METRICS, {
- additionalFields: 'metrics,periods',
- branch: branch && getBranchName(branch)
- }).then(
- r => {
- if (this.mounted) {
- this.setState({
- loading: false,
- measures: enhanceMeasuresWithMetrics(r.component.measures, r.metrics),
- periods: r.periods
- });
- }
- },
- error => {
- throwGlobalError(error);
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- }
-
- loadHistory = () => {
- const { branch, component } = this.props;
-
- let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph());
- if (!graphMetrics || graphMetrics.length <= 0) {
- graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []);
- }
-
- const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics));
- return getAllTimeMachineData(component.key, metrics, {
- branch: branch && getBranchName(branch)
- }).then(r => {
- if (this.mounted) {
- const history /*: History */ = {};
- r.measures.forEach(measure => {
- const measureHistory = measure.history.map(analysis => ({
- date: parseDate(analysis.date),
- value: analysis.value
- }));
- history[measure.metric] = measureHistory;
- });
- const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
- this.setState({ history, historyStartDate });
- }
- }, throwGlobalError);
- };
-
- getApplicationLeakPeriod = () =>
- this.state.measures.find(measure => measure.metric.key === 'new_bugs') ? { index: 1 } : null;
-
- renderLoading() {
- return (
- <div className="text-center">
- <i className="spinner spinner-margin" />
- </div>
- );
- }
-
- render() {
- const { branch, component } = this.props;
- const { loading, measures, periods, history, historyStartDate } = this.state;
-
- if (loading) {
- return this.renderLoading();
- }
-
- const leakPeriod =
- component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods);
- const branchName = branch && getBranchName(branch);
- const domainProps = {
- branch: branchName,
- component,
- measures,
- leakPeriod,
- history,
- historyStartDate
- };
-
- return (
- <div className="page page-limited">
- <div className="overview page-with-sidebar">
- <div className="overview-main page-main">
- {component.qualifier === 'APP' ? (
- <ApplicationQualityGate component={component} />
- ) : (
- <QualityGate branch={branchName} component={component} measures={measures} />
- )}
-
- <div className="overview-domains-list">
- <BugsAndVulnerabilities {...domainProps} />
- <CodeSmells {...domainProps} />
- <Coverage {...domainProps} />
- <Duplications {...domainProps} />
- </div>
- </div>
-
- <div className="page-sidebar-fixed">
- <Meta
- branch={branchName}
- component={component}
- history={history}
- measures={measures}
- onComponentChange={this.props.onComponentChange}
- />
- </div>
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { uniq } from 'lodash';
+import QualityGate from '../qualityGate/QualityGate';
+import ApplicationQualityGate from '../qualityGate/ApplicationQualityGate';
+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 throwGlobalError from '../../../app/utils/throwGlobalError';
+import { getMeasuresAndMeta } from '../../../api/measures';
+import { getAllTimeMachineData, History } from '../../../api/time-machine';
+import { parseDate } from '../../../helpers/dates';
+import { enhanceMeasuresWithMetrics, MeasureEnhanced } from '../../../helpers/measures';
+import { getLeakPeriod, Period } from '../../../helpers/periods';
+import { getCustomGraph, getGraph } from '../../../helpers/storage';
+import { METRICS, HISTORY_METRICS_LIST } from '../utils';
+import { DEFAULT_GRAPH, getDisplayedHistoryMetrics } from '../../projectActivity/utils';
+import { getBranchName } from '../../../helpers/branches';
+import { Branch, Component } from '../../../app/types';
+import '../styles.css';
+
+interface Props {
+ branch?: Branch;
+ component: Component;
+ onComponentChange: (changes: {}) => void;
+}
+
+interface State {
+ history?: History;
+ historyStartDate?: Date;
+ loading: boolean;
+ measures: MeasureEnhanced[];
+ periods?: Period[];
+}
+
+export default class OverviewApp extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true, measures: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadMeasures().then(this.loadHistory, () => {});
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (
+ this.props.component.key !== prevProps.component.key ||
+ this.props.branch !== prevProps.branch
+ ) {
+ this.loadMeasures().then(this.loadHistory, () => {});
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadMeasures() {
+ const { branch, component } = this.props;
+ this.setState({ loading: true });
+
+ return getMeasuresAndMeta(component.key, METRICS, {
+ additionalFields: 'metrics,periods',
+ branch: getBranchName(branch)
+ }).then(
+ r => {
+ if (this.mounted && r.metrics) {
+ this.setState({
+ loading: false,
+ measures: enhanceMeasuresWithMetrics(r.component.measures, r.metrics),
+ periods: r.periods
+ });
+ }
+ },
+ error => {
+ throwGlobalError(error);
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ }
+
+ loadHistory = () => {
+ const { branch, component } = this.props;
+
+ let graphMetrics = getDisplayedHistoryMetrics(getGraph(), getCustomGraph());
+ if (!graphMetrics || graphMetrics.length <= 0) {
+ graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []);
+ }
+
+ const metrics = uniq(HISTORY_METRICS_LIST.concat(graphMetrics));
+ return getAllTimeMachineData(component.key, metrics, { branch: getBranchName(branch) }).then(
+ r => {
+ if (this.mounted) {
+ const history: History = {};
+ r.measures.forEach(measure => {
+ const measureHistory = measure.history.map(analysis => ({
+ date: parseDate(analysis.date),
+ value: analysis.value
+ }));
+ history[measure.metric] = measureHistory;
+ });
+ const historyStartDate = history[HISTORY_METRICS_LIST[0]][0].date;
+ this.setState({ history, historyStartDate });
+ }
+ }
+ );
+ };
+
+ getApplicationLeakPeriod = () =>
+ this.state.measures.find(measure => measure.metric.key === 'new_bugs') ? { index: 1 } : null;
+
+ renderLoading() {
+ return (
+ <div className="text-center">
+ <i className="spinner spinner-margin" />
+ </div>
+ );
+ }
+
+ render() {
+ const { branch, component } = this.props;
+ const { loading, measures, periods, history, historyStartDate } = this.state;
+
+ if (loading) {
+ return this.renderLoading();
+ }
+
+ const leakPeriod =
+ component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods);
+ const branchName = getBranchName(branch);
+ const domainProps = {
+ branch: branchName,
+ component,
+ measures,
+ leakPeriod,
+ history,
+ historyStartDate
+ };
+
+ return (
+ <div className="page page-limited">
+ <div className="overview page-with-sidebar">
+ <div className="overview-main page-main">
+ {component.qualifier === 'APP' ? (
+ <ApplicationQualityGate component={component} />
+ ) : (
+ <QualityGate branch={branchName} component={component} measures={measures} />
+ )}
+
+ <div className="overview-domains-list">
+ <BugsAndVulnerabilities {...domainProps} />
+ <CodeSmells {...domainProps} />
+ <Coverage {...domainProps} />
+ <Duplications {...domainProps} />
+ </div>
+ </div>
+
+ <div className="page-sidebar-fixed">
+ <Meta
+ branch={branchName}
+ component={component}
+ history={history}
+ measures={measures}
+ onComponentChange={this.props.onComponentChange}
+ />
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { max } from 'd3-array';
-import { LineChart } from '../../../components/charts/line-chart';
-
-const HEIGHT = 80;
-
-export default class Timeline extends React.PureComponent {
- static propTypes = {
- history: PropTypes.arrayOf(PropTypes.object).isRequired,
- before: PropTypes.object,
- after: PropTypes.object
- };
-
- filterSnapshots() {
- const { history, before, after } = this.props;
-
- return history.filter(s => {
- const matchBefore = !before || s.date <= before;
- const matchAfter = !after || s.date >= after;
- return matchBefore && matchAfter;
- });
- }
-
- render() {
- const snapshots = this.filterSnapshots();
-
- if (snapshots.length < 2) {
- return null;
- }
-
- const data = snapshots.map((snapshot, index) => {
- return { x: index, y: snapshot.value };
- });
- const domain = [0, max(this.props.history, d => parseFloat(d.value))];
- return (
- <LineChart
- data={data}
- domain={domain}
- interpolate="basis"
- displayBackdrop={true}
- displayPoints={false}
- displayVerticalGrid={false}
- height={HEIGHT}
- padding={[0, 0, 0, 0]}
- />
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { max } from 'd3-array';
+import { LineChart } from '../../../components/charts/line-chart';
+import { HistoryItem } from '../../../api/time-machine';
+
+const HEIGHT = 80;
+
+interface Props {
+ history: HistoryItem[];
+ before?: Date;
+ after?: Date;
+}
+
+export default class Timeline extends React.PureComponent<Props> {
+ filterSnapshots() {
+ const { history, before, after } = this.props;
+
+ return history.filter(s => {
+ const matchBefore = !before || s.date <= before;
+ const matchAfter = !after || s.date >= after;
+ return matchBefore && matchAfter;
+ });
+ }
+
+ render() {
+ const snapshots = this.filterSnapshots();
+
+ if (snapshots.length < 2) {
+ return null;
+ }
+
+ const data = snapshots.map((snapshot, index) => {
+ return { x: index, y: snapshot.value };
+ });
+ const domain = [0, max(this.props.history, d => parseFloat(d.value))];
+ return (
+ <LineChart
+ data={data}
+ domain={domain}
+ interpolate="basis"
+ displayBackdrop={true}
+ displayPoints={false}
+ displayVerticalGrid={false}
+ height={HEIGHT}
+ padding={[0, 0, 0, 0]}
+ />
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { mount, shallow } from 'enzyme';
+import App from '../App';
+import OverviewApp from '../OverviewApp';
+import EmptyOverview from '../EmptyOverview';
+import { BranchType, LongLivingBranch } from '../../../../app/types';
+
+const component = {
+ key: 'foo',
+ analysisDate: '2016-01-01',
+ breadcrumbs: [],
+ name: 'Foo',
+ organization: 'org',
+ qualifier: 'TRK',
+ version: '0.0.1'
+};
+
+it('should render OverviewApp', () => {
+ expect(getWrapper().type()).toBe(OverviewApp);
+});
+
+it('should render EmptyOverview', () => {
+ const output = getWrapper({ component: { key: 'foo' } });
+ expect(output.type()).toBe(EmptyOverview);
+});
+
+it('redirects on Code page for files', () => {
+ const branch: LongLivingBranch = { isMain: false, name: 'b', type: BranchType.LONG };
+ const newComponent = {
+ ...component,
+ breadcrumbs: [
+ { key: 'project', name: 'Project', qualifier: 'TRK' },
+ { key: 'foo', name: 'Foo', qualifier: 'DIR' }
+ ],
+ qualifier: 'FIL'
+ };
+ const replace = jest.fn();
+ mount(<App branch={branch} component={newComponent} onComponentChange={jest.fn()} />, {
+ context: { router: { replace } }
+ });
+ expect(replace).toBeCalledWith({
+ pathname: '/code',
+ query: { branch: 'b', id: 'project', selected: 'foo' }
+ });
+});
+
+function getWrapper(props = {}) {
+ return shallow(<App component={component} onComponentChange={jest.fn()} {...props} />);
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import Analysis from './Analysis';
-import { getAllMetrics } from '../../../api/metrics';
-import { getProjectActivity } from '../../../api/projectActivity';
-import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Analysis as AnalysisType } from '../../projectActivity/types'; */
-/*:: import type { History, Metric } from '../types'; */
-
-/*::
-type Props = {
- branch?: string,
- component: Object,
- history: ?History,
- qualifier: string
-};
-*/
-
-/*::
-type State = {
- analyses: Array<AnalysisType>,
- loading: boolean,
- metrics: Array<Metric>
-};
-*/
-
-const PAGE_SIZE = 3;
-
-export default class AnalysesList extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = { analyses: [], loading: true, metrics: [] };
-
- componentDidMount() {
- this.mounted = true;
- this.fetchData();
- }
-
- componentDidUpdate(prevProps /*: Props */) {
- if (prevProps.component !== this.props.component) {
- this.fetchData();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- getTopLevelComponent = () => {
- const { component } = this.props;
- let current = component.breadcrumbs.length - 1;
- while (
- current > 0 &&
- !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier)
- ) {
- current--;
- }
- return component.breadcrumbs[current].key;
- };
-
- fetchData() {
- this.setState({ loading: true });
- Promise.all([
- getProjectActivity({
- branch: this.props.branch,
- project: this.getTopLevelComponent(),
- ps: PAGE_SIZE
- }),
- getAllMetrics()
- ]).then(
- response => {
- if (this.mounted) {
- this.setState({
- analyses: response[0].analyses,
- metrics: response[1],
- loading: false
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- }
-
- renderList(analyses /*: Array<AnalysisType> */) {
- if (!analyses.length) {
- return <p className="spacer-top note">{translate('no_results')}</p>;
- }
-
- return (
- <ul className="spacer-top">
- {analyses.map(analysis => (
- <Analysis key={analysis.key} analysis={analysis} qualifier={this.props.qualifier} />
- ))}
- </ul>
- );
- }
-
- render() {
- const { analyses, loading } = this.state;
-
- if (loading) {
- return null;
- }
-
- return (
- <div className="overview-meta-card">
- <h4 className="overview-meta-header">{translate('project_activity.page')}</h4>
-
- <PreviewGraph
- branch={this.props.branch}
- history={this.props.history}
- project={this.props.component.key}
- metrics={this.state.metrics}
- />
-
- {this.renderList(analyses)}
-
- <div className="spacer-top small">
- <Link
- to={{
- pathname: '/project/activity',
- query: { id: this.props.component.key, branch: this.props.branch }
- }}>
- {translate('show_more')}
- </Link>
- </div>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import Analysis from './Analysis';
+import { getAllMetrics } from '../../../api/metrics';
+import { getProjectActivity, Analysis as IAnalysis } from '../../../api/projectActivity';
+import PreviewGraph from '../../../components/preview-graph/PreviewGraph';
+import { translate } from '../../../helpers/l10n';
+import { Metric, Component } from '../../../app/types';
+import { History } from '../../../api/time-machine';
+
+interface Props {
+ branch?: string;
+ component: Component;
+ history?: History;
+ qualifier: string;
+}
+
+interface State {
+ analyses: IAnalysis[];
+ loading: boolean;
+ metrics: Metric[];
+}
+
+const PAGE_SIZE = 3;
+
+export default class AnalysesList extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { analyses: [], loading: true, metrics: [] };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchData();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.component !== this.props.component) {
+ this.fetchData();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ getTopLevelComponent = () => {
+ const { component } = this.props;
+ let current = component.breadcrumbs.length - 1;
+ while (
+ current > 0 &&
+ !['TRK', 'VW', 'APP'].includes(component.breadcrumbs[current].qualifier)
+ ) {
+ current--;
+ }
+ return component.breadcrumbs[current].key;
+ };
+
+ fetchData = () => {
+ this.setState({ loading: true });
+ Promise.all([
+ getProjectActivity({
+ branch: this.props.branch,
+ project: this.getTopLevelComponent(),
+ ps: PAGE_SIZE
+ }),
+ getAllMetrics()
+ ]).then(
+ ([{ analyses }, metrics]) => {
+ if (this.mounted) {
+ this.setState({ analyses, metrics, loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ renderList(analyses: IAnalysis[]) {
+ if (!analyses.length) {
+ return <p className="spacer-top note">{translate('no_results')}</p>;
+ }
+
+ return (
+ <ul className="spacer-top">
+ {analyses.map(analysis => (
+ <Analysis key={analysis.key} analysis={analysis} qualifier={this.props.qualifier} />
+ ))}
+ </ul>
+ );
+ }
+
+ render() {
+ const { analyses, loading } = this.state;
+
+ if (loading) {
+ return null;
+ }
+
+ return (
+ <div className="overview-meta-card">
+ <h4 className="overview-meta-header">{translate('project_activity.page')}</h4>
+
+ <PreviewGraph
+ branch={this.props.branch}
+ history={this.props.history}
+ project={this.props.component.key}
+ metrics={this.state.metrics}
+ />
+
+ {this.renderList(analyses)}
+
+ <div className="spacer-top small">
+ <Link
+ to={{
+ pathname: '/project/activity',
+ query: { id: this.props.component.key, branch: this.props.branch }
+ }}>
+ {translate('show_more')}
+ </Link>
+ </div>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { sortBy } from 'lodash';
-import Event from './Event';
-import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Analysis as AnalysisType, Event as EventType } from '../../projectActivity/types'; */
-
-/*::
-type Props = {
- analysis: AnalysisType,
- qualifier: string
-};
-*/
-
-export default function Analysis(props /*: Props */) {
- const { analysis } = props;
- const sortedEvents /*: Array<EventType> */ = sortBy(
- analysis.events,
- // versions first
- (event /*: EventType */) => (event.category === 'VERSION' ? 0 : 1),
- // then the rest sorted by category
- 'category'
- );
-
- // use `TRK` for all components but applications
- const qualifier = props.qualifier === 'APP' ? 'APP' : 'TRK';
-
- return (
- <li className="overview-analysis">
- <div className="small little-spacer-bottom">
- <strong>
- <DateTooltipFormatter date={analysis.date} placement="right" />
- </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.analyzed', qualifier)}</span>
- )}
- </li>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { sortBy } from 'lodash';
+import Event from './Event';
+import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter';
+import { Analysis as IAnalysis, Event as IEvent } from '../../../api/projectActivity';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ analysis: IAnalysis;
+ qualifier: string;
+}
+
+export default function Analysis({ analysis, ...props }: Props) {
+ const sortedEvents: Array<IEvent> = sortBy(
+ analysis.events,
+ // versions first
+ (event: IEvent) => (event.category === 'VERSION' ? 0 : 1),
+ // then the rest sorted by category
+ 'category'
+ );
+
+ // use `TRK` for all components but applications
+ const qualifier = props.qualifier === 'APP' ? 'APP' : 'TRK';
+
+ return (
+ <li className="overview-analysis">
+ <div className="small little-spacer-bottom">
+ <strong>
+ <DateTooltipFormatter date={analysis.date} placement="right" />
+ </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.analyzed', qualifier)}</span>
+ )}
+ </li>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import Tooltip from '../../../components/controls/Tooltip';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Event as EventType } from '../../projectActivity/types'; */
-
-export default function Event(props /*: { event: EventType } */) {
- const { event } = props;
-
- if (event.category === 'VERSION') {
- return (
- <Tooltip overlay={`${translate('version')} ${props.event.name}`} mouseEnterDelay={0.5}>
- <span className="overview-analysis-event badge">{props.event.name}</span>
- </Tooltip>
- );
- }
-
- return (
- <div className="overview-analysis-event">
- <span className="note">{translate('event.category', event.category)}:</span>{' '}
- {event.description ? (
- <Tooltip overlay={event.description} placement="left" mouseEnterDelay={0.5}>
- <strong>{event.name}</strong>
- </Tooltip>
- ) : (
- <strong>{event.name}</strong>
- )}
- </div>
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import Tooltip from '../../../components/controls/Tooltip';
+import { Event as IEvent } from '../../../api/projectActivity';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ event: IEvent;
+}
+
+export default function Event({ event }: Props) {
+ if (event.category === 'VERSION') {
+ return (
+ <Tooltip overlay={`${translate('version')} ${event.name}`} mouseEnterDelay={0.5}>
+ <span className="overview-analysis-event badge">{event.name}</span>
+ </Tooltip>
+ );
+ }
+
+ return (
+ <div className="overview-analysis-event">
+ <span className="note">{translate('event.category', event.category)}:</span>{' '}
+ {event.description ? (
+ <Tooltip overlay={event.description} placement="left" mouseEnterDelay={0.5}>
+ <strong>{event.name}</strong>
+ </Tooltip>
+ ) : (
+ <strong>{event.name}</strong>
+ )}
+ </div>
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import Analysis from '../Analysis';
-
-const ANALYSIS = {
- key: '1',
- date: '2017-06-10T16:10:59+0200',
- events: [
- { key: '1', category: 'OTHER', name: 'test' },
- { key: '2', category: 'VERSION', name: '6.5-SNAPSHOT' }
- ]
-};
-
-it('should sort the events with version first', () => {
- expect(shallow(<Analysis analysis={ANALYSIS} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Analysis from '../Analysis';
+
+const ANALYSIS = {
+ key: '1',
+ date: '2017-06-10T16:10:59+0200',
+ events: [
+ { key: '1', category: 'OTHER', name: 'test' },
+ { key: '2', category: 'VERSION', name: '6.5-SNAPSHOT' }
+ ]
+};
+
+it('should sort the events with version first', () => {
+ expect(shallow(<Analysis analysis={ANALYSIS} qualifier="TRK" />)).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import Event from '../Event';
-
-const EVENT = { key: '1', category: 'OTHER', name: 'test' };
-const VERSION = { key: '2', category: 'VERSION', name: '6.5-SNAPSHOT' };
-
-it('should render an event correctly', () => {
- expect(shallow(<Event event={EVENT} />)).toMatchSnapshot();
-});
-
-it('should render a version correctly', () => {
- expect(shallow(<Event event={VERSION} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Event from '../Event';
+
+const EVENT = { key: '1', category: 'OTHER', name: 'test' };
+const VERSION = { key: '2', category: 'VERSION', name: '6.5-SNAPSHOT' };
+
+it('should render an event correctly', () => {
+ expect(shallow(<Event event={EVENT} />)).toMatchSnapshot();
+});
+
+it('should render a version correctly', () => {
+ expect(shallow(<Event event={VERSION} />)).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should sort the events with version first 1`] = `
-<li
- className="overview-analysis"
->
- <div
- className="small little-spacer-bottom"
- >
- <strong>
- <DateTooltipFormatter
- date="2017-06-10T16:10:59+0200"
- placement="right"
- />
- </strong>
- </div>
- <div
- className="project-activity-events"
- >
- <Event
- event={
- Object {
- "category": "VERSION",
- "key": "2",
- "name": "6.5-SNAPSHOT",
- }
- }
- key="2"
- />
- <Event
- event={
- Object {
- "category": "OTHER",
- "key": "1",
- "name": "test",
- }
- }
- key="1"
- />
- </div>
-</li>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should sort the events with version first 1`] = `
+<li
+ className="overview-analysis"
+>
+ <div
+ className="small little-spacer-bottom"
+ >
+ <strong>
+ <DateTooltipFormatter
+ date="2017-06-10T16:10:59+0200"
+ placement="right"
+ />
+ </strong>
+ </div>
+ <div
+ className="project-activity-events"
+ >
+ <Event
+ event={
+ Object {
+ "category": "VERSION",
+ "key": "2",
+ "name": "6.5-SNAPSHOT",
+ }
+ }
+ key="2"
+ />
+ <Event
+ event={
+ Object {
+ "category": "OTHER",
+ "key": "1",
+ "name": "test",
+ }
+ }
+ key="1"
+ />
+ </div>
+</li>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render a version correctly 1`] = `
-<Tooltip
- mouseEnterDelay={0.5}
- overlay="version 6.5-SNAPSHOT"
- placement="bottom"
->
- <span
- className="overview-analysis-event badge"
- >
- 6.5-SNAPSHOT
- </span>
-</Tooltip>
-`;
-
-exports[`should render an event correctly 1`] = `
-<div
- className="overview-analysis-event"
->
- <span
- className="note"
- >
- event.category.OTHER
- :
- </span>
-
- <strong>
- test
- </strong>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render a version correctly 1`] = `
+<Tooltip
+ mouseEnterDelay={0.5}
+ overlay="version 6.5-SNAPSHOT"
+ placement="bottom"
+>
+ <span
+ className="overview-analysis-event badge"
+ >
+ 6.5-SNAPSHOT
+ </span>
+</Tooltip>
+`;
+
+exports[`should render an event correctly 1`] = `
+<div
+ className="overview-analysis-event"
+>
+ <span
+ className="note"
+ >
+ event.category.OTHER
+ :
+ </span>
+
+ <strong>
+ test
+ </strong>
+</div>
+`;
*/
import React from 'react';
import enhance from './enhance';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getMetricName } from '../helpers/metrics';
import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';
*/
import React from 'react';
import enhance from './enhance';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getMetricName } from '../helpers/metrics';
import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { Link } from 'react-router';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import BubblesIcon from '../../../components/icons-components/BubblesIcon';
-import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
-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,
- isDiffMetric,
- getPeriodValue,
- getShortType,
- getRatingTooltip
-} from '../../../helpers/measures';
-import { translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
-import { getPeriodDate } from '../../../helpers/periods';
-import {
- getComponentDrilldownUrl,
- getComponentIssuesUrl,
- getMeasureHistoryUrl
-} from '../../../helpers/urls';
-
-export default function enhance(ComposedComponent) {
- return class extends React.PureComponent {
- static displayName = `enhance(${ComposedComponent.displayName})}`;
-
- getValue = measure => {
- const { leakPeriod } = this.props;
-
- if (!measure) {
- return 0;
- }
-
- return isDiffMetric(measure.metric.key)
- ? getPeriodValue(measure, leakPeriod.index)
- : measure.value;
- };
-
- renderHeader = (domain, label) => {
- const { branch, component } = this.props;
- return (
- <div className="overview-card-header">
- <div className="overview-title">
- <span>{label}</span>
- <Link
- className="button button-small spacer-left text-text-bottom"
- to={getComponentDrilldownUrl(component.key, domain, branch)}>
- <BubblesIcon size={14} />
- </Link>
- </div>
- </div>
- );
- };
-
- renderMeasure = metricKey => {
- const { branch, measures, component } = this.props;
- const measure = measures.find(measure => measure.metric.key === metricKey);
-
- if (measure == null) {
- return null;
- }
-
- return (
- <div className="overview-domain-measure">
- <div className="overview-domain-measure-value">
- <DrilldownLink branch={branch} component={component.key} metric={metricKey}>
- <span className="js-overview-main-tests">
- {formatMeasure(measure.value, getShortType(measure.metric.type))}
- </span>
- </DrilldownLink>
- </div>
-
- <div className="overview-domain-measure-label offset-left">
- {getLocalizedMetricName(measure.metric)}
- {this.renderHistoryLink(measure.metric.key)}
- </div>
- </div>
- );
- };
-
- renderMeasureVariation = (metricKey, customLabel) => {
- const NO_VALUE = '—';
- const { measures, leakPeriod } = this.props;
- const measure = measures.find(measure => measure.metric.key === metricKey);
- const periodValue = getPeriodValue(measure, leakPeriod.index);
- const formatted =
- periodValue != null
- ? formatMeasureVariation(periodValue, getShortType(measure.metric.type))
- : NO_VALUE;
- return (
- <div className="overview-domain-measure">
- <div className="overview-domain-measure-value">{formatted}</div>
-
- <div className="overview-domain-measure-label">{customLabel || measure.metric.name}</div>
- </div>
- );
- };
-
- renderRating = metricKey => {
- const { branch, component, measures } = this.props;
- const measure = measures.find(measure => measure.metric.key === metricKey);
- if (!measure) {
- return null;
- }
- const value = this.getValue(measure);
- const title = getRatingTooltip(metricKey, value);
- return (
- <Tooltip overlay={title} placement="top">
- <div className="overview-domain-measure-sup">
- <DrilldownLink
- branch={branch}
- className="link-no-underline"
- component={component.key}
- metric={metricKey}>
- <Rating value={value} />
- </DrilldownLink>
- </div>
- </Tooltip>
- );
- };
-
- renderIssues = (metric, type) => {
- const { branch, measures, component } = this.props;
- const measure = measures.find(measure => measure.metric.key === metric);
- const value = this.getValue(measure);
- const params = { branch, resolved: 'false', types: type };
- if (isDiffMetric(metric)) {
- Object.assign(params, { sinceLeakPeriod: 'true' });
- }
-
- const tooltip = (
- <DateTimeFormatter date={component.analysisDate}>
- {formattedAnalysisDate => (
- <span>
- {translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate)}
- </span>
- )}
- </DateTimeFormatter>
- );
-
- return (
- <Tooltip overlay={tooltip} placement="top">
- <Link to={getComponentIssuesUrl(component.key, params)}>
- {formatMeasure(value, 'SHORT_INT')}
- </Link>
- </Tooltip>
- );
- };
-
- renderHistoryLink = metricKey => {
- const linkClass = 'button button-small spacer-left overview-domain-measure-history-link';
- return (
- <Link
- className={linkClass}
- to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}>
- <HistoryIcon />
- </Link>
- );
- };
-
- renderTimeline = (metricKey, range, children) => {
- if (!this.props.history) {
- return null;
- }
- const history = this.props.history[metricKey];
- if (!history) {
- return null;
- }
- const props = {
- history,
- [range]: getPeriodDate(this.props.leakPeriod)
- };
- return (
- <div className="overview-domain-timeline">
- <Timeline {...props} />
- {children}
- </div>
- );
- };
-
- render() {
- return (
- <ComposedComponent
- {...this.props}
- getValue={this.getValue}
- renderHeader={this.renderHeader}
- renderHistoryLink={this.renderHistoryLink}
- renderMeasure={this.renderMeasure}
- renderMeasureVariation={this.renderMeasureVariation}
- renderRating={this.renderRating}
- renderIssues={this.renderIssues}
- renderTimeline={this.renderTimeline}
- />
- );
- }
- };
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
+import BubblesIcon from '../../../components/icons-components/BubblesIcon';
+import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
+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,
+ isDiffMetric,
+ getPeriodValue,
+ getShortType,
+ getRatingTooltip,
+ MeasureEnhanced
+} from '../../../helpers/measures';
+import { translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
+import { getPeriodDate } from '../../../helpers/periods';
+import {
+ getComponentDrilldownUrl,
+ getComponentIssuesUrl,
+ getMeasureHistoryUrl
+} from '../../../helpers/urls';
+import { Component } from '../../../app/types';
+import { History } from '../../../api/time-machine';
+
+export interface EnhanceProps {
+ branch?: string;
+ component: Component;
+ measures: MeasureEnhanced[];
+ leakPeriod?: { index: number; date?: string };
+ history?: History;
+ historyStartDate?: Date;
+}
+
+export interface ComposedProps extends EnhanceProps {
+ getValue: (measure: MeasureEnhanced) => string | undefined;
+ renderHeader: (domain: string, label: string) => React.ReactNode;
+ renderMeasure: (metricKey: string) => React.ReactNode;
+ renderRating: (metricKey: string) => React.ReactNode;
+ renderIssues: (metric: string, type: string) => React.ReactNode;
+ renderHistoryLink: (metricKey: string) => React.ReactNode;
+ renderTimeline: (metricKey: string, range: string, children?: React.ReactNode) => React.ReactNode;
+}
+
+export default function enhance(ComposedComponent: React.ComponentType<ComposedProps>) {
+ return class extends React.PureComponent<EnhanceProps> {
+ static displayName = `enhance(${ComposedComponent.displayName})}`;
+
+ getValue = (measure: MeasureEnhanced) => {
+ const { leakPeriod } = this.props;
+ if (!measure) {
+ return '0';
+ }
+ return isDiffMetric(measure.metric.key)
+ ? getPeriodValue(measure, leakPeriod ? leakPeriod.index : 0)
+ : measure.value;
+ };
+
+ renderHeader = (domain: string, label: string) => {
+ const { branch, component } = this.props;
+ return (
+ <div className="overview-card-header">
+ <div className="overview-title">
+ <span>{label}</span>
+ <Link
+ className="button button-small spacer-left text-text-bottom"
+ to={getComponentDrilldownUrl(component.key, domain, branch)}>
+ <BubblesIcon size={14} />
+ </Link>
+ </div>
+ </div>
+ );
+ };
+
+ renderMeasure = (metricKey: string) => {
+ const { branch, measures, component } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metricKey);
+ if (!measure) {
+ return null;
+ }
+
+ return (
+ <div className="overview-domain-measure">
+ <div className="overview-domain-measure-value">
+ <DrilldownLink branch={branch} component={component.key} metric={metricKey}>
+ <span className="js-overview-main-tests">
+ {formatMeasure(measure.value, getShortType(measure.metric.type))}
+ </span>
+ </DrilldownLink>
+ </div>
+
+ <div className="overview-domain-measure-label offset-left">
+ {getLocalizedMetricName(measure.metric)}
+ {this.renderHistoryLink(measure.metric.key)}
+ </div>
+ </div>
+ );
+ };
+
+ renderRating = (metricKey: string) => {
+ const { branch, component, measures } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metricKey);
+ if (!measure) {
+ return null;
+ }
+
+ const value = this.getValue(measure);
+ const title = value && getRatingTooltip(metricKey, value);
+ return (
+ <Tooltip overlay={title} placement="top">
+ <div className="overview-domain-measure-sup">
+ <DrilldownLink
+ branch={branch}
+ className="link-no-underline"
+ component={component.key}
+ metric={metricKey}>
+ <Rating value={value} />
+ </DrilldownLink>
+ </div>
+ </Tooltip>
+ );
+ };
+
+ renderIssues = (metric: string, type: string) => {
+ const { branch, measures, component } = this.props;
+ const measure = measures.find(measure => measure.metric.key === metric);
+ if (!measure) {
+ return null;
+ }
+
+ const value = this.getValue(measure);
+ const params = { branch, resolved: 'false', types: type };
+ if (isDiffMetric(metric)) {
+ Object.assign(params, { sinceLeakPeriod: 'true' });
+ }
+
+ const tooltip = component.analysisDate && (
+ <DateTimeFormatter date={component.analysisDate}>
+ {formattedAnalysisDate => (
+ <span>
+ {translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate)}
+ </span>
+ )}
+ </DateTimeFormatter>
+ );
+
+ return (
+ <Tooltip overlay={tooltip} placement="top">
+ <Link to={getComponentIssuesUrl(component.key, params)}>
+ {formatMeasure(value, 'SHORT_INT')}
+ </Link>
+ </Tooltip>
+ );
+ };
+
+ renderHistoryLink = (metricKey: string) => {
+ const linkClass = 'button button-small spacer-left overview-domain-measure-history-link';
+ return (
+ <Link
+ className={linkClass}
+ to={getMeasureHistoryUrl(this.props.component.key, metricKey, this.props.branch)}>
+ <HistoryIcon />
+ </Link>
+ );
+ };
+
+ renderTimeline = (metricKey: string, range: 'before' | 'after', children?: React.ReactNode) => {
+ if (!this.props.history) {
+ return null;
+ }
+ const history = this.props.history[metricKey];
+ if (!history) {
+ return null;
+ }
+ const props = { history, [range]: getPeriodDate(this.props.leakPeriod) };
+ return (
+ <div className="overview-domain-timeline">
+ <Timeline {...props} />
+ {children}
+ </div>
+ );
+ };
+
+ render() {
+ return (
+ <ComposedComponent
+ {...this.props}
+ getValue={this.getValue}
+ renderHeader={this.renderHeader}
+ renderHistoryLink={this.renderHistoryLink}
+ renderMeasure={this.renderMeasure}
+ renderRating={this.renderRating}
+ renderIssues={this.renderIssues}
+ renderTimeline={this.renderTimeline}
+ />
+ );
+ }
+ };
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-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 AnalysesList from '../events/AnalysesList';
-import MetaSize from './MetaSize';
-import MetaTags from './MetaTags';
-import BadgesModal from '../badges/BadgesModal';
-import { areThereCustomOrganizations, getGlobalSettingValue } from '../../../store/rootReducer';
-import { Visibility } from '../../../app/types';
-
-const Meta = ({
- branch,
- component,
- history,
- measures,
- areThereCustomOrganizations,
- onComponentChange,
- onSonarCloud
-}) => {
- const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
-
- const isProject = qualifier === 'TRK';
- const isPrivate = visibility === Visibility.Private;
-
- const hasDescription = !!description;
- const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
- const hasQualityGate = !!qualityGate;
-
- const shouldShowQualityProfiles = isProject && hasQualityProfiles;
- const shouldShowQualityGate = isProject && hasQualityGate;
- const hasOrganization = component.organization != null && areThereCustomOrganizations;
-
- return (
- <div className="overview-meta">
- {hasDescription && (
- <div className="overview-meta-card overview-meta-description">{description}</div>
- )}
-
- <MetaSize branch={branch} component={component} measures={measures} />
-
- {isProject && <MetaTags component={component} onComponentChange={onComponentChange} />}
-
- <AnalysesList
- branch={branch}
- component={component}
- qualifier={component.qualifier}
- history={history}
- />
-
- {shouldShowQualityGate && (
- <MetaQualityGate
- gate={qualityGate}
- organization={hasOrganization && component.organization}
- />
- )}
-
- {shouldShowQualityProfiles && (
- <MetaQualityProfiles
- component={component}
- customOrganizations={areThereCustomOrganizations}
- profiles={qualityProfiles}
- />
- )}
-
- {isProject && <MetaLinks component={component} />}
-
- <MetaKey component={component} />
-
- {hasOrganization && <MetaOrganizationKey component={component} />}
-
- {onSonarCloud &&
- isProject &&
- !isPrivate && <BadgesModal branch={branch} project={component.key} />}
- </div>
- );
-};
-
-const mapStateToProps = state => {
- const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
- return {
- areThereCustomOrganizations: areThereCustomOrganizations(state),
- onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
- };
-};
-
-export default connect(mapStateToProps)(Meta);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+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 AnalysesList from '../events/AnalysesList';
+import MetaSize from './MetaSize';
+import MetaTags from './MetaTags';
+import BadgesModal from '../badges/BadgesModal';
+import { areThereCustomOrganizations, getGlobalSettingValue } from '../../../store/rootReducer';
+import { Visibility, Component } from '../../../app/types';
+import { History } from '../../../api/time-machine';
+import { MeasureEnhanced } from '../../../helpers/measures';
+
+interface OwnProps {
+ branch?: string;
+ component: Component;
+ history?: History;
+ measures: MeasureEnhanced[];
+ onComponentChange: (changes: {}) => void;
+}
+
+interface StateToProps {
+ areThereCustomOrganizations: boolean;
+ onSonarCloud: boolean;
+}
+
+export function Meta(props: OwnProps & StateToProps) {
+ const { branch, component, areThereCustomOrganizations } = props;
+ const { qualifier, description, qualityProfiles, qualityGate, visibility } = component;
+
+ const isProject = qualifier === 'TRK';
+ const isPrivate = visibility === Visibility.Private;
+
+ const hasDescription = !!description;
+ const hasQualityProfiles = Array.isArray(qualityProfiles) && qualityProfiles.length > 0;
+ const hasQualityGate = !!qualityGate;
+
+ const shouldShowQualityProfiles = isProject && hasQualityProfiles;
+ const shouldShowQualityGate = isProject && hasQualityGate;
+ const hasOrganization = component.organization != null && areThereCustomOrganizations;
+
+ return (
+ <div className="overview-meta">
+ {hasDescription && (
+ <div className="overview-meta-card overview-meta-description">{description}</div>
+ )}
+
+ <MetaSize branch={branch} component={component} measures={props.measures} />
+
+ {isProject && <MetaTags component={component} onComponentChange={props.onComponentChange} />}
+
+ <AnalysesList
+ branch={branch}
+ component={component}
+ qualifier={component.qualifier}
+ history={props.history}
+ />
+
+ {shouldShowQualityGate && (
+ <MetaQualityGate
+ gate={qualityGate}
+ organization={hasOrganization && component.organization}
+ />
+ )}
+
+ {shouldShowQualityProfiles && (
+ <MetaQualityProfiles
+ component={component}
+ customOrganizations={areThereCustomOrganizations}
+ profiles={qualityProfiles}
+ />
+ )}
+
+ {isProject && <MetaLinks component={component} />}
+
+ <MetaKey component={component} />
+
+ {hasOrganization && <MetaOrganizationKey component={component} />}
+
+ {props.onSonarCloud &&
+ isProject &&
+ !isPrivate && <BadgesModal branch={branch} project={component.key} />}
+ </div>
+ );
+}
+
+const mapStateToProps = (state: any): StateToProps => {
+ const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
+ return {
+ areThereCustomOrganizations: areThereCustomOrganizations(state),
+ onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
+ };
+};
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(Meta);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { isProvided, isClickable } from '../../project-admin/links/utils';
-import BugTrackerIcon from '../../../components/ui/BugTrackerIcon';
-
-/*::
-type Link = {
- id: string,
- name: string,
- url: string,
- type: string
-};
-*/
-
-/*::
-type State = {|
- expanded: boolean
-|};
-*/
-
-export default class MetaLink extends React.PureComponent {
- /*:: props: {
- link: Link
- };
-*/
-
- state /*: State */ = {
- expanded: false
- };
-
- handleClick = (e /*: Object */) => {
- e.preventDefault();
- e.target.blur();
- this.setState((s /*: State */) => ({ expanded: !s.expanded }));
- };
-
- renderLinkIcon(link /*: Link */) {
- if (link.type === 'issue') {
- return <BugTrackerIcon />;
- }
-
- return isProvided(link) ? (
- <i className={`icon-color-link icon-${link.type}`} />
- ) : (
- <i className="icon-color-link icon-detach" />
- );
- }
-
- render() {
- const { link } = this.props;
-
- return (
- <li>
- <a
- className="link-with-icon"
- href={link.url}
- target="_blank"
- onClick={!isClickable(link) ? this.handleClick : undefined}>
- {this.renderLinkIcon(link)}
-
- {link.name}
- </a>
- {this.state.expanded && (
- <div className="little-spacer-top">
- <input
- type="text"
- className="overview-key"
- value={link.url}
- readOnly={true}
- onClick={e => e.target.select()}
- />
- </div>
- )}
- </li>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { isProvided, isClickable } from '../../project-admin/links/utils';
+import BugTrackerIcon from '../../../components/ui/BugTrackerIcon';
+import { ProjectLink } from '../../../api/projectLinks';
+
+interface Props {
+ link: ProjectLink;
+}
+
+interface State {
+ expanded: boolean;
+}
+
+export default class MetaLink extends React.PureComponent<Props, State> {
+ state: State = { expanded: false };
+
+ handleClick = (e: React.SyntheticEvent<HTMLAnchorElement>) => {
+ e.preventDefault();
+ e.currentTarget.blur();
+ this.setState((s: State) => ({ expanded: !s.expanded }));
+ };
+
+ handleInputClick = (e: React.SyntheticEvent<HTMLInputElement>) => {
+ e.currentTarget.select();
+ };
+
+ renderLinkIcon = (link: ProjectLink) => {
+ if (link.type === 'issue') {
+ return <BugTrackerIcon />;
+ }
+
+ return isProvided(link) ? (
+ <i className={`icon-color-link icon-${link.type}`} />
+ ) : (
+ <i className="icon-color-link icon-detach" />
+ );
+ };
+
+ render() {
+ const { link } = this.props;
+
+ return (
+ <li>
+ <a
+ className="link-with-icon"
+ href={link.url}
+ target="_blank"
+ onClick={!isClickable(link) ? this.handleClick : undefined}>
+ {this.renderLinkIcon(link)}
+
+ {link.name}
+ </a>
+ {this.state.expanded && (
+ <div className="little-spacer-top">
+ <input
+ type="text"
+ className="overview-key"
+ value={link.url}
+ readOnly={true}
+ onClick={this.handleInputClick}
+ />
+ </div>
+ )}
+ </li>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import MetaLink from './MetaLink';
-import { getProjectLinks } from '../../../api/projectLinks';
-import { orderLinks } from '../../project-admin/links/utils';
-
-export default class MetaLinks extends React.PureComponent {
- static propTypes = {
- component: PropTypes.object.isRequired
- };
-
- state = {};
-
- componentDidMount() {
- this.mounted = true;
- this.loadLinks();
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.component.key !== this.props.component.key) {
- this.loadLinks();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- loadLinks() {
- getProjectLinks(this.props.component.key).then(links => {
- if (this.mounted) {
- this.setState({ links });
- }
- });
- }
-
- render() {
- const { links } = this.state;
-
- if (links == null || links.length === 0) {
- return null;
- }
-
- const orderedLinks = orderLinks(links);
-
- return (
- <div className="overview-meta-card">
- <ul className="overview-meta-list">
- {orderedLinks.map(link => <MetaLink key={link.id} link={link} />)}
- </ul>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import MetaLink from './MetaLink';
+import { getProjectLinks, ProjectLink } from '../../../api/projectLinks';
+import { orderLinks } from '../../project-admin/links/utils';
+import { LightComponent } from '../../../app/types';
+
+interface Props {
+ component: LightComponent;
+}
+
+interface State {
+ links?: ProjectLink[];
+}
+
+export default class MetaLinks extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = {};
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadLinks();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.component.key !== this.props.component.key) {
+ this.loadLinks();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadLinks = () =>
+ getProjectLinks(this.props.component.key).then(
+ links => {
+ if (this.mounted) {
+ this.setState({ links });
+ }
+ },
+ () => {}
+ );
+
+ render() {
+ const { links } = this.state;
+
+ if (!links || links.length === 0) {
+ return null;
+ }
+
+ const orderedLinks = orderLinks(links);
+
+ return (
+ <div className="overview-meta-card">
+ <ul className="overview-meta-list">
+ {orderedLinks.map(link => <MetaLink key={link.id} link={link} />)}
+ </ul>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
-import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer';
-import SizeRating from '../../../components/ui/SizeRating';
-import { formatMeasure } from '../../../helpers/measures';
-import { getMetricName } from '../helpers/metrics';
-import { translate } from '../../../helpers/l10n';
-
-export default class MetaSize extends React.PureComponent {
- static propTypes = {
- branch: PropTypes.string,
- component: PropTypes.object.isRequired,
- measures: PropTypes.array.isRequired
- };
-
- renderLoC = ncloc => (
- <div
- id="overview-ncloc"
- className={classNames('overview-meta-size-ncloc', {
- 'is-half-width': this.props.component.qualifier === 'APP'
- })}>
- <span className="spacer-right">
- <SizeRating value={ncloc.value} />
- </span>
- <DrilldownLink branch={this.props.branch} component={this.props.component.key} metric="ncloc">
- {formatMeasure(ncloc.value, 'SHORT_INT')}
- </DrilldownLink>
- <div className="spacer-top text-muted">{getMetricName('ncloc')}</div>
- </div>
- );
-
- renderLoCDistribution = () => {
- const languageDistribution = this.props.measures.find(
- measure => measure.metric.key === 'ncloc_language_distribution'
- );
-
- const className =
- this.props.component.qualifier === 'TRK' ? 'overview-meta-size-lang-dist' : 'big-spacer-top';
-
- return languageDistribution ? (
- <div id="overview-language-distribution" className={className}>
- <LanguageDistributionContainer distribution={languageDistribution.value} width={160} />
- </div>
- ) : null;
- };
-
- renderProjects = () => {
- const projects = this.props.measures.find(measure => measure.metric.key === 'projects');
-
- return projects ? (
- <div id="overview-projects" className="overview-meta-size-ncloc is-half-width">
- <DrilldownLink
- branch={this.props.branch}
- component={this.props.component.key}
- metric="projects">
- {formatMeasure(projects.value, 'SHORT_INT')}
- </DrilldownLink>
- <div className="spacer-top text-muted">{translate('metric.projects.name')}</div>
- </div>
- ) : null;
- };
-
- render() {
- const ncloc = this.props.measures.find(measure => measure.metric.key === 'ncloc');
-
- if (ncloc == null) {
- return null;
- }
-
- return (
- <div id="overview-size" className="overview-meta-card">
- {this.props.component.qualifier === 'APP' && this.renderProjects()}
- {this.renderLoC(ncloc)}
- {this.renderLoCDistribution()}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
+import LanguageDistributionContainer from '../../../components/charts/LanguageDistributionContainer';
+import SizeRating from '../../../components/ui/SizeRating';
+import { formatMeasure, MeasureEnhanced } from '../../../helpers/measures';
+import { getMetricName } from '../helpers/metrics';
+import { translate } from '../../../helpers/l10n';
+import { LightComponent } from '../../../app/types';
+
+interface Props {
+ branch?: string;
+ component: LightComponent;
+ measures: MeasureEnhanced[];
+}
+
+export default class MetaSize extends React.PureComponent<Props> {
+ renderLoC = (ncloc: MeasureEnhanced) => (
+ <div
+ id="overview-ncloc"
+ className={classNames('overview-meta-size-ncloc', {
+ 'is-half-width': this.props.component.qualifier === 'APP'
+ })}>
+ <span className="spacer-right">
+ <SizeRating value={Number(ncloc.value)} />
+ </span>
+ <DrilldownLink branch={this.props.branch} component={this.props.component.key} metric="ncloc">
+ {formatMeasure(ncloc.value, 'SHORT_INT')}
+ </DrilldownLink>
+ <div className="spacer-top text-muted">{getMetricName('ncloc')}</div>
+ </div>
+ );
+
+ renderLoCDistribution = () => {
+ const languageDistribution = this.props.measures.find(
+ measure => measure.metric.key === 'ncloc_language_distribution'
+ );
+
+ const className =
+ this.props.component.qualifier === 'TRK' ? 'overview-meta-size-lang-dist' : 'big-spacer-top';
+
+ return languageDistribution ? (
+ <div id="overview-language-distribution" className={className}>
+ <LanguageDistributionContainer distribution={languageDistribution.value} width={160} />
+ </div>
+ ) : null;
+ };
+
+ renderProjects = () => {
+ const projects = this.props.measures.find(measure => measure.metric.key === 'projects');
+
+ return projects ? (
+ <div id="overview-projects" className="overview-meta-size-ncloc is-half-width">
+ <DrilldownLink
+ branch={this.props.branch}
+ component={this.props.component.key}
+ metric="projects">
+ {formatMeasure(projects.value, 'SHORT_INT')}
+ </DrilldownLink>
+ <div className="spacer-top text-muted">{translate('metric.projects.name')}</div>
+ </div>
+ ) : null;
+ };
+
+ render() {
+ const ncloc = this.props.measures.find(measure => measure.metric.key === 'ncloc');
+
+ if (ncloc == null) {
+ return null;
+ }
+
+ return (
+ <div id="overview-size" className="overview-meta-card">
+ {this.props.component.qualifier === 'APP' && this.renderProjects()}
+ {this.renderLoC(ncloc)}
+ {this.renderLoCDistribution()}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-//@flow
-import React from 'react';
-import { setProjectTags } from '../../../api/components';
-import { translate } from '../../../helpers/l10n';
-import TagsList from '../../../components/tags/TagsList';
-import MetaTagsSelector from './MetaTagsSelector';
-
-/*::
-type Props = {
- component: {
- key: string,
- tags: Array<string>,
- configuration?: {
- showSettings?: boolean
- }
- },
- onComponentChange: {} => void
-};
-*/
-
-/*::
-type State = {
- popupOpen: boolean,
- popupPosition: { top: number, right: number }
-};
-*/
-
-export default class MetaTags extends React.PureComponent {
- /*:: card: HTMLElement; */
- /*:: tagsList: HTMLElement; */
- /*:: tagsSelector: HTMLElement; */
- /*:: props: Props; */
- state /*: State */ = {
- popupOpen: false,
- popupPosition: {
- top: 0,
- right: 0
- }
- };
-
- componentDidMount() {
- if (this.canUpdateTags()) {
- const buttonPos = this.tagsList.getBoundingClientRect();
- const cardPos = this.card.getBoundingClientRect();
- this.setState({ popupPosition: this.getPopupPos(buttonPos, cardPos) });
-
- window.addEventListener('keydown', this.handleKey, false);
- window.addEventListener('click', this.handleOutsideClick, false);
- }
- }
-
- componentWillUnmount() {
- window.removeEventListener('keydown', this.handleKey);
- window.removeEventListener('click', this.handleOutsideClick);
- }
-
- handleKey = (evt /*: KeyboardEvent */) => {
- // Escape key
- if (evt.keyCode === 27) {
- this.setState({ popupOpen: false });
- }
- };
-
- handleOutsideClick = (evt /*: SyntheticInputEvent */) => {
- if (!this.tagsSelector || !this.tagsSelector.contains(evt.target)) {
- this.setState({ popupOpen: false });
- }
- };
-
- handleClick = (evt /*: MouseEvent */) => {
- evt.stopPropagation();
- this.setState(state => ({ popupOpen: !state.popupOpen }));
- };
-
- canUpdateTags() {
- const { configuration } = this.props.component;
- return configuration && configuration.showSettings;
- }
-
- getPopupPos(
- eltPos /*: { height: number, width: number } */,
- containerPos /*: { width: number } */
- ) {
- return {
- top: eltPos.height,
- right: containerPos.width - eltPos.width
- };
- }
-
- handleSetProjectTags = (tags /*: Array<string> */) => {
- setProjectTags({ project: this.props.component.key, tags: tags.join(',') }).then(
- () => this.props.onComponentChange({ tags }),
- () => {}
- );
- };
-
- render() {
- const { tags, key } = this.props.component;
- const { popupOpen, popupPosition } = this.state;
-
- if (this.canUpdateTags()) {
- return (
- <div className="overview-meta-card overview-meta-tags" ref={card => (this.card = card)}>
- <button
- className="button-link"
- onClick={this.handleClick}
- ref={tagsList => (this.tagsList = tagsList)}>
- <TagsList tags={tags.length ? tags : [translate('no_tags')]} allowUpdate={true} />
- </button>
- {popupOpen && (
- <div ref={tagsSelector => (this.tagsSelector = tagsSelector)}>
- <MetaTagsSelector
- position={popupPosition}
- project={key}
- selectedTags={tags}
- setProjectTags={this.handleSetProjectTags}
- />
- </div>
- )}
- </div>
- );
- } else {
- return (
- <div className="overview-meta-card overview-meta-tags">
- <TagsList tags={tags.length ? tags : [translate('no_tags')]} allowUpdate={false} />
- </div>
- );
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { setProjectTags } from '../../../api/components';
+import { translate } from '../../../helpers/l10n';
+import TagsList from '../../../components/tags/TagsList';
+import MetaTagsSelector from './MetaTagsSelector';
+import { BubblePopupPosition } from '../../../components/common/BubblePopup';
+import { Component } from '../../../app/types';
+
+interface Props {
+ component: Component;
+ onComponentChange: (changes: {}) => void;
+}
+
+interface State {
+ popupOpen: boolean;
+ popupPosition: BubblePopupPosition;
+}
+
+export default class MetaTags extends React.PureComponent<Props, State> {
+ card: HTMLDivElement | null;
+ tagsList: HTMLButtonElement | null;
+ tagsSelector: HTMLDivElement | null;
+ state: State = { popupOpen: false, popupPosition: { top: 0, right: 0 } };
+
+ componentDidMount() {
+ if (this.canUpdateTags() && this.tagsList && this.card) {
+ const buttonPos = this.tagsList.getBoundingClientRect();
+ const cardPos = this.card.getBoundingClientRect();
+ this.setState({ popupPosition: this.getPopupPos(buttonPos, cardPos) });
+
+ window.addEventListener('keydown', this.handleKey, false);
+ window.addEventListener('click', this.handleOutsideClick, false);
+ }
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('keydown', this.handleKey);
+ window.removeEventListener('click', this.handleOutsideClick);
+ }
+
+ handleKey = (evt: KeyboardEvent) => {
+ // Escape key
+ if (evt.keyCode === 27) {
+ this.setState({ popupOpen: false });
+ }
+ };
+
+ handleOutsideClick = (evt: Event) => {
+ if (!this.tagsSelector || !this.tagsSelector.contains(evt.target as Node)) {
+ this.setState({ popupOpen: false });
+ }
+ };
+
+ handleClick = (evt: React.SyntheticEvent<HTMLButtonElement>) => {
+ evt.stopPropagation();
+ this.setState(state => ({ popupOpen: !state.popupOpen }));
+ };
+
+ canUpdateTags = () => {
+ const { configuration } = this.props.component;
+ return configuration && configuration.showSettings;
+ };
+
+ getPopupPos = (eltPos: ClientRect, containerPos: ClientRect) => ({
+ top: eltPos.height,
+ right: containerPos.width - eltPos.width
+ });
+
+ handleSetProjectTags = (tags: string[]) => {
+ setProjectTags({ project: this.props.component.key, tags: tags.join(',') }).then(
+ () => this.props.onComponentChange({ tags }),
+ () => {}
+ );
+ };
+
+ render() {
+ const { key } = this.props.component;
+ const { popupOpen, popupPosition } = this.state;
+ const tags = this.props.component.tags || [];
+
+ if (this.canUpdateTags()) {
+ return (
+ <div className="overview-meta-card overview-meta-tags" ref={card => (this.card = card)}>
+ <button
+ className="button-link"
+ onClick={this.handleClick}
+ ref={tagsList => (this.tagsList = tagsList)}>
+ <TagsList tags={tags.length ? tags : [translate('no_tags')]} allowUpdate={true} />
+ </button>
+ {popupOpen && (
+ <div ref={tagsSelector => (this.tagsSelector = tagsSelector)}>
+ <MetaTagsSelector
+ position={popupPosition}
+ project={key}
+ selectedTags={tags}
+ setProjectTags={this.handleSetProjectTags}
+ />
+ </div>
+ )}
+ </div>
+ );
+ } else {
+ return (
+ <div className="overview-meta-card overview-meta-tags">
+ <TagsList tags={tags.length ? tags : [translate('no_tags')]} allowUpdate={false} />
+ </div>
+ );
+ }
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-//@flow
-import React from 'react';
-import { without } from 'lodash';
-import TagsSelector from '../../../components/tags/TagsSelector';
-import { searchProjectTags } from '../../../api/components';
-
-/*::
-type Props = {
- position: {},
- project: string,
- selectedTags: Array<string>,
- setProjectTags: (Array<string>) => void
-};
-*/
-
-/*::
-type State = {
- searchResult: Array<string>
-};
-*/
-
-const LIST_SIZE = 10;
-
-export default class MetaTagsSelector extends React.PureComponent {
- /*:: props: Props; */
- state /*: State */ = { searchResult: [] };
-
- componentDidMount() {
- this.onSearch('');
- }
-
- onSearch = (query /*: string */) => {
- searchProjectTags({
- q: query,
- ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, 100)
- }).then(result => {
- this.setState({ searchResult: result.tags });
- });
- };
-
- onSelect = (tag /*: string */) => {
- this.props.setProjectTags([...this.props.selectedTags, tag]);
- };
-
- onUnselect = (tag /*: string */) => {
- this.props.setProjectTags(without(this.props.selectedTags, tag));
- };
-
- render() {
- return (
- <TagsSelector
- position={this.props.position}
- tags={this.state.searchResult}
- selectedTags={this.props.selectedTags}
- listSize={LIST_SIZE}
- onSearch={this.onSearch}
- onSelect={this.onSelect}
- onUnselect={this.onUnselect}
- />
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { without } from 'lodash';
+import TagsSelector from '../../../components/tags/TagsSelector';
+import { BubblePopupPosition } from '../../../components/common/BubblePopup';
+import { searchProjectTags } from '../../../api/components';
+
+interface Props {
+ position: BubblePopupPosition;
+ project: string;
+ selectedTags: string[];
+ setProjectTags: (tags: string[]) => void;
+}
+
+interface State {
+ searchResult: string[];
+}
+
+const LIST_SIZE = 10;
+
+export default class MetaTagsSelector extends React.PureComponent<Props, State> {
+ state: State = { searchResult: [] };
+
+ componentDidMount() {
+ this.onSearch('');
+ }
+
+ onSearch = (query: string) => {
+ searchProjectTags({
+ q: query,
+ ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, 100)
+ }).then(result => this.setState({ searchResult: result.tags }), () => {});
+ };
+
+ onSelect = (tag: string) => {
+ this.props.setProjectTags([...this.props.selectedTags, tag]);
+ };
+
+ onUnselect = (tag: string) => {
+ this.props.setProjectTags(without(this.props.selectedTags, tag));
+ };
+
+ render() {
+ return (
+ <TagsSelector
+ position={this.props.position}
+ tags={this.state.searchResult}
+ selectedTags={this.props.selectedTags}
+ listSize={LIST_SIZE}
+ onSearch={this.onSearch}
+ onSelect={this.onSelect}
+ onUnselect={this.onUnselect}
+ />
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import MetaLink from '../MetaLink';
-import { click } from '../../../../helpers/testUtils';
-
-it('should match snapshot', () => {
- const link = {
- id: '1',
- name: 'Foo',
- url: 'http://example.com',
- type: 'foo'
- };
-
- expect(shallow(<MetaLink link={link} />)).toMatchSnapshot();
-});
-
-it('should expand and collapse link', () => {
- const link = {
- id: '1',
- name: 'Foo',
- url: 'scm:git:git@github.com',
- type: 'foo'
- };
-
- const wrapper = shallow(<MetaLink link={link} />);
- expect(wrapper).toMatchSnapshot();
-
- // expand
- click(wrapper.find('a'));
- expect(wrapper).toMatchSnapshot();
-
- // collapse
- click(wrapper.find('a'));
- expect(wrapper).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import MetaLink from '../MetaLink';
+import { click } from '../../../../helpers/testUtils';
+
+it('should match snapshot', () => {
+ const link = {
+ id: '1',
+ name: 'Foo',
+ url: 'http://example.com',
+ type: 'foo'
+ };
+
+ expect(shallow(<MetaLink link={link} />)).toMatchSnapshot();
+});
+
+it('should expand and collapse link', () => {
+ const link = {
+ id: '1',
+ name: 'Foo',
+ url: 'scm:git:git@github.com',
+ type: 'foo'
+ };
+
+ const wrapper = shallow(<MetaLink link={link} />);
+ expect(wrapper).toMatchSnapshot();
+
+ // expand
+ click(wrapper.find('a'));
+ expect(wrapper).toMatchSnapshot();
+
+ // collapse
+ click(wrapper.find('a'));
+ expect(wrapper).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import { shallow } from 'enzyme';
-import { click } from '../../../../helpers/testUtils';
-import MetaTags from '../MetaTags';
-
-const component = {
- key: 'my-project',
- tags: [],
- configuration: {
- showSettings: false
- }
-};
-
-const componentWithTags = {
- key: 'my-second-project',
- tags: ['foo', 'bar'],
- configuration: {
- showSettings: true
- }
-};
-
-it('should render without tags and admin rights', () => {
- expect(
- shallow(<MetaTags component={component} />, { disableLifecycleMethods: true })
- ).toMatchSnapshot();
-});
-
-it('should render with tags and admin rights', () => {
- expect(
- shallow(<MetaTags component={componentWithTags} />, { disableLifecycleMethods: true })
- ).toMatchSnapshot();
-});
-
-it('should open the tag selector on click', () => {
- const wrapper = shallow(<MetaTags component={componentWithTags} />, {
- disableLifecycleMethods: true
- });
- expect(wrapper).toMatchSnapshot();
-
- // open
- click(wrapper.find('button'));
- expect(wrapper).toMatchSnapshot();
-
- // close
- click(wrapper.find('button'));
- expect(wrapper).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import { click } from '../../../../helpers/testUtils';
+import MetaTags from '../MetaTags';
+
+const component = {
+ key: 'my-project',
+ tags: [],
+ configuration: {
+ showSettings: false
+ },
+ organization: 'foo',
+ qualifier: 'TRK',
+ name: 'MyProject',
+ breadcrumbs: []
+};
+
+const componentWithTags = {
+ key: 'my-second-project',
+ tags: ['foo', 'bar'],
+ configuration: {
+ showSettings: true
+ },
+ organization: 'foo',
+ qualifier: 'TRK',
+ name: 'MySecondProject',
+ breadcrumbs: []
+};
+
+it('should render without tags and admin rights', () => {
+ expect(
+ shallow(<MetaTags component={component} onComponentChange={jest.fn()} />, {
+ disableLifecycleMethods: true
+ })
+ ).toMatchSnapshot();
+});
+
+it('should render with tags and admin rights', () => {
+ expect(
+ shallow(<MetaTags component={componentWithTags} onComponentChange={jest.fn()} />, {
+ disableLifecycleMethods: true
+ })
+ ).toMatchSnapshot();
+});
+
+it('should open the tag selector on click', () => {
+ const wrapper = shallow(
+ <MetaTags component={componentWithTags} onComponentChange={jest.fn()} />,
+ {
+ disableLifecycleMethods: true
+ }
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ // open
+ click(wrapper.find('button'));
+ expect(wrapper).toMatchSnapshot();
+
+ // close
+ click(wrapper.find('button'));
+ expect(wrapper).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-/* eslint-disable import/order, import/first */
-import React from 'react';
-import { mount, shallow } from 'enzyme';
-import MetaTagsSelector from '../MetaTagsSelector';
-
-jest.mock('../../../../api/components', () => ({
- searchProjectTags: jest.fn()
-}));
-
-jest.mock('lodash', () => {
- const lodash = require.requireActual('lodash');
- lodash.debounce = jest.fn(fn => fn);
- return lodash;
-});
-
-import { searchProjectTags } from '../../../../api/components';
-
-it('searches tags on mount', () => {
- searchProjectTags.mockImplementation(() => Promise.resolve({ tags: ['foo', 'bar'] }));
- mount(
- <MetaTagsSelector position={{}} project="foo" selectedTags={[]} setProjectTags={jest.fn()} />
- );
- expect(searchProjectTags).toBeCalledWith({ ps: 9, q: '' });
-});
-
-it('selects and deselects tags', () => {
- const setProjectTags = jest.fn();
- const wrapper = shallow(
- <MetaTagsSelector
- position={{}}
- project="foo"
- selectedTags={['foo', 'bar']}
- setProjectTags={setProjectTags}
- />
- );
-
- wrapper.find('TagsSelector').prop('onSelect')('baz');
- expect(setProjectTags).toHaveBeenLastCalledWith(['foo', 'bar', 'baz']);
-
- // note that the `selectedTags` is a prop and so it wasn't changed
- wrapper.find('TagsSelector').prop('onUnselect')('bar');
- expect(setProjectTags).toHaveBeenLastCalledWith(['foo']);
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/* eslint-disable import/order, import/first */
+import * as React from 'react';
+import { mount, shallow } from 'enzyme';
+import MetaTagsSelector from '../MetaTagsSelector';
+
+jest.mock('../../../../api/components', () => ({
+ searchProjectTags: jest.fn()
+}));
+
+jest.mock('lodash', () => {
+ const lodash = require.requireActual('lodash');
+ lodash.debounce = jest.fn(fn => fn);
+ return lodash;
+});
+
+import { searchProjectTags } from '../../../../api/components';
+
+it('searches tags on mount', () => {
+ (searchProjectTags as jest.Mock).mockImplementation(() =>
+ Promise.resolve({ tags: ['foo', 'bar'] })
+ );
+ mount(
+ <MetaTagsSelector
+ position={{ top: 0, right: 0 }}
+ project="foo"
+ selectedTags={[]}
+ setProjectTags={jest.fn()}
+ />
+ );
+ expect(searchProjectTags).toBeCalledWith({ ps: 9, q: '' });
+});
+
+it('selects and deselects tags', () => {
+ const setProjectTags = jest.fn();
+ const wrapper = shallow(
+ <MetaTagsSelector
+ position={{ top: 0, right: 0 }}
+ project="foo"
+ selectedTags={['foo', 'bar']}
+ setProjectTags={setProjectTags}
+ />
+ );
+
+ const tagSelect: any = wrapper.find('TagsSelector');
+ tagSelect.prop('onSelect')('baz');
+ expect(setProjectTags).toHaveBeenLastCalledWith(['foo', 'bar', 'baz']);
+
+ // note that the `selectedTags` is a prop and so it wasn't changed
+ tagSelect.prop('onUnselect')('bar');
+ expect(setProjectTags).toHaveBeenLastCalledWith(['foo']);
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should expand and collapse link 1`] = `
-<li>
- <a
- className="link-with-icon"
- href="scm:git:git@github.com"
- onClick={[Function]}
- target="_blank"
- >
- <i
- className="icon-color-link icon-detach"
- />
-
- Foo
- </a>
-</li>
-`;
-
-exports[`should expand and collapse link 2`] = `
-<li>
- <a
- className="link-with-icon"
- href="scm:git:git@github.com"
- onClick={[Function]}
- target="_blank"
- >
- <i
- className="icon-color-link icon-detach"
- />
-
- Foo
- </a>
- <div
- className="little-spacer-top"
- >
- <input
- className="overview-key"
- onClick={[Function]}
- readOnly={true}
- type="text"
- value="scm:git:git@github.com"
- />
- </div>
-</li>
-`;
-
-exports[`should expand and collapse link 3`] = `
-<li>
- <a
- className="link-with-icon"
- href="scm:git:git@github.com"
- onClick={[Function]}
- target="_blank"
- >
- <i
- className="icon-color-link icon-detach"
- />
-
- Foo
- </a>
-</li>
-`;
-
-exports[`should match snapshot 1`] = `
-<li>
- <a
- className="link-with-icon"
- href="http://example.com"
- target="_blank"
- >
- <i
- className="icon-color-link icon-detach"
- />
-
- Foo
- </a>
-</li>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should expand and collapse link 1`] = `
+<li>
+ <a
+ className="link-with-icon"
+ href="scm:git:git@github.com"
+ onClick={[Function]}
+ target="_blank"
+ >
+ <i
+ className="icon-color-link icon-detach"
+ />
+
+ Foo
+ </a>
+</li>
+`;
+
+exports[`should expand and collapse link 2`] = `
+<li>
+ <a
+ className="link-with-icon"
+ href="scm:git:git@github.com"
+ onClick={[Function]}
+ target="_blank"
+ >
+ <i
+ className="icon-color-link icon-detach"
+ />
+
+ Foo
+ </a>
+ <div
+ className="little-spacer-top"
+ >
+ <input
+ className="overview-key"
+ onClick={[Function]}
+ readOnly={true}
+ type="text"
+ value="scm:git:git@github.com"
+ />
+ </div>
+</li>
+`;
+
+exports[`should expand and collapse link 3`] = `
+<li>
+ <a
+ className="link-with-icon"
+ href="scm:git:git@github.com"
+ onClick={[Function]}
+ target="_blank"
+ >
+ <i
+ className="icon-color-link icon-detach"
+ />
+
+ Foo
+ </a>
+</li>
+`;
+
+exports[`should match snapshot 1`] = `
+<li>
+ <a
+ className="link-with-icon"
+ href="http://example.com"
+ target="_blank"
+ >
+ <i
+ className="icon-color-link icon-detach"
+ />
+
+ Foo
+ </a>
+</li>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the tag selector on click 1`] = `
-<div
- className="overview-meta-card overview-meta-tags"
->
- <button
- className="button-link"
- onClick={[Function]}
- >
- <TagsList
- allowUpdate={true}
- tags={
- Array [
- "foo",
- "bar",
- ]
- }
- />
- </button>
-</div>
-`;
-
-exports[`should open the tag selector on click 2`] = `
-<div
- className="overview-meta-card overview-meta-tags"
->
- <button
- className="button-link"
- onClick={[Function]}
- >
- <TagsList
- allowUpdate={true}
- tags={
- Array [
- "foo",
- "bar",
- ]
- }
- />
- </button>
- <div>
- <MetaTagsSelector
- position={
- Object {
- "right": 0,
- "top": 0,
- }
- }
- project="my-second-project"
- selectedTags={
- Array [
- "foo",
- "bar",
- ]
- }
- setProjectTags={[Function]}
- />
- </div>
-</div>
-`;
-
-exports[`should open the tag selector on click 3`] = `
-<div
- className="overview-meta-card overview-meta-tags"
->
- <button
- className="button-link"
- onClick={[Function]}
- >
- <TagsList
- allowUpdate={true}
- tags={
- Array [
- "foo",
- "bar",
- ]
- }
- />
- </button>
-</div>
-`;
-
-exports[`should render with tags and admin rights 1`] = `
-<div
- className="overview-meta-card overview-meta-tags"
->
- <button
- className="button-link"
- onClick={[Function]}
- >
- <TagsList
- allowUpdate={true}
- tags={
- Array [
- "foo",
- "bar",
- ]
- }
- />
- </button>
-</div>
-`;
-
-exports[`should render without tags and admin rights 1`] = `
-<div
- className="overview-meta-card overview-meta-tags"
->
- <TagsList
- allowUpdate={false}
- tags={
- Array [
- "no_tags",
- ]
- }
- />
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should open the tag selector on click 1`] = `
+<div
+ className="overview-meta-card overview-meta-tags"
+>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ >
+ <TagsList
+ allowUpdate={true}
+ tags={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+ />
+ </button>
+</div>
+`;
+
+exports[`should open the tag selector on click 2`] = `
+<div
+ className="overview-meta-card overview-meta-tags"
+>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ >
+ <TagsList
+ allowUpdate={true}
+ tags={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+ />
+ </button>
+ <div>
+ <MetaTagsSelector
+ position={
+ Object {
+ "right": 0,
+ "top": 0,
+ }
+ }
+ project="my-second-project"
+ selectedTags={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+ setProjectTags={[Function]}
+ />
+ </div>
+</div>
+`;
+
+exports[`should open the tag selector on click 3`] = `
+<div
+ className="overview-meta-card overview-meta-tags"
+>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ >
+ <TagsList
+ allowUpdate={true}
+ tags={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+ />
+ </button>
+</div>
+`;
+
+exports[`should render with tags and admin rights 1`] = `
+<div
+ className="overview-meta-card overview-meta-tags"
+>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ >
+ <TagsList
+ allowUpdate={true}
+ tags={
+ Array [
+ "foo",
+ "bar",
+ ]
+ }
+ />
+ </button>
+</div>
+`;
+
+exports[`should render without tags and admin rights 1`] = `
+<div
+ className="overview-meta-card overview-meta-tags"
+>
+ <TagsList
+ allowUpdate={false}
+ tags={
+ Array [
+ "no_tags",
+ ]
+ }
+ />
+</div>
+`;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { keyBy } from 'lodash';
-import ApplicationQualityGateProject from './ApplicationQualityGateProject';
-import Level from '../../../components/ui/Level';
-import { getApplicationQualityGate } from '../../../api/quality-gates';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
- component: { key: string, organization?: string }
-};
-*/
-
-/*::
-type State = {
- loading: boolean,
- metrics?: { [string]: Object },
- projects?: Array<{
- conditions: Array<Object>,
- key: string,
- name: string,
- status: string
- }>,
- status?: string
-};
-*/
-
-export default class ApplicationQualityGate extends React.PureComponent {
- /*:: mounted: boolean; */
- /*:: props: Props; */
- state /*: State */ = {
- loading: true
- };
-
- componentDidMount() {
- this.mounted = true;
- this.fetchDetails();
- }
-
- componentDidUpdate(prevProps /*: Props */) {
- if (prevProps.component.key !== this.props.component.key) {
- this.fetchDetails();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- fetchDetails = () => {
- const { component } = this.props;
- this.setState({ loading: true });
- getApplicationQualityGate({
- application: component.key,
- organization: component.organization
- }).then(
- ({ status, projects, metrics }) => {
- if (this.mounted) {
- this.setState({
- loading: false,
- metrics: keyBy(metrics, 'key'),
- status,
- projects
- });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- }
- );
- };
-
- render() {
- const { metrics, status, projects } = this.state;
-
- return (
- <div className="overview-quality-gate" id="overview-quality-gate">
- <h2 className="overview-title">
- {translate('overview.quality_gate')}
- {this.state.loading && <i className="spinner spacer-left" />}
- {status != null && <Level level={status} />}
- </h2>
-
- {projects != null && (
- <div
- id="overview-quality-gate-conditions-list"
- className="overview-quality-gate-conditions-list clearfix">
- {projects
- .filter(project => project.status !== 'OK')
- .map(project => (
- <ApplicationQualityGateProject
- key={project.key}
- metrics={metrics}
- project={project}
- />
- ))}
- </div>
- )}
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { keyBy } from 'lodash';
+import ApplicationQualityGateProject from './ApplicationQualityGateProject';
+import Level from '../../../components/ui/Level';
+import { getApplicationQualityGate, ApplicationProject } from '../../../api/quality-gates';
+import { translate } from '../../../helpers/l10n';
+import { LightComponent, Metric } from '../../../app/types';
+
+interface Props {
+ component: LightComponent;
+}
+
+type State = {
+ loading: boolean;
+ metrics?: { [key: string]: Metric };
+ projects?: ApplicationProject[];
+ status?: string;
+};
+
+export default class ApplicationQualityGate extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchDetails();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.component.key !== this.props.component.key) {
+ this.fetchDetails();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchDetails = () => {
+ const { component } = this.props;
+ this.setState({ loading: true });
+ getApplicationQualityGate({
+ application: component.key,
+ organization: component.organization
+ }).then(
+ ({ status, projects, metrics }) => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ metrics: keyBy(metrics, 'key'),
+ status,
+ projects
+ });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ render() {
+ const { metrics, status, projects } = this.state;
+
+ return (
+ <div className="overview-quality-gate" id="overview-quality-gate">
+ <h2 className="overview-title">
+ {translate('overview.quality_gate')}
+ {this.state.loading && <i className="spinner spacer-left" />}
+ {status != null && <Level level={status} />}
+ </h2>
+
+ {projects &&
+ metrics && (
+ <div
+ id="overview-quality-gate-conditions-list"
+ className="overview-quality-gate-conditions-list clearfix">
+ {projects
+ .filter(project => project.status !== 'OK')
+ .map(project => (
+ <ApplicationQualityGateProject
+ key={project.key}
+ metrics={metrics}
+ project={project}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { Link } from 'react-router';
-import classNames from 'classnames';
-import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
-import { getProjectUrl } from '../../../helpers/urls';
-import './ApplicationQualityGateProject.css';
-
-/*::
-type Condition = {
- comparator: string,
- errorThreshold?: string,
- metric: string,
- onLeak: boolean,
- status: string,
- value: string,
- warningThreshold?: string
-};
-*/
-
-/*::
-type Props = {
- metrics: {
- [string]: {
- key: string,
- name: string,
- type: string
- }
- },
- project: {
- conditions: Array<Condition>,
- key: string,
- name: string,
- status: string
- }
-};
-*/
-
-export default class ApplicationQualityGateProject extends React.PureComponent {
- /*:: props: Props; */
-
- renderCondition = (condition /*: Condition */) => {
- const metric = this.props.metrics[condition.metric];
- const metricName = getLocalizedMetricName(metric);
- const threshold = condition.errorThreshold || condition.warningThreshold;
- const isDiff = isDiffMetric(condition.metric);
-
- return (
- <li className={classNames({ 'is-on-leak': isDiff })} key={condition.metric}>
- <span className="text-limited">
- <strong>{formatMeasure(condition.value, metric.type)}</strong> {metricName}
- {!isDiff && condition.onLeak && ' ' + translate('quality_gates.conditions.leak')}
- </span>
- <span
- className={classNames('big-spacer-left', {
- 'text-danger': condition.status === 'ERROR',
- 'text-warning': condition.status === 'WARN'
- })}>
- {translate('quality_gates.operator', condition.comparator, 'short')}{' '}
- {formatMeasure(threshold, metric.type)}
- </span>
- </li>
- );
- };
-
- render() {
- const { project } = this.props;
-
- return (
- <Link
- className={classNames(
- 'overview-quality-gate-condition',
- 'overview-quality-gate-condition-' + project.status.toLowerCase()
- )}
- to={getProjectUrl(project.key)}>
- <div className="application-quality-gate-project">
- <h4>{project.name}</h4>
- <ul className="application-quality-gate-project-conditions">
- {project.conditions.filter(c => c.status !== 'OK').map(this.renderCondition)}
- </ul>
- </div>
- </Link>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { Link } from 'react-router';
+import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
+import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { getProjectUrl } from '../../../helpers/urls';
+import './ApplicationQualityGateProject.css';
+import { Metric } from '../../../app/types';
+import { ApplicationProject, ConditionAnalysis } from '../../../api/quality-gates';
+
+interface Props {
+ metrics: { [key: string]: Metric };
+ project: ApplicationProject;
+}
+
+export default class ApplicationQualityGateProject extends React.PureComponent<Props> {
+ renderCondition = (condition: ConditionAnalysis) => {
+ const metric = this.props.metrics[condition.metric];
+ const metricName = getLocalizedMetricName(metric);
+ const threshold = condition.errorThreshold || condition.warningThreshold;
+ const isDiff = isDiffMetric(condition.metric);
+
+ return (
+ <li className={classNames({ 'is-on-leak': isDiff })} key={condition.metric}>
+ <span className="text-limited">
+ <strong>{formatMeasure(condition.value, metric.type)}</strong> {metricName}
+ {!isDiff && condition.onLeak && ' ' + translate('quality_gates.conditions.leak')}
+ </span>
+ <span
+ className={classNames('big-spacer-left', {
+ 'text-danger': condition.status === 'ERROR',
+ 'text-warning': condition.status === 'WARN'
+ })}>
+ {translate('quality_gates.operator', condition.comparator, 'short')}{' '}
+ {formatMeasure(threshold, metric.type)}
+ </span>
+ </li>
+ );
+ };
+
+ render() {
+ const { project } = this.props;
+
+ return (
+ <Link
+ className={classNames(
+ 'overview-quality-gate-condition',
+ 'overview-quality-gate-condition-' + project.status.toLowerCase()
+ )}
+ to={getProjectUrl(project.key)}>
+ <div className="application-quality-gate-project">
+ <h4>{project.name}</h4>
+ <ul className="application-quality-gate-project-conditions">
+ {project.conditions.filter(c => c.status !== 'OK').map(this.renderCondition)}
+ </ul>
+ </div>
+ </Link>
+ );
+ }
+}
import React from 'react';
import classNames from 'classnames';
import { Link } from 'react-router';
-import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
import Measure from '../../../components/measure/Measure';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import { getPeriodValue, isDiffMetric, formatMeasure } from '../../../helpers/measures';
return this.wrapWithLink(
<div className="overview-quality-gate-condition-container">
<div className="overview-quality-gate-condition-value">
- <Measure measure={{ ...measure, value: actual, leak: actual }} decimals={decimals} />
+ <Measure
+ decimals={decimals}
+ value={actual}
+ metricKey={measure.metric.key}
+ metricType={measure.metric.type}
+ />
</div>
<div>
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import ApplicationQualityGate from '../ApplicationQualityGate';
-
-it('renders', () => {
- const wrapper = shallow(<ApplicationQualityGate component={{ key: 'foo' }} />);
- expect(wrapper).toMatchSnapshot();
- wrapper.setState({
- loading: false,
- metrics: {},
- status: 'ERROR',
- projects: [
- { conditions: [], key: 'project1', name: 'project1', status: 'ERROR' },
- { conditions: [], key: 'project2', name: 'project2', status: 'OK' },
- { conditions: [], key: 'project3', name: 'project3', status: 'WARN' }
- ]
- });
- expect(wrapper).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ApplicationQualityGate from '../ApplicationQualityGate';
+
+it('renders', () => {
+ const wrapper = shallow(
+ <ApplicationQualityGate component={{ key: 'foo', organization: 'foo', qualifier: 'TRK' }} />
+ );
+ expect(wrapper).toMatchSnapshot();
+ wrapper.setState({
+ loading: false,
+ metrics: {},
+ status: 'ERROR',
+ projects: [
+ { conditions: [], key: 'project1', name: 'project1', status: 'ERROR' },
+ { conditions: [], key: 'project2', name: 'project2', status: 'OK' },
+ { conditions: [], key: 'project3', name: 'project3', status: 'WARN' }
+ ]
+ });
+ expect(wrapper).toMatchSnapshot();
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { shallow } from 'enzyme';
-import ApplicationQualityGateProject from '../ApplicationQualityGateProject';
-
-const metrics = {
- bugs: { key: 'bugs', name: 'Bugs', type: 'INT' },
- new_coverage: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' },
- skipped_tests: { key: 'skipped_tests', name: 'Skipped Tests', type: 'INT' }
-};
-
-it('renders', () => {
- const project = {
- key: 'foo',
- name: 'Foo',
- status: 'ERROR',
- conditions: [
- {
- status: 'ERROR',
- metric: 'new_coverage',
- comparator: 'LT',
- onLeak: true,
- errorThreshold: '85',
- value: '82.50562381034781'
- },
- {
- status: 'WARN',
- metric: 'bugs',
- comparator: 'GT',
- onLeak: false,
- warningThreshold: '0',
- value: '17'
- },
- {
- status: 'ERROR',
- metric: 'bugs',
- comparator: 'GT',
- onLeak: true,
- warningThreshold: '0',
- value: '3'
- },
- {
- status: 'OK',
- metric: 'skipped_tests',
- comparator: 'GT',
- onLeak: false,
- warningThreshold: '0',
- value: '0'
- }
- ]
- };
- const wrapper = shallow(<ApplicationQualityGateProject metrics={metrics} project={project} />);
- expect(wrapper).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import ApplicationQualityGateProject from '../ApplicationQualityGateProject';
+
+const metrics = {
+ bugs: { key: 'bugs', name: 'Bugs', type: 'INT' },
+ new_coverage: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' },
+ skipped_tests: { key: 'skipped_tests', name: 'Skipped Tests', type: 'INT' }
+};
+
+it('renders', () => {
+ const project = {
+ key: 'foo',
+ name: 'Foo',
+ status: 'ERROR',
+ conditions: [
+ {
+ status: 'ERROR',
+ metric: 'new_coverage',
+ comparator: 'LT',
+ onLeak: true,
+ errorThreshold: '85',
+ value: '82.50562381034781'
+ },
+ {
+ status: 'WARN',
+ metric: 'bugs',
+ comparator: 'GT',
+ onLeak: false,
+ warningThreshold: '0',
+ value: '17'
+ },
+ {
+ status: 'ERROR',
+ metric: 'bugs',
+ comparator: 'GT',
+ onLeak: true,
+ warningThreshold: '0',
+ value: '3'
+ },
+ {
+ status: 'OK',
+ metric: 'skipped_tests',
+ comparator: 'GT',
+ onLeak: false,
+ warningThreshold: '0',
+ value: '0'
+ }
+ ]
+ };
+ const wrapper = shallow(<ApplicationQualityGateProject metrics={metrics} project={project} />);
+ expect(wrapper).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<div
- className="overview-quality-gate"
- id="overview-quality-gate"
->
- <h2
- className="overview-title"
- >
- overview.quality_gate
- <i
- className="spinner spacer-left"
- />
- </h2>
-</div>
-`;
-
-exports[`renders 2`] = `
-<div
- className="overview-quality-gate"
- id="overview-quality-gate"
->
- <h2
- className="overview-title"
- >
- overview.quality_gate
- <Level
- level="ERROR"
- />
- </h2>
- <div
- className="overview-quality-gate-conditions-list clearfix"
- id="overview-quality-gate-conditions-list"
- >
- <ApplicationQualityGateProject
- key="project1"
- metrics={Object {}}
- project={
- Object {
- "conditions": Array [],
- "key": "project1",
- "name": "project1",
- "status": "ERROR",
- }
- }
- />
- <ApplicationQualityGateProject
- key="project3"
- metrics={Object {}}
- project={
- Object {
- "conditions": Array [],
- "key": "project3",
- "name": "project3",
- "status": "WARN",
- }
- }
- />
- </div>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="overview-quality-gate"
+ id="overview-quality-gate"
+>
+ <h2
+ className="overview-title"
+ >
+ overview.quality_gate
+ <i
+ className="spinner spacer-left"
+ />
+ </h2>
+</div>
+`;
+
+exports[`renders 2`] = `
+<div
+ className="overview-quality-gate"
+ id="overview-quality-gate"
+>
+ <h2
+ className="overview-title"
+ >
+ overview.quality_gate
+ <Level
+ level="ERROR"
+ />
+ </h2>
+ <div
+ className="overview-quality-gate-conditions-list clearfix"
+ id="overview-quality-gate-conditions-list"
+ >
+ <ApplicationQualityGateProject
+ key="project1"
+ metrics={Object {}}
+ project={
+ Object {
+ "conditions": Array [],
+ "key": "project1",
+ "name": "project1",
+ "status": "ERROR",
+ }
+ }
+ />
+ <ApplicationQualityGateProject
+ key="project3"
+ metrics={Object {}}
+ project={
+ Object {
+ "conditions": Array [],
+ "key": "project3",
+ "name": "project3",
+ "status": "WARN",
+ }
+ }
+ />
+ </div>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<Link
- className="overview-quality-gate-condition overview-quality-gate-condition-error"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/dashboard",
- "query": Object {
- "branch": undefined,
- "id": "foo",
- },
- }
- }
->
- <div
- className="application-quality-gate-project"
- >
- <h4>
- Foo
- </h4>
- <ul
- className="application-quality-gate-project-conditions"
- >
- <li
- className="is-on-leak"
- key="new_coverage"
- >
- <span
- className="text-limited"
- >
- <strong>
- 82.5%
- </strong>
-
- Coverage on New Code
- </span>
- <span
- className="big-spacer-left text-danger"
- >
- quality_gates.operator.LT.short
-
- 85.0%
- </span>
- </li>
- <li
- className=""
- key="bugs"
- >
- <span
- className="text-limited"
- >
- <strong>
- 17
- </strong>
-
- Bugs
- </span>
- <span
- className="big-spacer-left text-warning"
- >
- quality_gates.operator.GT.short
-
- 0
- </span>
- </li>
- <li
- className=""
- key="bugs"
- >
- <span
- className="text-limited"
- >
- <strong>
- 3
- </strong>
-
- Bugs
- quality_gates.conditions.leak
- </span>
- <span
- className="big-spacer-left text-danger"
- >
- quality_gates.operator.GT.short
-
- 0
- </span>
- </li>
- </ul>
- </div>
-</Link>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<Link
+ className="overview-quality-gate-condition overview-quality-gate-condition-error"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/dashboard",
+ "query": Object {
+ "branch": undefined,
+ "id": "foo",
+ },
+ }
+ }
+>
+ <div
+ className="application-quality-gate-project"
+ >
+ <h4>
+ Foo
+ </h4>
+ <ul
+ className="application-quality-gate-project-conditions"
+ >
+ <li
+ className="is-on-leak"
+ key="new_coverage"
+ >
+ <span
+ className="text-limited"
+ >
+ <strong>
+ 82.5%
+ </strong>
+
+ Coverage on New Code
+ </span>
+ <span
+ className="big-spacer-left text-danger"
+ >
+ quality_gates.operator.LT.short
+
+ 85.0%
+ </span>
+ </li>
+ <li
+ className=""
+ key="bugs"
+ >
+ <span
+ className="text-limited"
+ >
+ <strong>
+ 17
+ </strong>
+
+ Bugs
+ </span>
+ <span
+ className="big-spacer-left text-warning"
+ >
+ quality_gates.operator.GT.short
+
+ 0
+ </span>
+ </li>
+ <li
+ className=""
+ key="bugs"
+ >
+ <span
+ className="text-limited"
+ >
+ <strong>
+ 3
+ </strong>
+
+ Bugs
+ quality_gates.conditions.leak
+ </span>
+ <span
+ className="big-spacer-left text-danger"
+ >
+ quality_gates.operator.GT.short
+
+ 0
+ </span>
+ </li>
+ </ul>
+ </div>
+</Link>
+`;
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "new_maintainability_rating",
- "name": "new_maintainability_rating",
- "type": "RATING",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "3",
- },
- ],
- "value": "3",
- }
- }
+ metricKey="new_maintainability_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "10",
- "metric": Object {
- "key": "new_open_issues",
- "name": "new_open_issues",
- "type": "INT",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "10",
- },
- ],
- "value": "10",
- }
- }
+ metricKey="new_open_issues"
+ metricType="INT"
+ value="10"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "new_reliability_rating",
- "name": "new_reliability_rating",
- "type": "RATING",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "3",
- },
- ],
- "value": "3",
- }
- }
+ metricKey="new_reliability_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "new_security_rating",
- "name": "new_security_rating",
- "type": "RATING",
- },
- "periods": Array [
- Object {
- "index": 1,
- "value": "3",
- },
- ],
- "value": "3",
- }
- }
+ metricKey="new_security_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "10",
- "metric": Object {
- "key": "open_issues",
- "name": "Open open_issues",
- "type": "INT",
- },
- "value": "10",
- }
- }
+ metricKey="open_issues"
+ metricType="INT"
+ value="10"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "reliability_rating",
- "name": "reliability_rating",
- "type": "RATING",
- },
- "value": "3",
- }
- }
+ metricKey="reliability_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "security_rating",
- "name": "security_rating",
- "type": "RATING",
- },
- "value": "3",
- }
- }
+ metricKey="security_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "new_maintainability_rating",
- "name": "new_maintainability_rating",
- "type": "RATING",
- },
- "value": "3",
- }
- }
+ metricKey="new_maintainability_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
>
<Measure
decimals={null}
- measure={
- Object {
- "leak": "3",
- "metric": Object {
- "key": "sqale_rating",
- "name": "sqale_rating",
- "type": "RATING",
- },
- "value": "3",
- }
- }
+ metricKey="sqale_rating"
+ metricType="RATING"
+ value="3"
/>
</div>
<div>
<Link to={getComponentDrilldownUrl(component, metricKey)}>
<span>
<Measure
- measure={{
- metric: { key: 'projects', type: 'SHORT_INT' },
- value: String(effort.projects)
- }}
- />{' '}
+ className="little-spacer-right"
+ metricKey="projects"
+ metricType="SHORT_INT"
+ value={String(effort.projects)}
+ />
{translate('projects_')}
</span>
</Link>
<Link to={getComponentDrilldownUrl(component, 'alert_status')}>
<span>
<Measure
- measure={{ metric: { key: 'projects', type: 'SHORT_INT' }, value: effort }}
- />{' '}
+ className="little-spacer-right"
+ metricKey="projects"
+ metricType="SHORT_INT"
+ value={effort}
+ />
{Number(effort) === 1 ? 'project' : 'projects'}
</span>
</Link>{' '}
<li>
<div className="portfolio-measure-secondary-value">
<Link to={getComponentDrilldownUrl(component.key, 'projects')}>
- <Measure
- measure={{ metric: { key: 'projects', type: 'SHORT_INT' }, value: projects }}
- />
+ <Measure metricKey="projects" metricType="SHORT_INT" value={projects} />
</Link>
</div>
<div className="spacer-top text-muted">{translate('projects')}</div>
<li>
<div className="portfolio-measure-secondary-value">
<Link to={getComponentDrilldownUrl(component.key, 'ncloc')}>
- <Measure measure={{ metric: { key: 'ncloc', type: 'SHORT_INT' }, value: ncloc }} />
+ <Measure metricKey="ncloc" metricType="SHORT_INT" value={ncloc} />
</Link>
</div>
<div className="spacer-top text-muted">{translate('metric.ncloc.name')}</div>
function renderCell(measures: { [key: string]: string | undefined }, metric: string, type: string) {
return (
<td className="text-center">
- <Measure measure={{ metric: { key: metric, type }, value: measures[metric] }} />
+ <Measure metricKey={metric} metricType={type} value={measures[metric]} />
</td>
);
}
return (
<td className="text-right">
<span className="note">
- <Measure
- measure={{
- metric: { key: 'ncloc', type: 'SHORT_INT' },
- value: measures['ncloc']
- }}
- />
+ <Measure metricKey="ncloc" metricType="SHORT_INT" value={measures['ncloc']} />
</span>
{maxLoc > 0 && (
<svg width="50" height="16" className="spacer-left">
>
<span>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "projects",
- "type": "SHORT_INT",
- },
- "value": "3",
- }
- }
+ className="little-spacer-right"
+ metricKey="projects"
+ metricType="SHORT_INT"
+ value="3"
/>
-
projects_
</span>
</Link>,
>
<span>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "projects",
- "type": "SHORT_INT",
- },
- "value": "7",
- }
- }
+ className="little-spacer-right"
+ metricKey="projects"
+ metricType="SHORT_INT"
+ value="7"
/>
-
projects
</span>
</Link>
}
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "projects",
- "type": "SHORT_INT",
- },
- "value": "15",
- }
- }
+ metricKey="projects"
+ metricType="SHORT_INT"
+ value="15"
/>
</Link>
</div>
}
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "ncloc",
- "type": "SHORT_INT",
- },
- "value": "1234",
- }
- }
+ metricKey="ncloc"
+ metricType="SHORT_INT"
+ value="1234"
/>
</Link>
</div>
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "releasability_rating",
- "type": "RATING",
- },
- "value": "3",
- }
- }
+ metricKey="releasability_rating"
+ metricType="RATING"
+ value="3"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "reliability_rating",
- "type": "RATING",
- },
- "value": "2",
- }
- }
+ metricKey="reliability_rating"
+ metricType="RATING"
+ value="2"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "security_rating",
- "type": "RATING",
- },
- "value": "1",
- }
- }
+ metricKey="security_rating"
+ metricType="RATING"
+ value="1"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "sqale_rating",
- "type": "RATING",
- },
- "value": "4",
- }
- }
+ metricKey="sqale_rating"
+ metricType="RATING"
+ value="4"
/>
</td>
<td
className="note"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "ncloc",
- "type": "SHORT_INT",
- },
- "value": "200",
- }
- }
+ metricKey="ncloc"
+ metricType="SHORT_INT"
+ value="200"
/>
</span>
<svg
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "alert_status",
- "type": "LEVEL",
- },
- "value": "ERROR",
- }
- }
+ metricKey="alert_status"
+ metricType="LEVEL"
+ value="ERROR"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "reliability_rating",
- "type": "RATING",
- },
- "value": "2",
- }
- }
+ metricKey="reliability_rating"
+ metricType="RATING"
+ value="2"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "security_rating",
- "type": "RATING",
- },
- "value": "1",
- }
- }
+ metricKey="security_rating"
+ metricType="RATING"
+ value="1"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "sqale_rating",
- "type": "RATING",
- },
- "value": "4",
- }
- }
+ metricKey="sqale_rating"
+ metricType="RATING"
+ value="4"
/>
</td>
<td
className="note"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "ncloc",
- "type": "SHORT_INT",
- },
- "value": "100",
- }
- }
+ metricKey="ncloc"
+ metricType="SHORT_INT"
+ value="100"
/>
</span>
<svg
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "alert_status",
- "type": "LEVEL",
- },
- "value": "WARN",
- }
- }
+ metricKey="alert_status"
+ metricType="LEVEL"
+ value="WARN"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "reliability_rating",
- "type": "RATING",
- },
- "value": "2",
- }
- }
+ metricKey="reliability_rating"
+ metricType="RATING"
+ value="2"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "security_rating",
- "type": "RATING",
- },
- "value": "1",
- }
- }
+ metricKey="security_rating"
+ metricType="RATING"
+ value="1"
/>
</td>
<td
className="text-center"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "sqale_rating",
- "type": "RATING",
- },
- "value": "4",
- }
- }
+ metricKey="sqale_rating"
+ metricType="RATING"
+ value="4"
/>
</td>
<td
className="note"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "ncloc",
- "type": "SHORT_INT",
- },
- "value": "150",
- }
- }
+ metricKey="ncloc"
+ metricType="SHORT_INT"
+ value="150"
/>
</span>
<svg
value: analysis.value
}))
})),
- throwGlobalError
+ () => {}
);
};
<div className="project-card-measure-number">
<Measure
className="spacer-right"
- measure={{
- metric: { key: 'new_bugs', type: 'SHORT_INT' },
- leak: measures['new_bugs']
- }}
+ metricKey="new_bugs"
+ metricType="SHORT_INT"
+ value={measures['new_bugs']}
/>
<Rating value={measures['new_reliability_rating']} />
</div>
<div className="project-card-measure-number">
<Measure
className="spacer-right"
- measure={{
- metric: { key: 'new_vulnerabilities', type: 'SHORT_INT' },
- leak: measures['new_vulnerabilities']
- }}
+ metricKey="new_vulnerabilities"
+ metricType="SHORT_INT"
+ value={measures['new_vulnerabilities']}
/>
<Rating value={measures['new_security_rating']} />
</div>
<div className="project-card-measure-number">
<Measure
className="spacer-right"
- measure={{
- metric: { key: 'new_code_smells', type: 'SHORT_INT' },
- leak: measures['new_code_smells']
- }}
+ metricKey="new_code_smells"
+ metricType="SHORT_INT"
+ value={measures['new_code_smells']}
/>
<Rating value={measures['new_maintainability_rating']} />
</div>
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
- measure={{
- metric: { key: 'new_coverage', type: 'PERCENT' },
- leak: measures['new_coverage']
- }}
+ metricKey="new_coverage"
+ metricType="PERCENT"
+ value={measures['new_coverage']}
/>
</div>
<div className="project-card-measure-label">{translate('metric.coverage.name')}</div>
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
<Measure
- measure={{
- metric: { key: 'new_duplicated_lines_density', type: 'PERCENT' },
- leak: measures['new_duplicated_lines_density']
- }}
+ metricKey="new_duplicated_lines_density"
+ metricType="PERCENT"
+ value={measures['new_duplicated_lines_density']}
/>
</div>
<div className="project-card-measure-label">
<div className="project-card-measure smaller-card project-card-ncloc" data-key="new_lines">
<div className="project-card-measure-inner">
<div className="project-card-measure-number">
- <Measure
- measure={{
- metric: { key: 'new_lines', type: 'SHORT_INT' },
- leak: measures['new_lines']
- }}
- />
+ <Measure metricKey="new_lines" metricType="SHORT_INT" value={measures['new_lines']} />
</div>
<div className="project-card-measure-label">{translate('metric.lines.name')}</div>
</div>
<div className="project-card-measure-number">
<Measure
className="spacer-right"
- measure={{
- metric: { key: 'bugs', type: 'SHORT_INT' },
- value: measures['bugs']
- }}
+ metricKey="bugs"
+ metricType="SHORT_INT"
+ value={measures['bugs']}
/>
<Rating value={measures['reliability_rating']} />
</div>
<div className="project-card-measure-number">
<Measure
className="spacer-right"
- measure={{
- metric: { key: 'vulnerabilities', type: 'SHORT_INT' },
- value: measures['vulnerabilities']
- }}
+ metricKey="vulnerabilities"
+ metricType="SHORT_INT"
+ value={measures['vulnerabilities']}
/>
<Rating value={measures['security_rating']} />
</div>
<div className="project-card-measure-number">
<Measure
className="spacer-right"
- measure={{
- metric: { key: 'code_smells', type: 'SHORT_INT' },
- value: measures['code_smells']
- }}
+ metricKey="code_smells"
+ metricType="SHORT_INT"
+ value={measures['code_smells']}
/>
<Rating value={measures['sqale_rating']} />
</div>
<CoverageRating value={measures['coverage']} />
</span>
)}
- <Measure
- measure={{
- metric: { key: 'coverage', type: 'PERCENT' },
- value: measures['coverage']
- }}
- />
+ <Measure metricKey="coverage" metricType="PERCENT" value={measures['coverage']} />
</div>
<div className="project-card-measure-label">{translate('metric.coverage.name')}</div>
</div>
</span>
)}
<Measure
- measure={{
- metric: { key: 'duplicated_lines_density', type: 'PERCENT' },
- value: measures['duplicated_lines_density']
- }}
+ metricKey="duplicated_lines_density"
+ metricType="PERCENT"
+ value={measures['duplicated_lines_density']}
/>
</div>
<div className="project-card-measure-label">
<div className="project-card-measure project-card-ncloc" data-key="ncloc">
<div className="project-card-measure-inner pull-right">
<div className="project-card-measure-number">
- <Measure
- measure={{
- metric: { key: 'ncloc', type: 'SHORT_INT' },
- value: measures['ncloc']
- }}
- />
+ <Measure metricKey="ncloc" metricType="SHORT_INT" value={measures['ncloc']} />
<span className="spacer-left">
<SizeRating value={Number(measures['ncloc'])} />
</span>
>
<Measure
className="spacer-right"
- measure={
- Object {
- "leak": "8",
- "metric": Object {
- "key": "new_bugs",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_bugs"
+ metricType="SHORT_INT"
+ value="8"
/>
<Rating
value="1.0"
>
<Measure
className="spacer-right"
- measure={
- Object {
- "leak": "2",
- "metric": Object {
- "key": "new_vulnerabilities",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_vulnerabilities"
+ metricType="SHORT_INT"
+ value="2"
/>
<Rating
value="2.0"
>
<Measure
className="spacer-right"
- measure={
- Object {
- "leak": "0",
- "metric": Object {
- "key": "new_code_smells",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_code_smells"
+ metricType="SHORT_INT"
+ value="0"
/>
<Rating
value="1.0"
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "leak": "26.55",
- "metric": Object {
- "key": "new_coverage",
- "type": "PERCENT",
- },
- }
- }
+ metricKey="new_coverage"
+ metricType="PERCENT"
+ value="26.55"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "leak": "0.55",
- "metric": Object {
- "key": "new_duplicated_lines_density",
- "type": "PERCENT",
- },
- }
- }
+ metricKey="new_duplicated_lines_density"
+ metricType="PERCENT"
+ value="0.55"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "leak": "87",
- "metric": Object {
- "key": "new_lines",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_lines"
+ metricType="SHORT_INT"
+ value="87"
/>
</div>
<div
>
<Measure
className="spacer-right"
- measure={
- Object {
- "leak": "8",
- "metric": Object {
- "key": "new_bugs",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_bugs"
+ metricType="SHORT_INT"
+ value="8"
/>
<Rating
value="1.0"
>
<Measure
className="spacer-right"
- measure={
- Object {
- "leak": "2",
- "metric": Object {
- "key": "new_vulnerabilities",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_vulnerabilities"
+ metricType="SHORT_INT"
+ value="2"
/>
<Rating
value="2.0"
>
<Measure
className="spacer-right"
- measure={
- Object {
- "leak": "0",
- "metric": Object {
- "key": "new_code_smells",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_code_smells"
+ metricType="SHORT_INT"
+ value="0"
/>
<Rating
value="1.0"
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "leak": undefined,
- "metric": Object {
- "key": "new_coverage",
- "type": "PERCENT",
- },
- }
- }
+ metricKey="new_coverage"
+ metricType="PERCENT"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "leak": undefined,
- "metric": Object {
- "key": "new_duplicated_lines_density",
- "type": "PERCENT",
- },
- }
- }
+ metricKey="new_duplicated_lines_density"
+ metricType="PERCENT"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "leak": undefined,
- "metric": Object {
- "key": "new_lines",
- "type": "SHORT_INT",
- },
- }
- }
+ metricKey="new_lines"
+ metricType="SHORT_INT"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "coverage",
- "type": "PERCENT",
- },
- "value": undefined,
- }
- }
+ metricKey="coverage"
+ metricType="PERCENT"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "duplicated_lines_density",
- "type": "PERCENT",
- },
- "value": undefined,
- }
- }
+ metricKey="duplicated_lines_density"
+ metricType="PERCENT"
/>
</div>
<div
>
<Measure
className="spacer-right"
- measure={
- Object {
- "metric": Object {
- "key": "bugs",
- "type": "SHORT_INT",
- },
- "value": "17",
- }
- }
+ metricKey="bugs"
+ metricType="SHORT_INT"
+ value="17"
/>
<Rating
value="1.0"
>
<Measure
className="spacer-right"
- measure={
- Object {
- "metric": Object {
- "key": "vulnerabilities",
- "type": "SHORT_INT",
- },
- "value": "0",
- }
- }
+ metricKey="vulnerabilities"
+ metricType="SHORT_INT"
+ value="0"
/>
<Rating
value="1.0"
>
<Measure
className="spacer-right"
- measure={
- Object {
- "metric": Object {
- "key": "code_smells",
- "type": "SHORT_INT",
- },
- "value": "132",
- }
- }
+ metricKey="code_smells"
+ metricType="SHORT_INT"
+ value="132"
/>
<Rating
value="1.0"
/>
</span>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "coverage",
- "type": "PERCENT",
- },
- "value": "88.3",
- }
- }
+ metricKey="coverage"
+ metricType="PERCENT"
+ value="88.3"
/>
</div>
<div
/>
</span>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "duplicated_lines_density",
- "type": "PERCENT",
- },
- "value": "9.8",
- }
- }
+ metricKey="duplicated_lines_density"
+ metricType="PERCENT"
+ value="9.8"
/>
</div>
<div
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "ncloc",
- "type": "SHORT_INT",
- },
- "value": "2053",
- }
- }
+ metricKey="ncloc"
+ metricType="SHORT_INT"
+ value="2053"
/>
<span
className="spacer-left"
className="project-card-measure-number"
>
<Measure
- measure={
- Object {
- "metric": Object {
- "key": "ncloc",
- "type": "SHORT_INT",
- },
- "value": "16549887",
- }
- }
+ metricKey="ncloc"
+ metricType="SHORT_INT"
+ value="16549887"
/>
<span
className="spacer-left"
searchProjectTags({
q: search,
ps: size(this.props.facet || {}) + LIST_SIZE
- }).then(result => {
- if (this.mounted) {
- this.setState({ isLoading: false, tags: result.tags });
- }
- });
+ }).then(
+ result => {
+ if (this.mounted) {
+ this.setState({ isLoading: false, tags: result.tags });
+ }
+ },
+ () => {}
+ );
}
};
import * as React from 'react';
import * as classNames from 'classnames';
+export interface BubblePopupPosition {
+ top: number;
+ right: number;
+}
+
interface Props {
customClass?: string;
children: React.ReactNode;
- position: { top: number; right: number };
+ position: BubblePopupPosition;
}
export default function BubblePopup(props: Props) {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import { difference } from 'lodash';
-import MultiSelectOption from './MultiSelectOption';
-import SearchBox from '../controls/SearchBox';
-import { translate } from '../../helpers/l10n';
-
-/*::
-type Props = {
- selectedElements: Array<string>,
- elements: Array<string>,
- listSize: number,
- onSearch: string => void,
- onSelect: string => void,
- onUnselect: string => void,
- validateSearchInput: string => string,
- placeholder: string
-};
-*/
-
-/*::
-type State = {
- query: string,
- selectedElements: Array<string>,
- unselectedElements: Array<string>,
- activeIdx: number
-};
-*/
-
-export default class MultiSelect extends React.PureComponent {
- /*:: container: HTMLElement; */
- /*:: searchInput: HTMLInputElement; */
- /*:: props: Props; */
- /*:: state: State; */
-
- static defaultProps = {
- listSize: 10,
- validateSearchInput: (value /*: string */) => value
- };
-
- constructor(props /*: Props */) {
- super(props);
- this.state = {
- query: '',
- selectedElements: [],
- unselectedElements: [],
- activeIdx: 0
- };
- }
-
- componentDidMount() {
- this.updateSelectedElements(this.props);
- this.updateUnselectedElements(this.props);
- if (this.container) {
- this.container.addEventListener('keydown', this.handleKeyboard, true);
- }
- }
-
- componentWillReceiveProps(nextProps /*: Props */) {
- if (
- this.props.elements !== nextProps.elements ||
- this.props.selectedElements !== nextProps.selectedElements
- ) {
- this.updateSelectedElements(nextProps);
- this.updateUnselectedElements(nextProps);
-
- const totalElements = this.getAllElements(nextProps, this.state).length;
- if (this.state.activeIdx >= totalElements) {
- this.setState({ activeIdx: totalElements - 1 });
- }
- }
- }
-
- componentDidUpdate() {
- this.searchInput && this.searchInput.focus();
- }
-
- componentWillUnmount() {
- this.container.removeEventListener('keydown', this.handleKeyboard);
- }
-
- handleSelectChange = (item /*: string */, selected /*: boolean */) => {
- if (selected) {
- this.onSelectItem(item);
- } else {
- this.onUnselectItem(item);
- }
- };
-
- handleSearchChange = (value /*: string */) => {
- this.onSearchQuery(this.props.validateSearchInput(value));
- };
-
- handleElementHover = (element /*: string */) => {
- this.setState((prevState, props) => {
- return { activeIdx: this.getAllElements(props, prevState).indexOf(element) };
- });
- };
-
- handleKeyboard = (evt /*: KeyboardEvent */) => {
- switch (evt.keyCode) {
- case 40: // down
- this.setState(this.selectNextElement);
- evt.stopPropagation();
- evt.preventDefault();
- break;
- case 38: // up
- this.setState(this.selectPreviousElement);
- evt.stopPropagation();
- evt.preventDefault();
- break;
- case 37: // left
- case 39: // right
- evt.stopPropagation();
- break;
- case 13: // enter
- if (this.state.activeIdx >= 0) {
- this.toggleSelect(this.getAllElements(this.props, this.state)[this.state.activeIdx]);
- }
- break;
- }
- };
-
- onSearchQuery(query /*: string */) {
- this.setState({ query, activeIdx: 0 });
- this.props.onSearch(query);
- }
-
- onSelectItem(item /*: string */) {
- if (this.isNewElement(item, this.props)) {
- this.onSearchQuery('');
- }
- this.props.onSelect(item);
- }
-
- onUnselectItem(item /*: string */) {
- this.props.onUnselect(item);
- }
-
- isNewElement(elem /*: string */, { selectedElements, elements } /*: Props */) {
- return elem && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1;
- }
-
- updateSelectedElements(props /*: Props */) {
- this.setState((state /*: State */) => {
- if (state.query) {
- return {
- selectedElements: [...props.selectedElements.filter(elem => elem.includes(state.query))]
- };
- } else {
- return { selectedElements: [...props.selectedElements] };
- }
- });
- }
-
- updateUnselectedElements(props /*: Props */) {
- this.setState((state /*: State */) => {
- if (props.listSize < state.selectedElements.length) {
- return { unselectedElements: [] };
- } else {
- return {
- unselectedElements: difference(props.elements, props.selectedElements).slice(
- 0,
- props.listSize - state.selectedElements.length
- )
- };
- }
- });
- }
-
- getAllElements(props /*: Props */, state /*: State */) {
- if (this.isNewElement(state.query, props)) {
- return [...state.selectedElements, ...state.unselectedElements, state.query];
- } else {
- return [...state.selectedElements, ...state.unselectedElements];
- }
- }
-
- setElementActive(idx /*: number */) {
- this.setState({ activeIdx: idx });
- }
-
- selectNextElement = (state /*: State */, props /*: Props */) => {
- const { activeIdx } = state;
- const allElements = this.getAllElements(props, state);
- if (activeIdx < 0 || activeIdx >= allElements.length - 1) {
- return { activeIdx: 0 };
- } else {
- return { activeIdx: activeIdx + 1 };
- }
- };
-
- selectPreviousElement = (state /*: State */, props /*: Props */) => {
- const { activeIdx } = state;
- const allElements = this.getAllElements(props, state);
- if (activeIdx <= 0) {
- const lastIdx = allElements.length - 1;
- return { activeIdx: lastIdx };
- } else {
- return { activeIdx: activeIdx - 1 };
- }
- };
-
- toggleSelect(item /*: string */) {
- if (this.props.selectedElements.indexOf(item) === -1) {
- this.onSelectItem(item);
- } else {
- this.onUnselectItem(item);
- }
- }
-
- render() {
- const { query, activeIdx, selectedElements, unselectedElements } = this.state;
- const activeElement = this.getAllElements(this.props, this.state)[activeIdx];
-
- return (
- <div className="multi-select" ref={div => (this.container = div)}>
- <div className="menu-search">
- <SearchBox
- autoFocus={true}
- onChange={this.handleSearchChange}
- placeholder={this.props.placeholder}
- value={query}
- />
- </div>
- <ul className="menu">
- {selectedElements.length > 0 &&
- selectedElements.map(element => (
- <MultiSelectOption
- key={element}
- element={element}
- selected={true}
- active={activeElement === element}
- onSelectChange={this.handleSelectChange}
- onHover={this.handleElementHover}
- />
- ))}
- {unselectedElements.length > 0 &&
- unselectedElements.map(element => (
- <MultiSelectOption
- key={element}
- element={element}
- active={activeElement === element}
- onSelectChange={this.handleSelectChange}
- onHover={this.handleElementHover}
- />
- ))}
- {this.isNewElement(query, this.props) && (
- <MultiSelectOption
- key={query}
- element={query}
- custom={true}
- active={activeElement === query}
- onSelectChange={this.handleSelectChange}
- onHover={this.handleElementHover}
- />
- )}
- </ul>
- </div>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { difference } from 'lodash';
+import MultiSelectOption from './MultiSelectOption';
+import SearchBox from '../controls/SearchBox';
+
+interface Props {
+ selectedElements: Array<string>;
+ elements: Array<string>;
+ listSize?: number;
+ onSearch: (query: string) => void;
+ onSelect: (item: string) => void;
+ onUnselect: (item: string) => void;
+ validateSearchInput?: (value: string) => string;
+ placeholder: string;
+}
+
+interface State {
+ query: string;
+ selectedElements: Array<string>;
+ unselectedElements: Array<string>;
+ activeIdx: number;
+}
+
+interface DefaultProps {
+ listSize: number;
+ validateSearchInput: (value: string) => string;
+}
+
+type PropsWithDefault = Props & DefaultProps;
+
+export default class MultiSelect extends React.PureComponent<Props, State> {
+ container: HTMLDivElement | null;
+ searchInput: HTMLInputElement | null;
+
+ static defaultProps: DefaultProps = {
+ listSize: 10,
+ validateSearchInput: (value: string) => value
+ };
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ query: '',
+ selectedElements: [],
+ unselectedElements: [],
+ activeIdx: 0
+ };
+ }
+
+ componentDidMount() {
+ this.updateSelectedElements(this.props);
+ this.updateUnselectedElements(this.props as PropsWithDefault);
+ if (this.container) {
+ this.container.addEventListener('keydown', this.handleKeyboard, true);
+ }
+ }
+
+ componentWillReceiveProps(nextProps: PropsWithDefault) {
+ if (
+ this.props.elements !== nextProps.elements ||
+ this.props.selectedElements !== nextProps.selectedElements
+ ) {
+ this.updateSelectedElements(nextProps);
+ this.updateUnselectedElements(nextProps);
+
+ const totalElements = this.getAllElements(nextProps, this.state).length;
+ if (this.state.activeIdx >= totalElements) {
+ this.setState({ activeIdx: totalElements - 1 });
+ }
+ }
+ }
+
+ componentDidUpdate() {
+ if (this.searchInput) {
+ this.searchInput.focus();
+ }
+ }
+
+ componentWillUnmount() {
+ if (this.container) {
+ this.container.removeEventListener('keydown', this.handleKeyboard);
+ }
+ }
+
+ handleSelectChange = (item: string, selected: boolean) => {
+ if (selected) {
+ this.onSelectItem(item);
+ } else {
+ this.onUnselectItem(item);
+ }
+ };
+
+ handleSearchChange = (value: string) => {
+ this.onSearchQuery((this.props as PropsWithDefault).validateSearchInput(value));
+ };
+
+ handleElementHover = (element: string) => {
+ this.setState((prevState, props) => {
+ return { activeIdx: this.getAllElements(props, prevState).indexOf(element) };
+ });
+ };
+
+ handleKeyboard = (evt: KeyboardEvent) => {
+ switch (evt.keyCode) {
+ case 40: // down
+ this.setState(this.selectNextElement);
+ evt.stopPropagation();
+ evt.preventDefault();
+ break;
+ case 38: // up
+ this.setState(this.selectPreviousElement);
+ evt.stopPropagation();
+ evt.preventDefault();
+ break;
+ case 37: // left
+ case 39: // right
+ evt.stopPropagation();
+ break;
+ case 13: // enter
+ if (this.state.activeIdx >= 0) {
+ this.toggleSelect(this.getAllElements(this.props, this.state)[this.state.activeIdx]);
+ }
+ break;
+ }
+ };
+
+ onSearchQuery = (query: string) => {
+ this.setState({ query, activeIdx: 0 });
+ this.props.onSearch(query);
+ };
+
+ onSelectItem = (item: string) => {
+ if (this.isNewElement(item, this.props)) {
+ this.onSearchQuery('');
+ }
+ this.props.onSelect(item);
+ };
+
+ onUnselectItem = (item: string) => this.props.onUnselect(item);
+
+ isNewElement = (elem: string, { selectedElements, elements }: Props) =>
+ elem && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1;
+
+ updateSelectedElements = (props: Props) => {
+ this.setState((state: State) => {
+ if (state.query) {
+ return {
+ selectedElements: [...props.selectedElements.filter(elem => elem.includes(state.query))]
+ };
+ } else {
+ return { selectedElements: [...props.selectedElements] };
+ }
+ });
+ };
+
+ updateUnselectedElements = (props: PropsWithDefault) => {
+ this.setState((state: State) => {
+ if (props.listSize < state.selectedElements.length) {
+ return { unselectedElements: [] };
+ } else {
+ return {
+ unselectedElements: difference(props.elements, props.selectedElements).slice(
+ 0,
+ props.listSize - state.selectedElements.length
+ )
+ };
+ }
+ });
+ };
+
+ getAllElements = (props: Props, state: State) => {
+ if (this.isNewElement(state.query, props)) {
+ return [...state.selectedElements, ...state.unselectedElements, state.query];
+ } else {
+ return [...state.selectedElements, ...state.unselectedElements];
+ }
+ };
+
+ setElementActive = (idx: number) => this.setState({ activeIdx: idx });
+
+ selectNextElement = (state: State, props: Props) => {
+ const { activeIdx } = state;
+ const allElements = this.getAllElements(props, state);
+ if (activeIdx < 0 || activeIdx >= allElements.length - 1) {
+ return { activeIdx: 0 };
+ } else {
+ return { activeIdx: activeIdx + 1 };
+ }
+ };
+
+ selectPreviousElement = (state: State, props: Props) => {
+ const { activeIdx } = state;
+ const allElements = this.getAllElements(props, state);
+ if (activeIdx <= 0) {
+ const lastIdx = allElements.length - 1;
+ return { activeIdx: lastIdx };
+ } else {
+ return { activeIdx: activeIdx - 1 };
+ }
+ };
+
+ toggleSelect = (item: string) => {
+ if (this.props.selectedElements.indexOf(item) === -1) {
+ this.onSelectItem(item);
+ } else {
+ this.onUnselectItem(item);
+ }
+ };
+
+ render() {
+ const { query, activeIdx, selectedElements, unselectedElements } = this.state;
+ const activeElement = this.getAllElements(this.props, this.state)[activeIdx];
+
+ return (
+ <div className="multi-select" ref={div => (this.container = div)}>
+ <div className="menu-search">
+ <SearchBox
+ autoFocus={true}
+ onChange={this.handleSearchChange}
+ placeholder={this.props.placeholder}
+ value={query}
+ />
+ </div>
+ <ul className="menu">
+ {selectedElements.length > 0 &&
+ selectedElements.map(element => (
+ <MultiSelectOption
+ key={element}
+ element={element}
+ selected={true}
+ active={activeElement === element}
+ onSelectChange={this.handleSelectChange}
+ onHover={this.handleElementHover}
+ />
+ ))}
+ {unselectedElements.length > 0 &&
+ unselectedElements.map(element => (
+ <MultiSelectOption
+ key={element}
+ element={element}
+ active={activeElement === element}
+ onSelectChange={this.handleSelectChange}
+ onHover={this.handleElementHover}
+ />
+ ))}
+ {this.isNewElement(query, this.props) && (
+ <MultiSelectOption
+ key={query}
+ element={query}
+ custom={true}
+ active={activeElement === query}
+ onSelectChange={this.handleSelectChange}
+ onHover={this.handleElementHover}
+ />
+ )}
+ </ul>
+ </div>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import classNames from 'classnames';
-
-/*::
-type Props = {
- element: string,
- selected: boolean,
- custom: boolean,
- active: boolean,
- onSelectChange: (string, boolean) => void,
- onHover: string => void
-};
-*/
-
-export default class MultiSelectOption extends React.PureComponent {
- /*:: props: Props; */
-
- static defaultProps = {
- selected: false,
- custom: false,
- active: false
- };
-
- handleSelect = (evt /*: SyntheticInputEvent */) => {
- evt.stopPropagation();
- evt.preventDefault();
- evt.target.blur();
- this.props.onSelectChange(this.props.element, !this.props.selected);
- };
-
- handleHover = () => {
- this.props.onHover(this.props.element);
- };
-
- render() {
- const className = classNames('icon-checkbox', {
- 'icon-checkbox-checked': this.props.selected
- });
- const activeClass = classNames({ active: this.props.active });
-
- return (
- <li>
- <a
- href="#"
- className={activeClass}
- onClick={this.handleSelect}
- onMouseOver={this.handleHover}
- onFocus={this.handleHover}>
- <i className={className} /> {this.props.custom && '+ '}
- {this.props.element}
- </a>
- </li>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+
+interface Props {
+ element: string;
+ selected?: boolean;
+ custom?: boolean;
+ active?: boolean;
+ onSelectChange: (elem: string, selected: boolean) => void;
+ onHover: (elem: string) => void;
+}
+
+export default class MultiSelectOption extends React.PureComponent<Props> {
+ handleSelect = (evt: React.SyntheticEvent<HTMLAnchorElement>) => {
+ evt.stopPropagation();
+ evt.preventDefault();
+ evt.currentTarget.blur();
+ this.props.onSelectChange(this.props.element, !this.props.selected);
+ };
+
+ handleHover = () => this.props.onHover(this.props.element);
+
+ render() {
+ const className = classNames('icon-checkbox', {
+ 'icon-checkbox-checked': this.props.selected
+ });
+ const activeClass = classNames({ active: this.props.active });
+
+ return (
+ <li>
+ <a
+ href="#"
+ className={activeClass}
+ onClick={this.handleSelect}
+ onMouseOver={this.handleHover}
+ onFocus={this.handleHover}>
+ <i className={className} /> {this.props.custom && '+ '}
+ {this.props.element}
+ </a>
+ </li>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow, mount } from 'enzyme';
-import React from 'react';
-import MultiSelect from '../MultiSelect';
-
-const props = {
- selectedElements: ['bar'],
- elements: [],
- onSearch: () => {},
- onSelect: () => {},
- onUnselect: () => {},
- placeholder: ''
-};
-
-const elements = ['foo', 'bar', 'baz'];
-
-it('should render multiselect with selected elements', () => {
- const multiselect = shallow(<MultiSelect {...props} />);
- // Will not only the selected element
- expect(multiselect).toMatchSnapshot();
-
- multiselect.setProps({ elements });
- expect(multiselect).toMatchSnapshot();
- multiselect.setState({ activeIdx: 2 });
- expect(multiselect).toMatchSnapshot();
- multiselect.setState({ query: 'test' });
- expect(multiselect).toMatchSnapshot();
-});
-
-it('should render with the focus inside the search input', () => {
- const multiselect = mount(<MultiSelect {...props} />);
- expect(multiselect.find('input').getDOMNode()).toBe(document.activeElement);
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow, mount } from 'enzyme';
+import MultiSelect from '../MultiSelect';
+
+const props = {
+ selectedElements: ['bar'],
+ elements: [],
+ onSearch: () => {},
+ onSelect: () => {},
+ onUnselect: () => {},
+ placeholder: ''
+};
+
+const elements = ['foo', 'bar', 'baz'];
+
+it('should render multiselect with selected elements', () => {
+ const multiselect = shallow(<MultiSelect {...props} />);
+ // Will not only the selected element
+ expect(multiselect).toMatchSnapshot();
+
+ multiselect.setProps({ elements });
+ expect(multiselect).toMatchSnapshot();
+ multiselect.setState({ activeIdx: 2 });
+ expect(multiselect).toMatchSnapshot();
+ multiselect.setState({ query: 'test' });
+ expect(multiselect).toMatchSnapshot();
+});
+
+it('should render with the focus inside the search input', () => {
+ const multiselect = mount(<MultiSelect {...props} />);
+ expect(multiselect.find('input').getDOMNode()).toBe(document.activeElement);
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import React from 'react';
-import MultiSelectOption from '../MultiSelectOption';
-
-const props = {
- element: 'mytag',
- selected: false,
- custom: false,
- active: false,
- onSelectChange: () => {},
- onHover: () => {}
-};
-
-it('should render standard tag', () => {
- expect(shallow(<MultiSelectOption {...props} />)).toMatchSnapshot();
-});
-
-it('should render selected tag', () => {
- expect(shallow(<MultiSelectOption {...props} selected={true} />)).toMatchSnapshot();
-});
-
-it('should render custom tag', () => {
- expect(shallow(<MultiSelectOption {...props} custom={true} />)).toMatchSnapshot();
-});
-
-it('should render active tag', () => {
- expect(shallow(<MultiSelectOption {...props} selected={true} active={true} />)).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import MultiSelectOption from '../MultiSelectOption';
+
+const props = {
+ element: 'mytag',
+ onSelectChange: () => {},
+ onHover: () => {}
+};
+
+it('should render standard tag', () => {
+ expect(shallow(<MultiSelectOption {...props} />)).toMatchSnapshot();
+});
+
+it('should render selected tag', () => {
+ expect(shallow(<MultiSelectOption {...props} selected={true} />)).toMatchSnapshot();
+});
+
+it('should render custom tag', () => {
+ expect(shallow(<MultiSelectOption {...props} custom={true} />)).toMatchSnapshot();
+});
+
+it('should render active tag', () => {
+ expect(shallow(<MultiSelectOption {...props} selected={true} active={true} />)).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render multiselect with selected elements 1`] = `
-<div
- className="multi-select"
->
- <div
- className="menu-search"
- >
- <SearchBox
- autoFocus={true}
- onChange={[Function]}
- placeholder=""
- value=""
- />
- </div>
- <ul
- className="menu"
- >
- <MultiSelectOption
- active={true}
- custom={false}
- element="bar"
- key="bar"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={true}
- />
- </ul>
-</div>
-`;
-
-exports[`should render multiselect with selected elements 2`] = `
-<div
- className="multi-select"
->
- <div
- className="menu-search"
- >
- <SearchBox
- autoFocus={true}
- onChange={[Function]}
- placeholder=""
- value=""
- />
- </div>
- <ul
- className="menu"
- >
- <MultiSelectOption
- active={true}
- custom={false}
- element="bar"
- key="bar"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={true}
- />
- <MultiSelectOption
- active={false}
- custom={false}
- element="foo"
- key="foo"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- <MultiSelectOption
- active={false}
- custom={false}
- element="baz"
- key="baz"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- </ul>
-</div>
-`;
-
-exports[`should render multiselect with selected elements 3`] = `
-<div
- className="multi-select"
->
- <div
- className="menu-search"
- >
- <SearchBox
- autoFocus={true}
- onChange={[Function]}
- placeholder=""
- value=""
- />
- </div>
- <ul
- className="menu"
- >
- <MultiSelectOption
- active={false}
- custom={false}
- element="bar"
- key="bar"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={true}
- />
- <MultiSelectOption
- active={false}
- custom={false}
- element="foo"
- key="foo"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- <MultiSelectOption
- active={true}
- custom={false}
- element="baz"
- key="baz"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- </ul>
-</div>
-`;
-
-exports[`should render multiselect with selected elements 4`] = `
-<div
- className="multi-select"
->
- <div
- className="menu-search"
- >
- <SearchBox
- autoFocus={true}
- onChange={[Function]}
- placeholder=""
- value="test"
- />
- </div>
- <ul
- className="menu"
- >
- <MultiSelectOption
- active={false}
- custom={false}
- element="bar"
- key="bar"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={true}
- />
- <MultiSelectOption
- active={false}
- custom={false}
- element="foo"
- key="foo"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- <MultiSelectOption
- active={true}
- custom={false}
- element="baz"
- key="baz"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- <MultiSelectOption
- active={false}
- custom={true}
- element="test"
- key="test"
- onHover={[Function]}
- onSelectChange={[Function]}
- selected={false}
- />
- </ul>
-</div>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render multiselect with selected elements 1`] = `
+<div
+ className="multi-select"
+>
+ <div
+ className="menu-search"
+ >
+ <SearchBox
+ autoFocus={true}
+ onChange={[Function]}
+ placeholder=""
+ value=""
+ />
+ </div>
+ <ul
+ className="menu"
+ >
+ <MultiSelectOption
+ active={true}
+ element="bar"
+ key="bar"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ selected={true}
+ />
+ </ul>
+</div>
+`;
+
+exports[`should render multiselect with selected elements 2`] = `
+<div
+ className="multi-select"
+>
+ <div
+ className="menu-search"
+ >
+ <SearchBox
+ autoFocus={true}
+ onChange={[Function]}
+ placeholder=""
+ value=""
+ />
+ </div>
+ <ul
+ className="menu"
+ >
+ <MultiSelectOption
+ active={true}
+ element="bar"
+ key="bar"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ selected={true}
+ />
+ <MultiSelectOption
+ active={false}
+ element="foo"
+ key="foo"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ <MultiSelectOption
+ active={false}
+ element="baz"
+ key="baz"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ </ul>
+</div>
+`;
+
+exports[`should render multiselect with selected elements 3`] = `
+<div
+ className="multi-select"
+>
+ <div
+ className="menu-search"
+ >
+ <SearchBox
+ autoFocus={true}
+ onChange={[Function]}
+ placeholder=""
+ value=""
+ />
+ </div>
+ <ul
+ className="menu"
+ >
+ <MultiSelectOption
+ active={false}
+ element="bar"
+ key="bar"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ selected={true}
+ />
+ <MultiSelectOption
+ active={false}
+ element="foo"
+ key="foo"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ <MultiSelectOption
+ active={true}
+ element="baz"
+ key="baz"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ </ul>
+</div>
+`;
+
+exports[`should render multiselect with selected elements 4`] = `
+<div
+ className="multi-select"
+>
+ <div
+ className="menu-search"
+ >
+ <SearchBox
+ autoFocus={true}
+ onChange={[Function]}
+ placeholder=""
+ value="test"
+ />
+ </div>
+ <ul
+ className="menu"
+ >
+ <MultiSelectOption
+ active={false}
+ element="bar"
+ key="bar"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ selected={true}
+ />
+ <MultiSelectOption
+ active={false}
+ element="foo"
+ key="foo"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ <MultiSelectOption
+ active={true}
+ element="baz"
+ key="baz"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ <MultiSelectOption
+ active={false}
+ custom={true}
+ element="test"
+ key="test"
+ onHover={[Function]}
+ onSelectChange={[Function]}
+ />
+ </ul>
+</div>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render active tag 1`] = `
-<li>
- <a
- className="active"
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
- >
- <i
- className="icon-checkbox icon-checkbox-checked"
- />
-
- mytag
- </a>
-</li>
-`;
-
-exports[`should render custom tag 1`] = `
-<li>
- <a
- className=""
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
- >
- <i
- className="icon-checkbox"
- />
-
- +
- mytag
- </a>
-</li>
-`;
-
-exports[`should render selected tag 1`] = `
-<li>
- <a
- className=""
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
- >
- <i
- className="icon-checkbox icon-checkbox-checked"
- />
-
- mytag
- </a>
-</li>
-`;
-
-exports[`should render standard tag 1`] = `
-<li>
- <a
- className=""
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
- >
- <i
- className="icon-checkbox"
- />
-
- mytag
- </a>
-</li>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render active tag 1`] = `
+<li>
+ <a
+ className="active"
+ href="#"
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+ >
+ <i
+ className="icon-checkbox icon-checkbox-checked"
+ />
+
+ mytag
+ </a>
+</li>
+`;
+
+exports[`should render custom tag 1`] = `
+<li>
+ <a
+ className=""
+ href="#"
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+ >
+ <i
+ className="icon-checkbox"
+ />
+
+ +
+ mytag
+ </a>
+</li>
+`;
+
+exports[`should render selected tag 1`] = `
+<li>
+ <a
+ className=""
+ href="#"
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+ >
+ <i
+ className="icon-checkbox icon-checkbox-checked"
+ />
+
+ mytag
+ </a>
+</li>
+`;
+
+exports[`should render standard tag 1`] = `
+<li>
+ <a
+ className=""
+ href="#"
+ onClick={[Function]}
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+ >
+ <i
+ className="icon-checkbox"
+ />
+
+ mytag
+ </a>
+</li>
+`;
render() {
return (
- // $FlowFixMe `this.props.popupPosition` is passed from `BabelPopupHelper`
<TagsSelector
position={this.props.popupPosition}
tags={this.state.searchResult}
import Rating from '../ui/Rating';
import Level from '../ui/Level';
import Tooltips from '../controls/Tooltip';
-import { formatMeasure, isDiffMetric, MeasureEnhanced } from '../../helpers/measures';
-import { formatLeak, getRatingTooltip } from './utils';
+import { formatMeasure } from '../../helpers/measures';
+import { getRatingTooltip } from './utils';
interface Props {
className?: string;
decimals?: number | null;
- measure?: MeasureEnhanced;
+ value?: string;
+ metricKey: string;
+ metricType: string;
}
-export default function Measure({ className, decimals, measure }: Props) {
- if (measure === undefined) {
- return <span>{'–'}</span>;
- }
-
- const { metric } = measure;
- const value = isDiffMetric(metric.key) ? measure.leak : measure.value;
-
+export default function Measure({ className, decimals, metricKey, metricType, value }: Props) {
if (value === undefined) {
return <span>{'–'}</span>;
}
- if (metric.type === 'LEVEL') {
+ if (metricType === 'LEVEL') {
return <Level className={className} level={value} />;
}
- if (metric.type !== 'RATING') {
- const formattedValue = isDiffMetric(metric.key)
- ? formatLeak(measure.leak, metric.key, metric.type, { decimals })
- : formatMeasure(measure.value, metric.type, { decimals });
+ if (metricType !== 'RATING') {
+ const formattedValue = formatMeasure(value, metricType, { decimals });
return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>;
}
- const tooltip = getRatingTooltip(metric.key, Number(value));
+ const tooltip = getRatingTooltip(metricKey, Number(value));
const rating = <Rating value={value} />;
if (tooltip) {
return (
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/* eslint-disable import/first */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Measure from '../Measure';
+
jest.mock('../../../helpers/measures', () => {
const measures = require.requireActual('../../../helpers/measures');
measures.getRatingTooltip = jest.fn(() => 'tooltip');
return measures;
});
-import * as React from 'react';
-import { shallow } from 'enzyme';
-import Measure from '../Measure';
-
it('renders trivial measure', () => {
- const measure = { metric: { key: 'coverage', name: 'Coverage', type: 'PERCENT' }, value: '73.0' };
- expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+ expect(
+ shallow(<Measure metricKey="coverage" metricType="PERCENT" value="73.0" />)
+ ).toMatchSnapshot();
});
it('renders leak measure', () => {
- const measure = {
- metric: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' },
- leak: '36.0'
- };
- expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+ expect(
+ shallow(<Measure metricKey="new_coverage" metricType="PERCENT" value="36.0" />)
+ ).toMatchSnapshot();
});
it('renders LEVEL', () => {
- const measure = {
- metric: { key: 'quality_gate_status', name: 'Quality Gate', type: 'LEVEL' },
- value: 'ERROR'
- };
- expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+ expect(
+ shallow(<Measure metricKey="quality_gate_status" metricType="LEVEL" value="ERROR" />)
+ ).toMatchSnapshot();
});
it('renders known RATING', () => {
- const measure = {
- metric: { key: 'sqale_rating', name: 'Maintainability Rating', type: 'RATING' },
- value: '3'
- };
- expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+ expect(
+ shallow(<Measure metricKey="sqale_rating" metricType="RATING" value="3" />)
+ ).toMatchSnapshot();
});
it('renders unknown RATING', () => {
- const measure = {
- metric: { key: 'foo_rating', name: 'Foo Rating', type: 'RATING' },
- value: '4'
- };
- expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+ expect(
+ shallow(<Measure metricKey="foo_rating" metricType="RATING" value="4" />)
+ ).toMatchSnapshot();
});
it('renders undefined measure', () => {
- const measure = { metric: { key: 'foo', name: 'Foo', type: 'PERCENT' } };
- expect(shallow(<Measure measure={measure} />)).toMatchSnapshot();
+ expect(shallow(<Measure metricKey="foo" metricType="PERCENT" />)).toMatchSnapshot();
});
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import {
- formatMeasure,
- formatMeasureVariation,
getRatingTooltip as nextGetRatingTooltip,
isDiffMetric,
Measure,
};
}
-export function formatLeak(
- value: string | undefined,
- metricKey: string,
- metricType: string,
- options: any
-): string {
- if (isDiffMetric(metricKey)) {
- return formatMeasure(value, metricType, options);
- } else {
- return formatMeasureVariation(value, metricType, options);
- }
-}
-
export function getLeakValue(measure: Measure | undefined): string | undefined {
if (!measure || !measure.periods) {
return undefined;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { History } from '../../api/time-machine';
+import { Metric } from '../../app/types';
+
+interface Props {
+ branch?: string;
+ history?: History;
+ metrics: Metric[];
+ project: string;
+ renderWhenEmpty?: () => void;
+}
+
+export default class PreviewGraph extends React.Component<Props> {}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import { getComponentDrilldownUrl, getComponentIssuesUrl } from '../../helpers/urls';
+
+const ISSUE_MEASURES = [
+ 'violations',
+ 'new_violations',
+ 'blocker_violations',
+ 'critical_violations',
+ 'major_violations',
+ 'minor_violations',
+ 'info_violations',
+ 'new_blocker_violations',
+ 'new_critical_violations',
+ 'new_major_violations',
+ 'new_minor_violations',
+ 'new_info_violations',
+ 'open_issues',
+ 'reopened_issues',
+ 'confirmed_issues',
+ 'false_positive_issues',
+ 'code_smells',
+ 'new_code_smells',
+ 'bugs',
+ 'new_bugs',
+ 'vulnerabilities',
+ 'new_vulnerabilities'
+];
+
+interface Props {
+ branch?: string;
+ children?: React.ReactNode;
+ className?: string;
+ component: string;
+ metric: string;
+ sinceLeakPeriod?: boolean;
+}
+
+export default class DrilldownLink extends React.PureComponent<Props> {
+ isIssueMeasure = () => {
+ return ISSUE_MEASURES.indexOf(this.props.metric) !== -1;
+ };
+
+ propsToIssueParams = () => {
+ const params: { [key: string]: string | boolean } = {};
+
+ if (this.props.sinceLeakPeriod) {
+ params.sinceLeakPeriod = true;
+ }
+
+ switch (this.props.metric) {
+ case 'blocker_violations':
+ case 'new_blocker_violations':
+ Object.assign(params, { resolved: 'false', severities: 'BLOCKER' });
+ break;
+ case 'critical_violations':
+ case 'new_critical_violations':
+ Object.assign(params, { resolved: 'false', severities: 'CRITICAL' });
+ break;
+ case 'major_violations':
+ case 'new_major_violations':
+ Object.assign(params, { resolved: 'false', severities: 'MAJOR' });
+ break;
+ case 'minor_violations':
+ case 'new_minor_violations':
+ Object.assign(params, { resolved: 'false', severities: 'MINOR' });
+ break;
+ case 'info_violations':
+ case 'new_info_violations':
+ Object.assign(params, { resolved: 'false', severities: 'INFO' });
+ break;
+ case 'open_issues':
+ Object.assign(params, { resolved: 'false', statuses: 'OPEN' });
+ break;
+ case 'reopened_issues':
+ Object.assign(params, { resolved: 'false', statuses: 'REOPENED' });
+ break;
+ case 'confirmed_issues':
+ Object.assign(params, { resolved: 'false', statuses: 'CONFIRMED' });
+ break;
+ case 'false_positive_issues':
+ Object.assign(params, { resolutions: 'FALSE-POSITIVE' });
+ break;
+ case 'code_smells':
+ case 'new_code_smells':
+ Object.assign(params, { resolved: 'false', types: 'CODE_SMELL' });
+ break;
+ case 'bugs':
+ case 'new_bugs':
+ Object.assign(params, { resolved: 'false', types: 'BUG' });
+ break;
+ case 'vulnerabilities':
+ case 'new_vulnerabilities':
+ Object.assign(params, { resolved: 'false', types: 'VULNERABILITY' });
+ break;
+ default:
+ Object.assign(params, { resolved: 'false' });
+ }
+ return params;
+ };
+
+ renderIssuesLink = () => {
+ const url = getComponentIssuesUrl(this.props.component, {
+ ...this.propsToIssueParams(),
+ branch: this.props.branch
+ });
+
+ return (
+ <Link to={url} className={this.props.className}>
+ {this.props.children}
+ </Link>
+ );
+ };
+
+ render() {
+ if (this.isIssueMeasure()) {
+ return this.renderIssuesLink();
+ }
+
+ const url = getComponentDrilldownUrl(
+ this.props.component,
+ this.props.metric,
+ this.props.branch
+ );
+ return (
+ <Link to={url} className={this.props.className}>
+ {this.props.children}
+ </Link>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import React from 'react';
-import PropTypes from 'prop-types';
-import { Link } from 'react-router';
-import { getComponentDrilldownUrl, getComponentIssuesUrl } from '../../helpers/urls';
-
-const ISSUE_MEASURES = [
- 'violations',
- 'new_violations',
- 'blocker_violations',
- 'critical_violations',
- 'major_violations',
- 'minor_violations',
- 'info_violations',
- 'new_blocker_violations',
- 'new_critical_violations',
- 'new_major_violations',
- 'new_minor_violations',
- 'new_info_violations',
- 'open_issues',
- 'reopened_issues',
- 'confirmed_issues',
- 'false_positive_issues',
- 'code_smells',
- 'new_code_smells',
- 'bugs',
- 'new_bugs',
- 'vulnerabilities',
- 'new_vulnerabilities'
-];
-
-export class DrilldownLink extends React.PureComponent {
- static propTypes = {
- branch: PropTypes.string,
- children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
- className: PropTypes.string,
- component: PropTypes.string.isRequired,
- metric: PropTypes.string.isRequired,
- sinceLeakPeriod: PropTypes.bool
- };
- isIssueMeasure = () => {
- return ISSUE_MEASURES.indexOf(this.props.metric) !== -1;
- };
-
- propsToIssueParams = () => {
- const params = {};
-
- if (this.props.sinceLeakPeriod) {
- params.sinceLeakPeriod = true;
- }
-
- switch (this.props.metric) {
- case 'blocker_violations':
- case 'new_blocker_violations':
- Object.assign(params, { resolved: 'false', severities: 'BLOCKER' });
- break;
- case 'critical_violations':
- case 'new_critical_violations':
- Object.assign(params, { resolved: 'false', severities: 'CRITICAL' });
- break;
- case 'major_violations':
- case 'new_major_violations':
- Object.assign(params, { resolved: 'false', severities: 'MAJOR' });
- break;
- case 'minor_violations':
- case 'new_minor_violations':
- Object.assign(params, { resolved: 'false', severities: 'MINOR' });
- break;
- case 'info_violations':
- case 'new_info_violations':
- Object.assign(params, { resolved: 'false', severities: 'INFO' });
- break;
- case 'open_issues':
- Object.assign(params, { resolved: 'false', statuses: 'OPEN' });
- break;
- case 'reopened_issues':
- Object.assign(params, { resolved: 'false', statuses: 'REOPENED' });
- break;
- case 'confirmed_issues':
- Object.assign(params, { resolved: 'false', statuses: 'CONFIRMED' });
- break;
- case 'false_positive_issues':
- Object.assign(params, { resolutions: 'FALSE-POSITIVE' });
- break;
- case 'code_smells':
- case 'new_code_smells':
- Object.assign(params, { resolved: 'false', types: 'CODE_SMELL' });
- break;
- case 'bugs':
- case 'new_bugs':
- Object.assign(params, { resolved: 'false', types: 'BUG' });
- break;
- case 'vulnerabilities':
- case 'new_vulnerabilities':
- Object.assign(params, { resolved: 'false', types: 'VULNERABILITY' });
- break;
- default:
- Object.assign(params, { resolved: 'false' });
- }
- return params;
- };
-
- renderIssuesLink = () => {
- const url = getComponentIssuesUrl(this.props.component, {
- ...this.propsToIssueParams(),
- branch: this.props.branch
- });
-
- return (
- <Link to={url} className={this.props.className}>
- {this.props.children}
- </Link>
- );
- };
-
- render() {
- if (this.isIssueMeasure()) {
- return this.renderIssuesLink();
- }
-
- const url = getComponentDrilldownUrl(
- this.props.component,
- this.props.metric,
- this.props.branch
- );
- return (
- <Link to={url} className={this.props.className}>
- {this.props.children}
- </Link>
- );
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-// @flow
-import React from 'react';
-import BubblePopup from '../common/BubblePopup';
-import MultiSelect from '../common/MultiSelect';
-import { translate } from '../../helpers/l10n';
-import './TagsList.css';
-
-/*::
-type Props = {
- position: {},
- tags: Array<string>,
- selectedTags: Array<string>,
- listSize: number,
- onSearch: string => void,
- onSelect: string => void,
- onUnselect: string => void
-};
-*/
-
-export default function TagsSelector(props /*: Props */) {
- return (
- <BubblePopup
- position={props.position}
- customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300">
- <MultiSelect
- elements={props.tags}
- selectedElements={props.selectedTags}
- listSize={props.listSize}
- onSearch={props.onSearch}
- onSelect={props.onSelect}
- onUnselect={props.onUnselect}
- validateSearchInput={validateTag}
- placeholder={translate('search.search_for_tags')}
- />
- </BubblePopup>
- );
-}
-
-export function validateTag(value /*: string */) {
- // Allow only a-z, 0-9, '+', '-', '#', '.'
- return value.toLowerCase().replace(/[^a-z0-9\+\-#.]/gi, '');
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import BubblePopup, { BubblePopupPosition } from '../common/BubblePopup';
+import MultiSelect from '../common/MultiSelect';
+import { translate } from '../../helpers/l10n';
+import './TagsList.css';
+
+interface Props {
+ position: BubblePopupPosition;
+ tags: string[];
+ selectedTags: string[];
+ listSize: number;
+ onSearch: (query: string) => void;
+ onSelect: (item: string) => void;
+ onUnselect: (item: string) => void;
+}
+
+export default function TagsSelector(props: Props) {
+ return (
+ <BubblePopup
+ position={props.position}
+ customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300">
+ <MultiSelect
+ elements={props.tags}
+ selectedElements={props.selectedTags}
+ listSize={props.listSize}
+ onSearch={props.onSearch}
+ onSelect={props.onSelect}
+ onUnselect={props.onUnselect}
+ validateSearchInput={validateTag}
+ placeholder={translate('search.search_for_tags')}
+ />
+ </BubblePopup>
+ );
+}
+
+export function validateTag(value: string) {
+ // Allow only a-z, 0-9, '+', '-', '#', '.'
+ return value.toLowerCase().replace(/[^a-z0-9\+\-#.]/gi, '');
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import React from 'react';
-import TagsSelector, { validateTag } from '../TagsSelector';
-
-const props = {
- position: { left: 0, top: 0 },
- tags: ['foo', 'bar', 'baz'],
- selectedTags: ['bar'],
- onSearch: () => {},
- onSelect: () => {},
- onUnselect: () => {}
-};
-
-it('should render with selected tags', () => {
- const tagsSelector = shallow(<TagsSelector {...props} />);
- expect(tagsSelector).toMatchSnapshot();
-});
-
-it('should render without tags at all', () => {
- expect(shallow(<TagsSelector {...props} tags={[]} selectedTags={[]} />)).toMatchSnapshot();
-});
-
-it('should validate tags correctly', () => {
- const validChars = 'abcdefghijklmnopqrstuvwxyz0123456789+-#.';
- expect(validateTag('test')).toBe('test');
- expect(validateTag(validChars)).toBe(validChars);
- expect(validateTag(validChars.toUpperCase())).toBe(validChars);
- expect(validateTag('T E$ST')).toBe('test');
- expect(validateTag('T E$st!^àéèing1')).toBe('testing1');
-});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import TagsSelector, { validateTag } from '../TagsSelector';
+
+const props = {
+ position: { right: 0, top: 0 },
+ listSize: 10,
+ tags: ['foo', 'bar', 'baz'],
+ selectedTags: ['bar'],
+ onSearch: () => {},
+ onSelect: () => {},
+ onUnselect: () => {}
+};
+
+it('should render with selected tags', () => {
+ const tagsSelector = shallow(<TagsSelector {...props} />);
+ expect(tagsSelector).toMatchSnapshot();
+});
+
+it('should render without tags at all', () => {
+ expect(shallow(<TagsSelector {...props} tags={[]} selectedTags={[]} />)).toMatchSnapshot();
+});
+
+it('should validate tags correctly', () => {
+ const validChars = 'abcdefghijklmnopqrstuvwxyz0123456789+-#.';
+ expect(validateTag('test')).toBe('test');
+ expect(validateTag(validChars)).toBe(validChars);
+ expect(validateTag(validChars.toUpperCase())).toBe(validChars);
+ expect(validateTag('T E$ST')).toBe('test');
+ expect(validateTag('T E$st!^àéèing1')).toBe('testing1');
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render with selected tags 1`] = `
-<BubblePopup
- customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300"
- position={
- Object {
- "left": 0,
- "top": 0,
- }
- }
->
- <MultiSelect
- elements={
- Array [
- "foo",
- "bar",
- "baz",
- ]
- }
- listSize={10}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- placeholder="search.search_for_tags"
- selectedElements={
- Array [
- "bar",
- ]
- }
- validateSearchInput={[Function]}
- />
-</BubblePopup>
-`;
-
-exports[`should render without tags at all 1`] = `
-<BubblePopup
- customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300"
- position={
- Object {
- "left": 0,
- "top": 0,
- }
- }
->
- <MultiSelect
- elements={Array []}
- listSize={10}
- onSearch={[Function]}
- onSelect={[Function]}
- onUnselect={[Function]}
- placeholder="search.search_for_tags"
- selectedElements={Array []}
- validateSearchInput={[Function]}
- />
-</BubblePopup>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render with selected tags 1`] = `
+<BubblePopup
+ customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300"
+ position={
+ Object {
+ "right": 0,
+ "top": 0,
+ }
+ }
+>
+ <MultiSelect
+ elements={
+ Array [
+ "foo",
+ "bar",
+ "baz",
+ ]
+ }
+ listSize={10}
+ onSearch={[Function]}
+ onSelect={[Function]}
+ onUnselect={[Function]}
+ placeholder="search.search_for_tags"
+ selectedElements={
+ Array [
+ "bar",
+ ]
+ }
+ validateSearchInput={[Function]}
+ />
+</BubblePopup>
+`;
+
+exports[`should render without tags at all 1`] = `
+<BubblePopup
+ customClass="bubble-popup-bottom-right bubble-popup-menu abs-width-300"
+ position={
+ Object {
+ "right": 0,
+ "top": 0,
+ }
+ }
+>
+ <MultiSelect
+ elements={Array []}
+ listSize={10}
+ onSearch={[Function]}
+ onSelect={[Function]}
+ onUnselect={[Function]}
+ placeholder="search.search_for_tags"
+ selectedElements={Array []}
+ validateSearchInput={[Function]}
+ />
+</BubblePopup>
+`;
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { resetBundle } from '../l10n';
-import { formatMeasure, formatMeasureVariation } from '../measures';
+import { formatMeasure } from '../measures';
const HOURS_IN_DAY = 8;
const ONE_MINUTE = 1;
expect(formatMeasure(undefined, 'INT')).toBe('');
});
});
-
-describe('#formatMeasureVariation()', () => {
- it('should format INT', () => {
- expect(formatMeasureVariation(0, 'INT')).toBe('+0');
- expect(formatMeasureVariation(1, 'INT')).toBe('+1');
- expect(formatMeasureVariation(-1, 'INT')).toBe('-1');
- expect(formatMeasureVariation(1529, 'INT')).toBe('+1,529');
- expect(formatMeasureVariation(-1529, 'INT')).toBe('-1,529');
- });
-
- it('should format SHORT_INT', () => {
- expect(formatMeasureVariation(0, 'SHORT_INT')).toBe('+0');
- expect(formatMeasureVariation(1, 'SHORT_INT')).toBe('+1');
- expect(formatMeasureVariation(-1, 'SHORT_INT')).toBe('-1');
- expect(formatMeasureVariation(1529, 'SHORT_INT')).toBe('+1.5k');
- expect(formatMeasureVariation(-1529, 'SHORT_INT')).toBe('-1.5k');
- expect(formatMeasureVariation(10678, 'SHORT_INT')).toBe('+11k');
- expect(formatMeasureVariation(-10678, 'SHORT_INT')).toBe('-11k');
- });
-
- it('should format FLOAT', () => {
- expect(formatMeasureVariation(0.0, 'FLOAT')).toBe('+0.0');
- expect(formatMeasureVariation(1.0, 'FLOAT')).toBe('+1.0');
- expect(formatMeasureVariation(-1.0, 'FLOAT')).toBe('-1.0');
- expect(formatMeasureVariation(50.89, 'FLOAT')).toBe('+50.89');
- expect(formatMeasureVariation(-50.89, 'FLOAT')).toBe('-50.89');
- });
-
- it('should respect FLOAT precision', () => {
- expect(formatMeasureVariation(0.1, 'FLOAT')).toBe('+0.1');
- expect(formatMeasureVariation(0.12, 'FLOAT')).toBe('+0.12');
- expect(formatMeasureVariation(0.12345, 'FLOAT')).toBe('+0.12345');
- expect(formatMeasureVariation(0.123456, 'FLOAT')).toBe('+0.12346');
- });
-
- it('should format PERCENT', () => {
- expect(formatMeasureVariation(0.0, 'PERCENT')).toBe('+0.0%');
- expect(formatMeasureVariation(1.0, 'PERCENT')).toBe('+1.0%');
- expect(formatMeasureVariation(-1.0, 'PERCENT')).toBe('-1.0%');
- expect(formatMeasureVariation(50.89, 'PERCENT')).toBe('+50.9%');
- expect(formatMeasureVariation(-50.89, 'PERCENT')).toBe('-50.9%');
- });
-
- it('should format WORK_DUR', () => {
- expect(formatMeasureVariation(0, 'WORK_DUR')).toBe('+0');
- expect(formatMeasureVariation(5 * ONE_DAY, 'WORK_DUR')).toBe('+5d');
- expect(formatMeasureVariation(2 * ONE_HOUR, 'WORK_DUR')).toBe('+2h');
- expect(formatMeasureVariation(ONE_MINUTE, 'WORK_DUR')).toBe('+1min');
- expect(formatMeasureVariation(-5 * ONE_DAY, 'WORK_DUR')).toBe('-5d');
- expect(formatMeasureVariation(-2 * ONE_HOUR, 'WORK_DUR')).toBe('-2h');
- expect(formatMeasureVariation(-1 * ONE_MINUTE, 'WORK_DUR')).toBe('-1min');
- });
-
- it('should format SHORT_WORK_DUR', () => {
- expect(formatMeasureVariation(0, 'SHORT_WORK_DUR')).toBe('+0');
- expect(formatMeasureVariation(5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('+5d');
- expect(formatMeasureVariation(2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+2h');
- expect(formatMeasureVariation(ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+1min');
- expect(formatMeasureVariation(30 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+30min');
- expect(formatMeasureVariation(58 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+1h');
- expect(formatMeasureVariation(5 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+5d');
- expect(formatMeasureVariation(2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+2h');
- expect(formatMeasureVariation(ONE_HOUR + 55 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+2h');
- expect(formatMeasureVariation(3 * ONE_DAY + 6 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+4d');
- expect(formatMeasureVariation(7 * ONE_HOUR + 59 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+1d');
- expect(formatMeasureVariation(5 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe(
- '+5d'
- );
- expect(formatMeasureVariation(15 * ONE_DAY + 2 * ONE_HOUR + ONE_MINUTE, 'SHORT_WORK_DUR')).toBe(
- '+15d'
- );
- expect(formatMeasureVariation(7 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('+7min');
- expect(formatMeasureVariation(-5 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('-5d');
- expect(formatMeasureVariation(-2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('-2h');
- expect(formatMeasureVariation(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('-1min');
-
- expect(formatMeasureVariation(1529 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('+1.5kd');
- expect(formatMeasureVariation(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('+1Md');
- expect(formatMeasureVariation(1234567 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('+1Md');
- });
-
- it('should not format unknown type', () => {
- expect(formatMeasureVariation('random value', 'RANDOM_TYPE')).toBe('random value');
- });
-
- it('should not fail with undefined', () => {
- expect(formatMeasureVariation(undefined, 'INT')).toBe('');
- });
-});
return useFormatter(value, formatter, options);
}
-/** Format a measure variation for a given type */
-export function formatMeasureVariation(
- value: string | number | undefined,
- type: string,
- options?: any
-): string {
- const formatter = getVariationFormatter(type);
- return useFormatter(value, formatter, options);
-}
-
/** Return a localized metric name */
export function localizeMetric(metricKey: string): string {
return translate('metric', metricKey, 'name');
}
/** Get period value of a measure */
-export function getPeriodValue(measure: Measure, periodIndex: number): string | undefined {
+export function getPeriodValue(
+ measure: Measure | MeasureEnhanced,
+ periodIndex: number
+): string | undefined {
const { periods } = measure;
const period = periods && periods.find(period => period.index === periodIndex);
return period ? period.value : undefined;
return FORMATTERS[type] || noFormatter;
}
-function getVariationFormatter(type: string): Formatter {
- const FORMATTERS: { [type: string]: Formatter } = {
- INT: intVariationFormatter,
- SHORT_INT: shortIntVariationFormatter,
- FLOAT: floatVariationFormatter,
- PERCENT: percentVariationFormatter,
- WORK_DUR: durationVariationFormatter,
- SHORT_WORK_DUR: shortDurationVariationFormatter,
- RATING: emptyFormatter,
- LEVEL: emptyFormatter,
- MILLISEC: millisecondsVariationFormatter
- };
- return FORMATTERS[type] || noFormatter;
-}
-
function numberFormatter(
value: number,
minimumFractionDigits = 0,
return value;
}
-function emptyFormatter(): string {
- return '';
-}
-
function intFormatter(value: number): string {
return numberFormatter(value);
}
-function intVariationFormatter(value: number): string {
- const prefix = value < 0 ? '-' : '+';
- return prefix + intFormatter(Math.abs(value));
-}
-
function shortIntFormatter(value: number): string {
if (value >= 1e9) {
return numberFormatter(value / 1e9) + translate('short_number_suffix.g');
}
}
-function shortIntVariationFormatter(value: number): string {
- const formatted = shortIntFormatter(Math.abs(value));
- return value < 0 ? `-${formatted}` : `+${formatted}`;
-}
-
function floatFormatter(value: number): string {
return numberFormatter(value, 1, 5);
}
-function floatVariationFormatter(value: number): string {
- const prefix = value < 0 ? '-' : '+';
- return prefix + floatFormatter(Math.abs(value));
-}
-
function percentFormatter(value: string | number, options: { decimals?: number } = {}): string {
if (typeof value === 'string') {
value = parseFloat(value);
return value === 100 ? '100%' : numberFormatter(value, 1) + '%';
}
-function percentVariationFormatter(
- value: string | number,
- options: { decimals?: number } = {}
-): string {
- if (typeof value === 'string') {
- value = parseFloat(value);
- }
- const prefix = value < 0 ? '-' : '+';
- return prefix + percentFormatter(Math.abs(value), options);
-}
-
function ratingFormatter(value: string | number): string {
if (typeof value === 'string') {
value = parseInt(value, 10);
}
}
-function millisecondsVariationFormatter(value: number): string {
- const absValue = Math.abs(value);
- const formattedValue = millisecondsFormatter(absValue);
- return value < 0 ? `-${formattedValue}` : `+${formattedValue}`;
-}
-
/*
* Debt Formatters
*/
return formatDurationShort(isNegative, days, hours, remainingValue);
}
-function durationVariationFormatter(value: string | number): string {
- if (value === 0 || value === '0') {
- return '+0';
- }
- const formatted = durationFormatter(value);
- return formatted[0] !== '-' ? '+' + formatted : formatted;
-}
-
-function shortDurationVariationFormatter(value: string | number): string {
- if (value === 0 || value === '0') {
- return '+0';
- }
- const formatted = shortDurationFormatter(value);
- return formatted[0] !== '-' ? '+' + formatted : formatted;
-}
-
function getRatingGrid(): string {
// workaround cyclic dependencies
const getStore = require('../app/utils/getStore').default;
);
}
-export function getRatingTooltip(metricKey: string, value: number): string {
+export function getRatingTooltip(metricKey: string, value: number | string): string {
const ratingLetter = formatMeasure(value, 'RATING');
- const finalMetricKey = metricKey.startsWith('new_') ? metricKey.substr(4) : metricKey;
+ const finalMetricKey = isDiffMetric(metricKey) ? metricKey.substr(4) : metricKey;
return finalMetricKey === 'sqale_rating' || finalMetricKey === 'maintainability_rating'
- ? getMaintainabilityRatingTooltip(value)
+ ? getMaintainabilityRatingTooltip(Number(value))
: translate('metric', finalMetricKey, 'tooltip', ratingLetter);
}
return translateWithParameters(`overview.period.${period.mode}`, parameter);
}
-export function getPeriodDate(period: Period | undefined): Date | undefined {
- return period ? parseDate(period.date) : undefined;
+export function getPeriodDate(period?: { date?: string }): Date | undefined {
+ return period && period.date ? parseDate(period.date) : undefined;
}
export function getLeakPeriodLabel(periods: Period[]): string | undefined {