aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-02-09 16:11:11 +0100
committerGitHub <noreply@github.com>2018-02-09 16:11:11 +0100
commit8f8e96192f36aa13496ebfe7bbaf333de8a92e35 (patch)
tree8a233650211ec26edabf027c49cf0ff5cc3878e7 /server
parent2e06a921076f73f901b333eb7fa236f7971ff3ac (diff)
downloadsonarqube-8f8e96192f36aa13496ebfe7bbaf333de8a92e35.tar.gz
sonarqube-8f8e96192f36aa13496ebfe7bbaf333de8a92e35.zip
rewrite custom metrics app in react (#3036)
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/metrics.ts41
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/forms.css2
-rw-r--r--server/sonar-web/src/main/js/app/types.ts1
-rw-r--r--server/sonar-web/src/main/js/app/utils/startReactApp.js4
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/form-view.js2
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/metric.js (renamed from server/sonar-web/src/main/js/apps/metrics/metric.js)0
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/metrics.js (renamed from server/sonar-web/src/main/js/apps/metrics/metrics.js)0
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx174
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/DeleteButton.tsx47
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/EditButton.tsx83
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx188
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/Header.tsx44
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/List.tsx90
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx80
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/CreateButton-test.tsx (renamed from server/sonar-web/src/main/js/apps/metrics/list-footer-view.js)42
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/DeleteButton-test.tsx (renamed from server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx)26
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/EditButton-test.tsx (renamed from server/sonar-web/src/main/js/apps/metrics/header-view.js)45
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx59
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Header-test.tsx (renamed from server/sonar-web/src/main/js/apps/metrics/list-view.js)27
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/List-test.tsx (renamed from server/sonar-web/src/main/js/apps/metrics/layout.js)23
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/App-test.tsx.snap130
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap41
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/EditButton-test.tsx.snap49
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap166
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Header-test.tsx.snap42
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/List-test.tsx.snap153
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/routes.ts (renamed from server/sonar-web/src/main/js/apps/metrics/routes.ts)4
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/create-view.js53
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/delete-view.js50
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/form-view.js73
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/init.js75
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/list-item-view.js66
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/templates/metrics-delete.hbs13
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/templates/metrics-form.hbs36
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/templates/metrics-header.hbs7
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/templates/metrics-layout.hbs5
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-footer.hbs6
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-item.hbs35
-rw-r--r--server/sonar-web/src/main/js/apps/metrics/update-view.js49
-rw-r--r--server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx4
43 files changed, 1560 insertions, 568 deletions
diff --git a/server/sonar-web/src/main/js/api/metrics.ts b/server/sonar-web/src/main/js/api/metrics.ts
index c84d8890aac..25cbbf710bc 100644
--- a/server/sonar-web/src/main/js/api/metrics.ts
+++ b/server/sonar-web/src/main/js/api/metrics.ts
@@ -17,22 +17,30 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { getJSON } from '../helpers/request';
+import { getJSON, post, postJSON } from '../helpers/request';
import { Metric } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
-interface MetricsResponse {
+export interface MetricsResponse {
metrics: Metric[];
p: number;
ps: number;
total: number;
}
-export function getMetrics(data: { p?: number; ps?: number }): Promise<MetricsResponse> {
+export function getMetrics(data?: {
+ isCustom?: boolean;
+ p?: number;
+ ps?: number;
+}): Promise<MetricsResponse> {
return getJSON('/api/metrics/search', data).catch(throwGlobalError);
}
-export function getAllMetrics(data?: { p?: number; ps?: number }): Promise<Metric[]> {
+export function getAllMetrics(data?: {
+ isCustom?: boolean;
+ p?: number;
+ ps?: number;
+}): Promise<Metric[]> {
return inner(data);
function inner(
@@ -56,3 +64,28 @@ export function getMetricDomains(): Promise<string[]> {
export function getMetricTypes(): Promise<string[]> {
return getJSON('/api/metrics/types').then(r => r.types, throwGlobalError);
}
+
+export function createMetric(data: {
+ description?: string;
+ domain?: string;
+ key: string;
+ name: string;
+ type: string;
+}): Promise<Metric> {
+ return postJSON('/api/metrics/create', data).catch(throwGlobalError);
+}
+
+export function updateMetric(data: {
+ description?: string;
+ domain?: string;
+ id: string;
+ key?: string;
+ name?: string;
+ type?: string;
+}) {
+ return post('/api/metrics/update', data).catch(throwGlobalError);
+}
+
+export function deleteMetric(data: { keys: string }) {
+ return post('/api/metrics/delete', data).catch(throwGlobalError);
+}
diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css
index beb0204de27..b7c3cec13a7 100644
--- a/server/sonar-web/src/main/js/app/styles/init/forms.css
+++ b/server/sonar-web/src/main/js/app/styles/init/forms.css
@@ -292,7 +292,7 @@ input[type='submit'].button-grey.button-active {
.button-link:disabled:active,
.button-link:disabled:focus {
color: var(--secondFontColor);
- background: transparent;
+ background: transparent !important;
cursor: default;
}
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 9d380964a46..1eb6b2b5350 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -117,6 +117,7 @@ export interface Metric {
direction?: number;
domain?: string;
hidden?: boolean;
+ id: string;
key: string;
name: string;
qualitative?: boolean;
diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js
index f3a96e04522..c6af220da94 100644
--- a/server/sonar-web/src/main/js/app/utils/startReactApp.js
+++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js
@@ -55,7 +55,7 @@ import ExploreIssues from '../../apps/explore/ExploreIssues';
import ExploreProjects from '../../apps/explore/ExploreProjects';
import IssuesPageSelector from '../../apps/issues/IssuesPageSelector';
import marketplaceRoutes from '../../apps/marketplace/routes';
-import metricsRoutes from '../../apps/metrics/routes';
+import customMetricsRoutes from '../../apps/custom-metrics/routes';
import overviewRoutes from '../../apps/overview/routes';
import organizationsRoutes from '../../apps/organizations/routes';
import permissionTemplatesRoutes from '../../apps/permission-templates/routes';
@@ -222,7 +222,7 @@ const startReactApp = () => {
component={GlobalAdminPageExtension}
/>
<Route path="background_tasks" childRoutes={backgroundTasksRoutes} />
- <Route path="custom_metrics" childRoutes={metricsRoutes} />
+ <Route path="custom_metrics" childRoutes={customMetricsRoutes} />
<Route path="groups" childRoutes={groupsRoutes} />
<Route path="permission_templates" childRoutes={permissionTemplatesRoutes} />
<Route path="roles/global" childRoutes={globalPermissionsRoutes} />
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/form-view.js b/server/sonar-web/src/main/js/apps/custom-measures/form-view.js
index aa8ff0a64df..35e2e05ca7f 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/form-view.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/form-view.js
@@ -17,9 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import Metrics from './metrics';
import Template from './templates/custom-measures-form.hbs';
import ModalForm from '../../components/common/modal-form';
-import Metrics from '../metrics/metrics';
export default ModalForm.extend({
template: Template,
diff --git a/server/sonar-web/src/main/js/apps/metrics/metric.js b/server/sonar-web/src/main/js/apps/custom-measures/metric.js
index 29fdb20beb2..29fdb20beb2 100644
--- a/server/sonar-web/src/main/js/apps/metrics/metric.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/metric.js
diff --git a/server/sonar-web/src/main/js/apps/metrics/metrics.js b/server/sonar-web/src/main/js/apps/custom-measures/metrics.js
index 0fb8c5e00c7..0fb8c5e00c7 100644
--- a/server/sonar-web/src/main/js/apps/metrics/metrics.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/metrics.js
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx
new file mode 100644
index 00000000000..09dffecab93
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx
@@ -0,0 +1,174 @@
+/*
+ * 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 { Helmet } from 'react-helmet';
+import { MetricProps } from './Form';
+import Header from './Header';
+import List from './List';
+import {
+ getMetricDomains,
+ getMetricTypes,
+ getMetrics,
+ deleteMetric,
+ updateMetric,
+ createMetric,
+ MetricsResponse
+} from '../../../api/metrics';
+import { Metric, Paging } from '../../../app/types';
+import ListFooter from '../../../components/controls/ListFooter';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {}
+
+interface State {
+ domains?: string[];
+ loading: boolean;
+ metrics?: Metric[];
+ paging?: Paging;
+ types?: string[];
+}
+
+const PAGE_SIZE = 500;
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchData();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchData = () => {
+ Promise.all([
+ getMetricDomains(),
+ getMetricTypes(),
+ getMetrics({ isCustom: true, ps: PAGE_SIZE })
+ ]).then(([domains, types, metricsResponse]) => {
+ if (this.mounted) {
+ this.setState({
+ domains,
+ loading: false,
+ metrics: metricsResponse.metrics,
+ paging: this.getPaging(metricsResponse),
+ types
+ });
+ }
+ }, this.stopLoading);
+ };
+
+ fetchMore = () => {
+ const { paging } = this.state;
+ if (paging) {
+ this.setState({ loading: true });
+ getMetrics({ isCustom: true, p: paging.pageIndex + 1, ps: PAGE_SIZE }).then(
+ metricsResponse => {
+ if (this.mounted) {
+ this.setState(({ metrics = [] }: State) => ({
+ loading: false,
+ metrics: [...metrics, ...metricsResponse.metrics],
+ paging: this.getPaging(metricsResponse)
+ }));
+ }
+ },
+ this.stopLoading
+ );
+ }
+ };
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ getPaging = (response: MetricsResponse): Paging => ({
+ pageIndex: response.p,
+ pageSize: response.ps,
+ total: response.total
+ });
+
+ handleCreate = (data: MetricProps) => {
+ return createMetric(data).then(metric => {
+ if (this.mounted) {
+ this.setState(({ metrics = [], paging }: State) => ({
+ metrics: [...metrics, metric],
+ paging: paging && { ...paging, total: paging.total + 1 }
+ }));
+ }
+ });
+ };
+
+ handleEdit = (data: { id: string } & MetricProps) => {
+ return updateMetric(data).then(() => {
+ if (this.mounted) {
+ this.setState(({ metrics = [] }: State) => ({
+ metrics: metrics.map(metric => (metric.id === data.id ? { ...metric, ...data } : metric))
+ }));
+ }
+ });
+ };
+
+ handleDelete = (metricKey: string) => {
+ return deleteMetric({ keys: metricKey }).then(() => {
+ if (this.mounted) {
+ this.setState(({ metrics = [], paging }: State) => ({
+ metrics: metrics.filter(metric => metric.key !== metricKey),
+ paging: paging && { ...paging, total: paging.total - 1 }
+ }));
+ }
+ });
+ };
+
+ render() {
+ const { domains, loading, metrics, paging, types } = this.state;
+
+ return (
+ <>
+ <Helmet title={translate('custom_metrics.page')} />
+ <div className="page page-limited" id="custom-metrics-page">
+ <Header domains={domains} loading={loading} onCreate={this.handleCreate} types={types} />
+ {metrics && (
+ <List
+ domains={domains}
+ metrics={metrics}
+ onDelete={this.handleDelete}
+ onEdit={this.handleEdit}
+ types={types}
+ />
+ )}
+ {metrics &&
+ paging && (
+ <ListFooter
+ count={metrics.length}
+ loadMore={this.fetchMore}
+ ready={!loading}
+ total={paging.total}
+ />
+ )}
+ </div>
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx
new file mode 100644
index 00000000000..fffca99bfda
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/CreateButton.tsx
@@ -0,0 +1,75 @@
+/*
+ * 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 Form, { MetricProps } from './Form';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ domains: string[];
+ onCreate: (data: MetricProps) => Promise<void>;
+ types: string[];
+}
+
+interface State {
+ modal: boolean;
+}
+
+export default class CreateButton extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { modal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleClick = () => {
+ this.setState({ modal: true });
+ };
+
+ handleClose = () => {
+ if (this.mounted) {
+ this.setState({ modal: false });
+ }
+ };
+
+ render() {
+ return (
+ <>
+ <button id="metrics-create" onClick={this.handleClick}>
+ {translate('custom_metrics.create_metric')}
+ </button>
+ {this.state.modal && (
+ <Form
+ confirmButtonText={translate('create')}
+ domains={this.props.domains}
+ header={translate('custom_metrics.create_metric')}
+ onClose={this.handleClose}
+ onSubmit={this.props.onCreate}
+ types={this.props.types}
+ />
+ )}
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/DeleteButton.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/DeleteButton.tsx
new file mode 100644
index 00000000000..d825823c7f3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/DeleteButton.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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 { Metric } from '../../../app/types';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+ metric: Metric;
+ onDelete: (metricKey: string) => Promise<void>;
+}
+
+export default function DeleteButton({ metric, onDelete }: Props) {
+ return (
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ confirmData={metric.key}
+ isDestructive={true}
+ modalBody={translateWithParameters('custom_metrics.delete_metric.confirmation', metric.name)}
+ modalHeader={translate('custom_metrics.delete_metric')}
+ onConfirm={onDelete}>
+ {({ onClick }) => (
+ <ActionsDropdownItem className="js-metric-delete" destructive={true} onClick={onClick}>
+ {translate('delete')}
+ </ActionsDropdownItem>
+ )}
+ </ConfirmButton>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/EditButton.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/EditButton.tsx
new file mode 100644
index 00000000000..6257f2ad92d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/EditButton.tsx
@@ -0,0 +1,83 @@
+/*
+ * 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 Form, { MetricProps } from './Form';
+import { Metric } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
+
+interface Props {
+ domains: string[];
+ metric: Metric;
+ onEdit: (data: { id: string } & MetricProps) => Promise<void>;
+ types: string[];
+}
+
+interface State {
+ modal: boolean;
+}
+
+export default class EditButton extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { modal: false };
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleClick = () => {
+ this.setState({ modal: true });
+ };
+
+ handleClose = () => {
+ if (this.mounted) {
+ this.setState({ modal: false });
+ }
+ };
+
+ handleSubmit = (data: MetricProps) => {
+ return this.props.onEdit({ id: this.props.metric.id, ...data });
+ };
+
+ render() {
+ return (
+ <>
+ <ActionsDropdownItem className="js-metric-update" onClick={this.handleClick}>
+ {translate('update_details')}
+ </ActionsDropdownItem>
+ {this.state.modal && (
+ <Form
+ confirmButtonText={translate('update_verb')}
+ domains={this.props.domains}
+ header={translate('custom_metrics.update_metric')}
+ metric={this.props.metric}
+ onClose={this.handleClose}
+ onSubmit={this.handleSubmit}
+ types={this.props.types}
+ />
+ )}
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx
new file mode 100644
index 00000000000..0922d9a9b40
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/Form.tsx
@@ -0,0 +1,188 @@
+/*
+ * 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 { Metric } from '../../../app/types';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import SimpleModal from '../../../components/controls/SimpleModal';
+import { translate } from '../../../helpers/l10n';
+import Select, { Creatable } from '../../../components/controls/Select';
+
+export interface MetricProps {
+ description: string;
+ domain?: string;
+ key: string;
+ name: string;
+ type: string;
+}
+
+interface Props {
+ confirmButtonText: string;
+ domains: string[];
+ metric?: Metric;
+ header: string;
+ onClose: () => void;
+ onSubmit: (data: MetricProps) => Promise<void>;
+ types: string[];
+}
+
+interface State extends MetricProps {}
+
+export default class Form extends React.PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ description: (props.metric && props.metric.description) || '',
+ domain: props.metric && props.metric.domain,
+ key: (props.metric && props.metric.key) || '',
+ name: (props.metric && props.metric.name) || '',
+ type: (props.metric && props.metric.type) || 'INT'
+ };
+ }
+
+ handleSubmit = () => {
+ return this.props
+ .onSubmit({
+ description: this.state.description,
+ domain: this.state.domain,
+ key: this.state.key,
+ name: this.state.name,
+ type: this.state.type
+ })
+ .then(this.props.onClose);
+ };
+
+ handleKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this.setState({ key: event.currentTarget.value });
+ };
+
+ handleDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
+ this.setState({ description: event.currentTarget.value });
+ };
+
+ handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this.setState({ name: event.currentTarget.value });
+ };
+
+ handleDomainChange = (option: { value: string } | null) => {
+ this.setState({ domain: option ? option.value : undefined });
+ };
+
+ handleTypeChange = ({ value }: { value: string }) => {
+ this.setState({ type: value });
+ };
+
+ render() {
+ return (
+ <SimpleModal
+ header={this.props.header}
+ onClose={this.props.onClose}
+ onSubmit={this.handleSubmit}>
+ {({ onCloseClick, onFormSubmit, submitting }) => (
+ <form onSubmit={onFormSubmit}>
+ <header className="modal-head">
+ <h2>{this.props.header}</h2>
+ </header>
+
+ <div className="modal-body">
+ <div className="modal-field">
+ <label htmlFor="create-metric-key">
+ {translate('key')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-metric-key"
+ maxLength={64}
+ name="key"
+ onChange={this.handleKeyChange}
+ required={true}
+ type="text"
+ value={this.state.key}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-metric-name">
+ {translate('name')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ id="create-metric-name"
+ maxLength={64}
+ name="name"
+ onChange={this.handleNameChange}
+ required={true}
+ type="text"
+ value={this.state.name}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-metric-description">{translate('description')}</label>
+ <textarea
+ id="create-metric-description"
+ name="description"
+ onChange={this.handleDescriptionChange}
+ value={this.state.description}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-metric-domain">{translate('custom_metrics.domain')}</label>
+ <Creatable
+ onChange={this.handleDomainChange}
+ options={this.props.domains.map(domain => ({ label: domain, value: domain }))}
+ value={this.state.domain}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-metric-type">
+ {translate('type')}
+ <em className="mandatory">*</em>
+ </label>
+ <Select
+ clearable={false}
+ onChange={this.handleTypeChange}
+ options={this.props.types.map(type => ({
+ label: translate('metric.type', type),
+ value: type
+ }))}
+ value={this.state.type}
+ />
+ </div>
+ </div>
+
+ <footer className="modal-foot">
+ <DeferredSpinner className="spacer-right" loading={submitting} />
+ <button disabled={submitting} id="create-metric-submit" type="submit">
+ {this.props.confirmButtonText}
+ </button>
+ <button
+ className="button-link"
+ disabled={submitting}
+ id="create-metric-cancel"
+ onClick={onCloseClick}
+ type="reset">
+ {translate('cancel')}
+ </button>
+ </footer>
+ </form>
+ )}
+ </SimpleModal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/Header.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/Header.tsx
new file mode 100644
index 00000000000..71004907a97
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/Header.tsx
@@ -0,0 +1,44 @@
+/*
+ * 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 CreateButton from './CreateButton';
+import { MetricProps } from './Form';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ domains: string[] | undefined;
+ loading: boolean;
+ onCreate: (data: MetricProps) => Promise<void>;
+ types: string[] | undefined;
+}
+
+export default function Header({ domains, loading, onCreate, types }: Props) {
+ return (
+ <header className="page-header" id="custom-metrics-header">
+ <h1 className="page-title">{translate('custom_metrics.page')}</h1>
+ <DeferredSpinner loading={loading} />
+ <div className="page-actions">
+ {domains && types && <CreateButton domains={domains} onCreate={onCreate} types={types} />}
+ </div>
+ <p className="page-description">{translate('custom_metrics.page.description')}</p>
+ </header>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/List.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/List.tsx
new file mode 100644
index 00000000000..a5449bf68b6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/List.tsx
@@ -0,0 +1,90 @@
+/*
+ * 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 DeleteButton from './DeleteButton';
+import EditButton from './EditButton';
+import { MetricProps } from './Form';
+import { Metric } from '../../../app/types';
+import ActionsDropdown, {
+ ActionsDropdownDivider
+} from '../../../components/controls/ActionsDropdown';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ domains?: string[];
+ metrics: Metric[];
+ onDelete: (metricKey: string) => Promise<void>;
+ onEdit: (data: { id: string } & MetricProps) => Promise<void>;
+ types?: string[];
+}
+
+export default function List({ domains, metrics, onDelete, onEdit, types }: Props) {
+ return (
+ <div className="boxed-group boxed-group-inner" id="custom-metrics-list">
+ {metrics.length > 0 ? (
+ <table className="data zebra zebra-hover">
+ <tbody>
+ {sortBy(metrics, metric => metric.name.toLowerCase()).map(metric => (
+ <tr data-metric={metric.key} key={metric.key}>
+ <td className="width-30">
+ <div>
+ <strong className="js-metric-name">{metric.name}</strong>
+ <span className="js-metric-key note little-spacer-left">{metric.key}</span>
+ </div>
+ </td>
+
+ <td className="width-20">
+ <span className="js-metric-domain">{metric.domain}</span>
+ </td>
+
+ <td className="width-20">
+ <span className="js-metric-type">{translate('metric.type', metric.type)}</span>
+ </td>
+
+ <td className="width-20" title={metric.description}>
+ <span className="js-metric-description">{metric.description}</span>
+ </td>
+
+ <td className="thin nowrap">
+ <ActionsDropdown>
+ {domains &&
+ types && (
+ <EditButton
+ domains={domains}
+ metric={metric}
+ onEdit={onEdit}
+ types={types}
+ />
+ )}
+ <ActionsDropdownDivider />
+ <DeleteButton metric={metric} onDelete={onDelete} />
+ </ActionsDropdown>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ ) : (
+ <p>{translate('no_results')}</p>
+ )}
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..d4b9e0772d5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/App-test.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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 App from '../App';
+
+jest.mock('../../../../api/metrics', () => ({
+ getMetricDomains: () => Promise.resolve(['Coverage', 'Issues']),
+ getMetricTypes: () => Promise.resolve(['INT', 'STRING']),
+ getMetrics: () =>
+ Promise.resolve({
+ metrics: [{ id: '3', key: 'foo', name: 'Foo', type: 'INT' }],
+ p: 1,
+ ps: 1,
+ total: 1
+ }),
+ deleteMetric: () => Promise.resolve(),
+ updateMetric: () => Promise.resolve(),
+ createMetric: () =>
+ Promise.resolve({ id: '4', domain: 'Coverage', key: 'bar', name: 'Bar', type: 'INT' })
+}));
+
+it('should work', async () => {
+ const wrapper = shallow(<App />);
+ (wrapper.instance() as App).mounted = true;
+ expect(wrapper).toMatchSnapshot();
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ // create
+ wrapper.find('Header').prop<Function>('onCreate')({
+ domain: 'Coverage',
+ key: 'bar',
+ name: 'Bar',
+ type: 'INT'
+ });
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state().metrics).toMatchSnapshot();
+ expect(wrapper.state().paging.total).toBe(2);
+
+ // edit
+ wrapper.find('List').prop<Function>('onEdit')({
+ domain: undefined,
+ id: '4',
+ key: 'bar',
+ name: 'Bar',
+ type: 'STRING'
+ });
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state().metrics).toMatchSnapshot();
+ expect(wrapper.state().paging.total).toBe(2);
+
+ // delete
+ wrapper.find('List').prop<Function>('onDelete')('bar');
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state().metrics).toMatchSnapshot();
+ expect(wrapper.state().paging.total).toBe(1);
+});
diff --git a/server/sonar-web/src/main/js/apps/metrics/list-footer-view.js b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/CreateButton-test.tsx
index a352a5923b5..85163c4dc7d 100644
--- a/server/sonar-web/src/main/js/apps/metrics/list-footer-view.js
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/CreateButton-test.tsx
@@ -17,35 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import Marionette from 'backbone.marionette';
-import Template from './templates/metrics-list-footer.hbs';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import CreateButton from '../CreateButton';
+import { click } from '../../../../helpers/testUtils';
-export default Marionette.ItemView.extend({
- template: Template,
+it('should create new group', () => {
+ const onCreate = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(
+ <CreateButton domains={['Coverage', 'Issues']} onCreate={onCreate} types={['INT', 'STRING']} />
+ );
+ expect(wrapper).toMatchSnapshot();
- collectionEvents: {
- all: 'render'
- },
+ click(wrapper.find('#metrics-create'));
+ expect(wrapper).toMatchSnapshot();
- events: {
- 'click #metrics-fetch-more': 'onMoreClick'
- },
-
- onMoreClick(e) {
- e.preventDefault();
- this.fetchMore();
- },
-
- fetchMore() {
- this.collection.fetchMore();
- },
-
- serializeData() {
- return {
- ...Marionette.ItemView.prototype.serializeData.apply(this, arguments),
- total: this.collection.total,
- count: this.collection.length,
- more: this.collection.hasMore()
- };
- }
+ wrapper.find('Form').prop<Function>('onSubmit')({ key: 'foo', name: 'foo', type: 'INT' });
+ expect(onCreate).toBeCalledWith({ key: 'foo', name: 'foo', type: 'INT' });
});
diff --git a/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/DeleteButton-test.tsx
index 377d77d21fe..a35e39a610e 100644
--- a/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/DeleteButton-test.tsx
@@ -18,21 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import Helmet from 'react-helmet';
-import init from '../init';
-import { translate } from '../../../helpers/l10n';
+import { shallow } from 'enzyme';
+import DeleteButton from '../DeleteButton';
-export default class MetricsAppContainer extends React.PureComponent {
- componentDidMount() {
- init(this.refs.container);
- }
+it('should delete metric', () => {
+ const metric = { id: '3', key: 'foo', name: 'Foo', type: 'INT' };
+ const onDelete = jest.fn();
+ const wrapper = shallow(<DeleteButton metric={metric} onDelete={onDelete} />);
+ expect(wrapper).toMatchSnapshot();
- render() {
- return (
- <div>
- <Helmet title={translate('custom_metrics.page')} />
- <div ref="container" />
- </div>
- );
- }
-}
+ wrapper.find('ConfirmButton').prop<Function>('onConfirm')('foo');
+ expect(onDelete).toBeCalledWith('foo');
+});
diff --git a/server/sonar-web/src/main/js/apps/metrics/header-view.js b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/EditButton-test.tsx
index df062bbfcab..5fce34493a4 100644
--- a/server/sonar-web/src/main/js/apps/metrics/header-view.js
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/EditButton-test.tsx
@@ -17,28 +17,33 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import Marionette from 'backbone.marionette';
-import CreateView from './create-view';
-import Template from './templates/metrics-header.hbs';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import EditButton from '../EditButton';
+import { click } from '../../../../helpers/testUtils';
-export default Marionette.ItemView.extend({
- template: Template,
+it('should edit metric', () => {
+ const metric = { id: '3', key: 'foo', name: 'Foo', type: 'INT' };
+ const onEdit = jest.fn();
- events: {
- 'click #metrics-create': 'onCreateClick'
- },
+ const wrapper = shallow(
+ <EditButton
+ domains={['Coverage', 'Issues']}
+ metric={metric}
+ onEdit={onEdit}
+ types={['INT', 'STRING']}
+ />
+ );
+ expect(wrapper).toMatchSnapshot();
- onCreateClick(e) {
- e.preventDefault();
- this.createMetric();
- },
+ click(wrapper.find('.js-metric-update'));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
- createMetric() {
- new CreateView({
- collection: this.collection,
- domains: this.options.domains,
- types: this.options.types,
- app: this.options.app
- }).render();
- }
+ wrapper.find('Form').prop<Function>('onSubmit')({
+ ...metric,
+ description: 'bla bla',
+ domain: 'Coverage'
+ });
+ expect(onEdit).toBeCalledWith({ ...metric, description: 'bla bla', domain: 'Coverage' });
});
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx
new file mode 100644
index 00000000000..a2b633095d9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Form-test.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 Form from '../Form';
+import { change, submit, click } from '../../../../helpers/testUtils';
+
+it('should render form', async () => {
+ const onClose = jest.fn();
+ const onSubmit = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(
+ <Form
+ confirmButtonText="confirmButtonText"
+ domains={['Coverage', 'Issues']}
+ header="header"
+ onClose={onClose}
+ onSubmit={onSubmit}
+ types={['INT', 'STRING']}
+ />
+ ).dive();
+ expect(wrapper).toMatchSnapshot();
+
+ change(wrapper.find('[name="key"]'), 'foo');
+ change(wrapper.find('[name="name"]'), 'Foo');
+ change(wrapper.find('[name="description"]'), 'bar');
+ wrapper.find('Creatable').prop<Function>('onChange')({ value: 'Coverage' });
+ submit(wrapper.find('form'));
+ expect(onSubmit).toBeCalledWith({
+ description: 'bar',
+ domain: 'Coverage',
+ key: 'foo',
+ name: 'Foo',
+ type: 'INT'
+ });
+
+ await new Promise(setImmediate);
+ expect(onClose).toBeCalled();
+
+ onClose.mockClear();
+ click(wrapper.find('button[type="reset"]'));
+ expect(onClose).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/metrics/list-view.js b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Header-test.tsx
index d9a2d7187af..48bcc5053a8 100644
--- a/server/sonar-web/src/main/js/apps/metrics/list-view.js
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/Header-test.tsx
@@ -17,17 +17,22 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import Marionette from 'backbone.marionette';
-import ListItemView from './list-item-view';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Header from '../Header';
-export default Marionette.CollectionView.extend({
- tagName: 'ul',
- childView: ListItemView,
+it('should create new metric', () => {
+ const onCreate = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(
+ <Header
+ domains={['Coverage', 'Issues']}
+ loading={false}
+ onCreate={onCreate}
+ types={['INT', 'STRING']}
+ />
+ );
+ expect(wrapper).toMatchSnapshot();
- childViewOptions() {
- return {
- types: this.options.types,
- domains: this.options.domains
- };
- }
+ wrapper.find('CreateButton').prop<Function>('onCreate')({ key: 'foo', name: 'Foo', type: 'INT' });
+ expect(onCreate).toBeCalledWith({ key: 'foo', name: 'Foo', type: 'INT' });
});
diff --git a/server/sonar-web/src/main/js/apps/metrics/layout.js b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/List-test.tsx
index 57763080bad..fc8f1c0372d 100644
--- a/server/sonar-web/src/main/js/apps/metrics/layout.js
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/List-test.tsx
@@ -17,15 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import Marionette from 'backbone.marionette';
-import Template from './templates/metrics-layout.hbs';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import List from '../List';
-export default Marionette.LayoutView.extend({
- template: Template,
+it('should render', () => {
+ const metrics = [
+ { id: '3', key: 'foo', name: 'Foo', type: 'INT' },
+ { id: '4', domain: 'Coverage', key: 'bar', name: 'Bar', type: 'INT' }
+ ];
+ expect(
+ shallow(<List metrics={metrics} onDelete={jest.fn()} onEdit={jest.fn()} />)
+ ).toMatchSnapshot();
+});
- regions: {
- headerRegion: '#metrics-header',
- listRegion: '#metrics-list',
- listFooterRegion: '#metrics-list-footer'
- }
+it('should render no results', () => {
+ expect(shallow(<List metrics={[]} onDelete={jest.fn()} onEdit={jest.fn()} />)).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..55f852e2689
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,130 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should work 1`] = `
+<React.Fragment>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="custom_metrics.page"
+ />
+ <div
+ className="page page-limited"
+ id="custom-metrics-page"
+ >
+ <Header
+ loading={true}
+ onCreate={[Function]}
+ />
+ </div>
+</React.Fragment>
+`;
+
+exports[`should work 2`] = `
+<React.Fragment>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="custom_metrics.page"
+ />
+ <div
+ className="page page-limited"
+ id="custom-metrics-page"
+ >
+ <Header
+ domains={
+ Array [
+ "Coverage",
+ "Issues",
+ ]
+ }
+ loading={false}
+ onCreate={[Function]}
+ types={
+ Array [
+ "INT",
+ "STRING",
+ ]
+ }
+ />
+ <List
+ domains={
+ Array [
+ "Coverage",
+ "Issues",
+ ]
+ }
+ metrics={
+ Array [
+ Object {
+ "id": "3",
+ "key": "foo",
+ "name": "Foo",
+ "type": "INT",
+ },
+ ]
+ }
+ onDelete={[Function]}
+ onEdit={[Function]}
+ types={
+ Array [
+ "INT",
+ "STRING",
+ ]
+ }
+ />
+ <ListFooter
+ count={1}
+ loadMore={[Function]}
+ ready={true}
+ total={1}
+ />
+ </div>
+</React.Fragment>
+`;
+
+exports[`should work 3`] = `
+Array [
+ Object {
+ "id": "3",
+ "key": "foo",
+ "name": "Foo",
+ "type": "INT",
+ },
+ Object {
+ "domain": "Coverage",
+ "id": "4",
+ "key": "bar",
+ "name": "Bar",
+ "type": "INT",
+ },
+]
+`;
+
+exports[`should work 4`] = `
+Array [
+ Object {
+ "id": "3",
+ "key": "foo",
+ "name": "Foo",
+ "type": "INT",
+ },
+ Object {
+ "domain": undefined,
+ "id": "4",
+ "key": "bar",
+ "name": "Bar",
+ "type": "STRING",
+ },
+]
+`;
+
+exports[`should work 5`] = `
+Array [
+ Object {
+ "id": "3",
+ "key": "foo",
+ "name": "Foo",
+ "type": "INT",
+ },
+]
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap
new file mode 100644
index 00000000000..fcc3412a7a6
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/CreateButton-test.tsx.snap
@@ -0,0 +1,41 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should create new group 1`] = `
+<React.Fragment>
+ <button
+ id="metrics-create"
+ onClick={[Function]}
+ >
+ custom_metrics.create_metric
+ </button>
+</React.Fragment>
+`;
+
+exports[`should create new group 2`] = `
+<React.Fragment>
+ <button
+ id="metrics-create"
+ onClick={[Function]}
+ >
+ custom_metrics.create_metric
+ </button>
+ <Form
+ confirmButtonText="create"
+ domains={
+ Array [
+ "Coverage",
+ "Issues",
+ ]
+ }
+ header="custom_metrics.create_metric"
+ onClose={[Function]}
+ onSubmit={[MockFunction]}
+ types={
+ Array [
+ "INT",
+ "STRING",
+ ]
+ }
+ />
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap
new file mode 100644
index 00000000000..bd6e9cd420b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should delete metric 1`] = `
+<ConfirmButton
+ confirmButtonText="delete"
+ confirmData="foo"
+ isDestructive={true}
+ modalBody="custom_metrics.delete_metric.confirmation.Foo"
+ modalHeader="custom_metrics.delete_metric"
+ onConfirm={[MockFunction]}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/EditButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/EditButton-test.tsx.snap
new file mode 100644
index 00000000000..66d078530d4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/EditButton-test.tsx.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should edit metric 1`] = `
+<React.Fragment>
+ <ActionsDropdownItem
+ className="js-metric-update"
+ onClick={[Function]}
+ >
+ update_details
+ </ActionsDropdownItem>
+</React.Fragment>
+`;
+
+exports[`should edit metric 2`] = `
+<React.Fragment>
+ <ActionsDropdownItem
+ className="js-metric-update"
+ onClick={[Function]}
+ >
+ update_details
+ </ActionsDropdownItem>
+ <Form
+ confirmButtonText="update_verb"
+ domains={
+ Array [
+ "Coverage",
+ "Issues",
+ ]
+ }
+ header="custom_metrics.update_metric"
+ metric={
+ Object {
+ "id": "3",
+ "key": "foo",
+ "name": "Foo",
+ "type": "INT",
+ }
+ }
+ onClose={[Function]}
+ onSubmit={[Function]}
+ types={
+ Array [
+ "INT",
+ "STRING",
+ ]
+ }
+ />
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap
new file mode 100644
index 00000000000..0eea472611e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Form-test.tsx.snap
@@ -0,0 +1,166 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render form 1`] = `
+<Modal
+ contentLabel="header"
+ onRequestClose={[MockFunction]}
+>
+ <form
+ onSubmit={[Function]}
+ >
+ <header
+ className="modal-head"
+ >
+ <h2>
+ header
+ </h2>
+ </header>
+ <div
+ className="modal-body"
+ >
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-metric-key"
+ >
+ key
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={true}
+ id="create-metric-key"
+ maxLength={64}
+ name="key"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-metric-name"
+ >
+ name
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ id="create-metric-name"
+ maxLength={64}
+ name="name"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-metric-description"
+ >
+ description
+ </label>
+ <textarea
+ id="create-metric-description"
+ name="description"
+ onChange={[Function]}
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-metric-domain"
+ >
+ custom_metrics.domain
+ </label>
+ <Creatable
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Coverage",
+ "value": "Coverage",
+ },
+ Object {
+ "label": "Issues",
+ "value": "Issues",
+ },
+ ]
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-metric-type"
+ >
+ type
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ clearable={false}
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "metric.type.INT",
+ "value": "INT",
+ },
+ Object {
+ "label": "metric.type.STRING",
+ "value": "STRING",
+ },
+ ]
+ }
+ value="INT"
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <DeferredSpinner
+ className="spacer-right"
+ loading={false}
+ timeout={100}
+ />
+ <button
+ disabled={false}
+ id="create-metric-submit"
+ type="submit"
+ >
+ confirmButtonText
+ </button>
+ <button
+ className="button-link"
+ disabled={false}
+ id="create-metric-cancel"
+ onClick={[Function]}
+ type="reset"
+ >
+ cancel
+ </button>
+ </footer>
+ </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Header-test.tsx.snap
new file mode 100644
index 00000000000..3b39b69bbad
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/Header-test.tsx.snap
@@ -0,0 +1,42 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should create new metric 1`] = `
+<header
+ className="page-header"
+ id="custom-metrics-header"
+>
+ <h1
+ className="page-title"
+ >
+ custom_metrics.page
+ </h1>
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ />
+ <div
+ className="page-actions"
+ >
+ <CreateButton
+ domains={
+ Array [
+ "Coverage",
+ "Issues",
+ ]
+ }
+ onCreate={[MockFunction]}
+ types={
+ Array [
+ "INT",
+ "STRING",
+ ]
+ }
+ />
+ </div>
+ <p
+ className="page-description"
+ >
+ custom_metrics.page.description
+ </p>
+</header>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/List-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/List-test.tsx.snap
new file mode 100644
index 00000000000..1fb174588c7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/components/__tests__/__snapshots__/List-test.tsx.snap
@@ -0,0 +1,153 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="boxed-group boxed-group-inner"
+ id="custom-metrics-list"
+>
+ <table
+ className="data zebra zebra-hover"
+ >
+ <tbody>
+ <tr
+ data-metric="bar"
+ key="bar"
+ >
+ <td
+ className="width-30"
+ >
+ <div>
+ <strong
+ className="js-metric-name"
+ >
+ Bar
+ </strong>
+ <span
+ className="js-metric-key note little-spacer-left"
+ >
+ bar
+ </span>
+ </div>
+ </td>
+ <td
+ className="width-20"
+ >
+ <span
+ className="js-metric-domain"
+ >
+ Coverage
+ </span>
+ </td>
+ <td
+ className="width-20"
+ >
+ <span
+ className="js-metric-type"
+ >
+ metric.type.INT
+ </span>
+ </td>
+ <td
+ className="width-20"
+ >
+ <span
+ className="js-metric-description"
+ />
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <ActionsDropdown>
+ <ActionsDropdownDivider />
+ <DeleteButton
+ metric={
+ Object {
+ "domain": "Coverage",
+ "id": "4",
+ "key": "bar",
+ "name": "Bar",
+ "type": "INT",
+ }
+ }
+ onDelete={[MockFunction]}
+ />
+ </ActionsDropdown>
+ </td>
+ </tr>
+ <tr
+ data-metric="foo"
+ key="foo"
+ >
+ <td
+ className="width-30"
+ >
+ <div>
+ <strong
+ className="js-metric-name"
+ >
+ Foo
+ </strong>
+ <span
+ className="js-metric-key note little-spacer-left"
+ >
+ foo
+ </span>
+ </div>
+ </td>
+ <td
+ className="width-20"
+ >
+ <span
+ className="js-metric-domain"
+ />
+ </td>
+ <td
+ className="width-20"
+ >
+ <span
+ className="js-metric-type"
+ >
+ metric.type.INT
+ </span>
+ </td>
+ <td
+ className="width-20"
+ >
+ <span
+ className="js-metric-description"
+ />
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <ActionsDropdown>
+ <ActionsDropdownDivider />
+ <DeleteButton
+ metric={
+ Object {
+ "id": "3",
+ "key": "foo",
+ "name": "Foo",
+ "type": "INT",
+ }
+ }
+ onDelete={[MockFunction]}
+ />
+ </ActionsDropdown>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
+
+exports[`should render no results 1`] = `
+<div
+ className="boxed-group boxed-group-inner"
+ id="custom-metrics-list"
+>
+ <p>
+ no_results
+ </p>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/metrics/routes.ts b/server/sonar-web/src/main/js/apps/custom-metrics/routes.ts
index 34e3b76be95..9397beea9d1 100644
--- a/server/sonar-web/src/main/js/apps/metrics/routes.ts
+++ b/server/sonar-web/src/main/js/apps/custom-metrics/routes.ts
@@ -22,9 +22,7 @@ import { RouterState, IndexRouteProps } from 'react-router';
const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
- import('./components/MetricsAppContainer').then(i =>
- callback(null, { component: i.default })
- );
+ import('./components/App').then(i => callback(null, { component: i.default }));
}
}
];
diff --git a/server/sonar-web/src/main/js/apps/metrics/create-view.js b/server/sonar-web/src/main/js/apps/metrics/create-view.js
deleted file mode 100644
index 3f208d30278..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/create-view.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Metric from './metric';
-import FormView from './form-view';
-
-export default FormView.extend({
- sendRequest() {
- const that = this;
- const metric = new Metric({
- key: this.$('#create-metric-key').val(),
- name: this.$('#create-metric-name').val(),
- description: this.$('#create-metric-description').val(),
- domain: this.$('#create-metric-domain').val(),
- type: this.$('#create-metric-type').val()
- });
- this.disableForm();
- return metric
- .save(null, {
- statusCode: {
- // do not show global error
- 400: null
- }
- })
- .done(() => {
- that.collection.refresh();
- if (that.options.domains.indexOf(metric.get('domain')) === -1) {
- that.options.domains.push(metric.get('domain'));
- }
- that.destroy();
- })
- .fail(jqXHR => {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- that.enableForm();
- });
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/metrics/delete-view.js b/server/sonar-web/src/main/js/apps/metrics/delete-view.js
deleted file mode 100644
index 5839bb00b96..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/delete-view.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Template from './templates/metrics-delete.hbs';
-import ModalForm from '../../components/common/modal-form';
-
-export default ModalForm.extend({
- template: Template,
-
- onFormSubmit() {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- },
-
- sendRequest() {
- const that = this;
- const collection = this.model.collection;
- return this.model
- .destroy({
- wait: true,
- statusCode: {
- // do not show global error
- 400: null
- }
- })
- .done(() => {
- collection.refresh();
- that.destroy();
- })
- .fail(jqXHR => {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/metrics/form-view.js b/server/sonar-web/src/main/js/apps/metrics/form-view.js
deleted file mode 100644
index 4cc287bec76..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/form-view.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import Template from './templates/metrics-form.hbs';
-import ModalForm from '../../components/common/modal-form';
-
-export default ModalForm.extend({
- template: Template,
-
- onRender() {
- const that = this;
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- this.$('#create-metric-domain')
- .select2({
- width: '250px',
- createSearchChoice(term) {
- return { id: term, text: '+' + term };
- },
- createSearchChoicePosition: 'top',
- initSelection(element, callback) {
- const value = $(element).val();
- callback({ id: value, text: value });
- },
- query(options) {
- const items = that.options.domains.filter(
- d => d.toLowerCase().indexOf(options.term.toLowerCase()) !== -1
- );
- const results = items.map(item => {
- return { id: item, text: item };
- });
- options.callback({ results, more: false });
- }
- })
- .select2('val', this.model && this.model.get('domain'));
- this.$('#create-metric-type').select2({ width: '250px' });
- },
-
- onDestroy() {
- ModalForm.prototype.onDestroy.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onFormSubmit() {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- },
-
- serializeData() {
- return {
- ...ModalForm.prototype.serializeData.apply(this, arguments),
- domains: this.options.domains,
- types: this.options.types
- };
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/metrics/init.js b/server/sonar-web/src/main/js/apps/metrics/init.js
deleted file mode 100644
index 377ba24fe8c..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/init.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import Layout from './layout';
-import Metrics from './metrics';
-import HeaderView from './header-view';
-import ListView from './list-view';
-import ListFooterView from './list-footer-view';
-import { getMetricDomains, getMetricTypes } from '../../api/metrics';
-
-const App = new Marionette.Application();
-const init = function(el) {
- // Layout
- this.layout = new Layout({ el });
- this.layout.render();
-
- // Collection
- this.metrics = new Metrics();
-
- // Header View
- this.headerView = new HeaderView({
- collection: this.metrics,
- domains: this.domains,
- types: this.types,
- app: App
- });
- this.layout.headerRegion.show(this.headerView);
-
- // List View
- this.listView = new ListView({
- collection: this.metrics,
- domains: this.domains,
- types: this.types
- });
- this.layout.listRegion.show(this.listView);
-
- // List Footer View
- this.listFooterView = new ListFooterView({ collection: this.metrics });
- this.layout.listFooterRegion.show(this.listFooterView);
-
- // Go!
- this.metrics.fetch();
-};
-
-App.on('start', el => {
- Promise.all([getMetricDomains(), getMetricTypes()]).then(
- ([domains, types]) => {
- App.domains = domains;
- App.types = types;
- init.call(App, el);
- },
- () => {}
- );
-});
-
-export default function(el) {
- App.start(el);
-}
diff --git a/server/sonar-web/src/main/js/apps/metrics/list-item-view.js b/server/sonar-web/src/main/js/apps/metrics/list-item-view.js
deleted file mode 100644
index 5cd0cd21abb..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/list-item-view.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import UpdateView from './update-view';
-import DeleteView from './delete-view';
-import Template from './templates/metrics-list-item.hbs';
-
-export default Marionette.ItemView.extend({
- tagName: 'li',
- className: 'panel panel-vertical',
- template: Template,
-
- events: {
- 'click .js-metric-update': 'onUpdateClick',
- 'click .js-metric-delete': 'onDeleteClick'
- },
-
- onRender() {
- this.$el.attr('data-id', this.model.id).attr('data-key', this.model.get('key'));
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- },
-
- onDestroy() {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onUpdateClick(e) {
- e.preventDefault();
- this.updateMetric();
- },
-
- onDeleteClick(e) {
- e.preventDefault();
- this.deleteMetric();
- },
-
- updateMetric() {
- new UpdateView({
- model: this.model,
- collection: this.model.collection,
- types: this.options.types,
- domains: this.options.domains
- }).render();
- },
-
- deleteMetric() {
- new DeleteView({ model: this.model }).render();
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-delete.hbs b/server/sonar-web/src/main/js/apps/metrics/templates/metrics-delete.hbs
deleted file mode 100644
index e6bf9ff5323..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-delete.hbs
+++ /dev/null
@@ -1,13 +0,0 @@
-<form id="delete-metric-form">
- <div class="modal-head">
- <h2>{{t 'custom_metrics.delete_metric'}}</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- {{tp 'custom_metrics.delete_metric.confirmation' name}}
- </div>
- <div class="modal-foot">
- <button id="delete-metric-submit" class="button-red">{{t 'delete'}}</button>
- <a href="#" class="js-modal-close" id="delete-metric-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-form.hbs b/server/sonar-web/src/main/js/apps/metrics/templates/metrics-form.hbs
deleted file mode 100644
index 638c96f654d..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-form.hbs
+++ /dev/null
@@ -1,36 +0,0 @@
-<form id="create-metric-form" autocomplete="off">
- <div class="modal-head">
- <h2>{{#if id}}{{t 'custom_metrics.update_metric'}}{{else}}{{t 'custom_metrics.create_metric'}}{{/if}}</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- <div class="modal-field">
- <label for="create-metric-key">{{t 'key'}}<em class="mandatory">*</em></label>
- <input id="create-metric-key" name="key" type="text" maxlength="64" required value="{{key}}">
- </div>
- <div class="modal-field">
- <label for="create-metric-name">{{t 'name'}}<em class="mandatory">*</em></label>
- <input id="create-metric-name" name="name" type="text" maxlength="64" required value="{{name}}">
- </div>
- <div class="modal-field">
- <label for="create-metric-description">{{t 'description'}}</label>
- <textarea id="create-metric-description" maxlength="255" name="description">{{description}}</textarea>
- </div>
- <div class="modal-field">
- <label for="create-metric-domain">{{t 'custom_metrics.domain'}}</label>
- <input id="create-metric-domain" name="domain" type="text" maxlength="200" value="{{this.domain}}">
- </div>
- <div class="modal-field">
- <label for="create-metric-type">{{t 'type'}}<em class="mandatory">*</em></label>
- <select id="create-metric-type" name="type">
- {{#each types}}
- <option value="{{this}}" {{#eq this ../type}}selected{{/eq}}>{{t 'metric.type' this}}</option>
- {{/each}}
- </select>
- </div>
- </div>
- <div class="modal-foot">
- <button id="create-metric-submit">{{#if id}}{{t 'update_verb'}}{{else}}{{t 'create'}}{{/if}}</button>
- <a href="#" class="js-modal-close" id="create-metric-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-header.hbs b/server/sonar-web/src/main/js/apps/metrics/templates/metrics-header.hbs
deleted file mode 100644
index 83f44fc0e50..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-header.hbs
+++ /dev/null
@@ -1,7 +0,0 @@
-<header class="page-header">
- <h1 class="page-title">{{t 'custom_metrics.page'}}</h1>
- <div class="page-actions">
- <button id="metrics-create">{{t 'custom_metrics.create_metric'}}</button>
- </div>
- <p class="page-description">{{t 'custom_metrics.page.description'}}</p>
-</header>
diff --git a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-layout.hbs b/server/sonar-web/src/main/js/apps/metrics/templates/metrics-layout.hbs
deleted file mode 100644
index 179ee3ed067..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-layout.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-<div class="page page-limited">
- <div id="metrics-header"></div>
- <div id="metrics-list" class="boxed-group boxed-group-inner"></div>
- <div id="metrics-list-footer"></div>
-</div>
diff --git a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-footer.hbs b/server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-footer.hbs
deleted file mode 100644
index fe8af8e8aef..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-footer.hbs
+++ /dev/null
@@ -1,6 +0,0 @@
-<footer class="spacer-top note text-center">
- {{tp 'x_of_y_shown' count total}}
- {{#if more}}
- <a id="metrics-fetch-more" class="spacer-left" href="#">{{t 'show_more'}}</a>
- {{/if}}
-</footer>
diff --git a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-item.hbs b/server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-item.hbs
deleted file mode 100644
index 4033a0c9024..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/templates/metrics-list-item.hbs
+++ /dev/null
@@ -1,35 +0,0 @@
-<div class="pull-right big-spacer-left nowrap">
- <div class="dropdown">
- <button class="dropdown-toggle" data-toggle="dropdown">
- {{settingsIcon}}<i class="icon-dropdown little-spacer-left" />
- </button>
- <ul class="dropdown-menu dropdown-menu-right">
- <li>
- <a class="js-metric-update" href="#">{{t 'update_verb'}}</a>
- </li>
- <li class="divider" />
- <li>
- <a class="js-metric-delete text-danger" href="#">{{t 'delete'}}</a>
- </li>
- </ul>
- </div>
-</div>
-
-<div class="display-inline-block text-top width-30">
- <div>
- <strong class="js-metric-name text-limited">{{name}}</strong>
- <span class="js-metric-key note little-spacer-left text-limited">{{key}}</span>
- </div>
-</div>
-
-<div class="display-inline-block text-top width-20 text-ellipsis">
- <span class="js-metric-domain">{{this.domain}}</span>
-</div>
-
-<div class="display-inline-block text-top width-20 text-ellipsis">
- <span class="js-metric-type">{{t 'metric.type' type}}</span>
-</div>
-
-<div class="display-inline-block text-top width-20 text-ellipsis" title="{{description}}">
- <span class="js-metric-description">{{description}}</span>
-</div>
diff --git a/server/sonar-web/src/main/js/apps/metrics/update-view.js b/server/sonar-web/src/main/js/apps/metrics/update-view.js
deleted file mode 100644
index e9b1c136d6e..00000000000
--- a/server/sonar-web/src/main/js/apps/metrics/update-view.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import FormView from './form-view';
-
-export default FormView.extend({
- sendRequest() {
- const that = this;
- this.model.set({
- key: this.$('#create-metric-key').val(),
- name: this.$('#create-metric-name').val(),
- description: this.$('#create-metric-description').val(),
- domain: this.$('#create-metric-domain').val(),
- type: this.$('#create-metric-type').val()
- });
- this.disableForm();
- return this.model
- .save(null, {
- statusCode: {
- // do not show global error
- 400: null
- }
- })
- .done(() => {
- that.collection.refresh();
- that.destroy();
- })
- .fail(jqXHR => {
- that.enableForm();
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx
index 3f7904c67e6..3a8abab2294 100644
--- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/ApplicationQualityGateProject-test.tsx
@@ -22,9 +22,9 @@ 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' }
+ bugs: { id: '1', key: 'bugs', name: 'Bugs', type: 'INT' },
+ new_coverage: { id: '2', key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' },
+ skipped_tests: { id: '3', key: 'skipped_tests', name: 'Skipped Tests', type: 'INT' }
};
it('renders', () => {
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx
index fdffd7d0d47..e52447250fe 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ThresholdInput-test.tsx
@@ -23,7 +23,7 @@ import ThresholdInput from '../ThresholdInput';
import { change } from '../../../../helpers/testUtils';
describe('on strings', () => {
- const metric = { key: 'foo', name: 'Foo', type: 'INTEGER' };
+ const metric = { id: '1', key: 'foo', name: 'Foo', type: 'INTEGER' };
it('should render text input', () => {
const input = shallow(
<ThresholdInput name="foo" value="2" metric={metric} onChange={jest.fn()} />
@@ -44,7 +44,7 @@ describe('on strings', () => {
});
describe('on ratings', () => {
- const metric = { key: 'foo', name: 'Foo', type: 'RATING' };
+ const metric = { id: '1', key: 'foo', name: 'Foo', type: 'RATING' };
it('should render Select', () => {
const select = shallow(
<ThresholdInput name="foo" value="2" metric={metric} onChange={jest.fn()} />