aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-02-13 09:19:41 +0100
committerGitHub <noreply@github.com>2018-02-13 09:19:41 +0100
commitab801b91e6f61f587a3e8f059b7c0953814617fa (patch)
treedef42912ababd4a436c88e248da0dbd1901ab257
parent40f9260e999f65532a82096a7995f14fad8f695f (diff)
downloadsonarqube-ab801b91e6f61f587a3e8f059b7c0953814617fa.tar.gz
sonarqube-ab801b91e6f61f587a3e8f059b7c0953814617fa.zip
rewrite custom measures app in react (#3052)
-rw-r--r--server/sonar-web/src/main/js/api/measures.ts37
-rw-r--r--server/sonar-web/src/main/js/app/types.ts22
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx159
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx73
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/DeleteButton.tsx53
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/EditButton.tsx79
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx209
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/Header.tsx (renamed from server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js)32
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/List.tsx125
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx90
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/CreateButton-test.tsx (renamed from server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js)48
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/DeleteButton-test.tsx (renamed from server/sonar-web/src/main/js/apps/custom-measures/header-view.js)38
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/EditButton-test.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx68
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Header-test.tsx (renamed from server/sonar-web/src/main/js/apps/custom-measures/layout.js)22
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/List-test.tsx51
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/App-test.tsx.snap173
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap32
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/EditButton-test.tsx.snap48
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap219
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Header-test.tsx.snap31
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/List-test.tsx.snap272
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/create-view.js49
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js55
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js66
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/delete-view.js50
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/form-view.js66
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/init.js67
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js63
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/list-view.js28
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/metric.js55
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/metrics.js56
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/routes.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs13
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs39
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs7
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs5
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs6
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs49
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list.hbs14
-rw-r--r--server/sonar-web/src/main/js/apps/custom-measures/update-view.js46
-rw-r--r--server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx2
43 files changed, 1872 insertions, 811 deletions
diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts
index 2f5ae506503..bf6f8712090 100644
--- a/server/sonar-web/src/main/js/api/measures.ts
+++ b/server/sonar-web/src/main/js/api/measures.ts
@@ -17,10 +17,10 @@
* 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, RequestData } from '../helpers/request';
+import { getJSON, RequestData, postJSON, post } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import { Measure, MeasurePeriod } from '../helpers/measures';
-import { Metric } from '../app/types';
+import { Metric, CustomMeasure, Paging } from '../app/types';
import { Period } from '../helpers/periods';
export function getMeasures(
@@ -66,3 +66,36 @@ export function getMeasuresForProjects(
metricKeys: metricKeys.join()
}).then(r => r.measures);
}
+
+export function getCustomMeasures(data: {
+ f?: string;
+ p?: number;
+ projectKey: string;
+ ps?: number;
+}): Promise<{ customMeasures: CustomMeasure[]; paging: Paging }> {
+ return getJSON('/api/custom_measures/search', data).then(
+ r =>
+ ({
+ customMeasures: r.customMeasures,
+ paging: { pageIndex: r.p, pageSize: r.ps, total: r.total }
+ } as any),
+ throwGlobalError
+ );
+}
+
+export function createCustomMeasure(data: {
+ description?: string;
+ metricKey: string;
+ projectKey: string;
+ value: string;
+}): Promise<CustomMeasure> {
+ return postJSON('/api/custom_measures/create', data).catch(throwGlobalError);
+}
+
+export function updateCustomMeasure(data: { description?: string; id: string; value?: string }) {
+ return post('/api/custom_measures/update', data).catch(throwGlobalError);
+}
+
+export function deleteCustomMeasure(data: { id: string }) {
+ return post('/api/custom_measures/delete', data).catch(throwGlobalError);
+}
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts
index 1eb6b2b5350..504e86547be 100644
--- a/server/sonar-web/src/main/js/app/types.ts
+++ b/server/sonar-web/src/main/js/app/types.ts
@@ -292,3 +292,25 @@ export interface User {
scmAccounts?: string[];
tokensCount?: number;
}
+
+export interface CustomMeasure {
+ createdAt?: string;
+ description?: string;
+ id: string;
+ metric: {
+ key: string;
+ name: string;
+ domain?: string;
+ type: string;
+ };
+ projectKey: string;
+ pending?: boolean;
+ user: {
+ active?: boolean;
+ email?: string;
+ login: string;
+ name: string;
+ };
+ value: string;
+ updatedAt?: string;
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx
new file mode 100644
index 00000000000..10ad59e53d8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx
@@ -0,0 +1,159 @@
+/*
+ * 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 Header from './Header';
+import List from './List';
+import {
+ getCustomMeasures,
+ createCustomMeasure,
+ updateCustomMeasure,
+ deleteCustomMeasure
+} from '../../../api/measures';
+import { Paging, CustomMeasure } from '../../../app/types';
+import ListFooter from '../../../components/controls/ListFooter';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ component: { key: string };
+}
+
+interface State {
+ loading: boolean;
+ measures?: CustomMeasure[];
+ paging?: Paging;
+}
+
+const PAGE_SIZE = 50;
+
+export default class App extends React.PureComponent<Props, State> {
+ mounted: boolean;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchMeasures();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchMeasures = () => {
+ this.setState({ loading: true });
+ getCustomMeasures({ projectKey: this.props.component.key, ps: PAGE_SIZE }).then(
+ ({ customMeasures, paging }) => {
+ if (this.mounted) {
+ this.setState({ loading: false, measures: customMeasures, paging });
+ }
+ },
+ this.stopLoading
+ );
+ };
+
+ fetchMore = () => {
+ const { paging } = this.state;
+ if (paging) {
+ this.setState({ loading: true });
+ getCustomMeasures({
+ projectKey: this.props.component.key,
+ p: paging.pageIndex + 1,
+ ps: PAGE_SIZE
+ }).then(({ customMeasures, paging }) => {
+ if (this.mounted) {
+ this.setState(({ measures = [] }: State) => ({
+ loading: false,
+ measures: [...measures, ...customMeasures],
+ paging
+ }));
+ }
+ }, this.stopLoading);
+ }
+ };
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ handleCreate = (data: { description: string; metricKey: string; value: string }) => {
+ return createCustomMeasure({ ...data, projectKey: this.props.component.key }).then(measure => {
+ if (this.mounted) {
+ this.setState(({ measures = [], paging }: State) => ({
+ measures: [...measures, measure],
+ paging: paging && { ...paging, total: paging.total + 1 }
+ }));
+ }
+ });
+ };
+
+ handleEdit = (data: { description: string; id: string; value: string }) => {
+ return updateCustomMeasure(data).then(() => {
+ if (this.mounted) {
+ this.setState(({ measures = [] }: State) => ({
+ measures: measures.map(
+ measure => (measure.id === data.id ? { ...measure, ...data } : measure)
+ )
+ }));
+ }
+ });
+ };
+
+ handleDelete = (measureId: string) => {
+ return deleteCustomMeasure({ id: measureId }).then(() => {
+ if (this.mounted) {
+ this.setState(({ measures = [], paging }: State) => ({
+ measures: measures.filter(measure => measure.id !== measureId),
+ paging: paging && { ...paging, total: paging.total - 1 }
+ }));
+ }
+ });
+ };
+
+ render() {
+ const { loading, measures, paging } = this.state;
+
+ return (
+ <>
+ <Helmet title={translate('custom_measures.page')} />
+ <div className="page page-limited">
+ <Header
+ loading={loading}
+ onCreate={this.handleCreate}
+ skipMetrics={measures && measures.map(measure => measure.metric.key)}
+ />
+ {measures && (
+ <List measures={measures} onDelete={this.handleDelete} onEdit={this.handleEdit} />
+ )}
+ {measures &&
+ paging && (
+ <ListFooter
+ count={measures.length}
+ loadMore={this.fetchMore}
+ ready={!loading}
+ total={paging.total}
+ />
+ )}
+ </div>
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx
new file mode 100644
index 00000000000..ed9e536e3f1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 from './Form';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ onCreate: (data: { description: string; metricKey: string; value: string }) => Promise<void>;
+ skipMetrics: string[] | undefined;
+}
+
+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="custom-measures-create" onClick={this.handleClick} type="button">
+ {translate('create')}
+ </button>
+ {this.state.modal && (
+ <Form
+ confirmButtonText={translate('create')}
+ header={translate('custom_measures.create_custom_measure')}
+ onClose={this.handleClose}
+ onSubmit={this.props.onCreate}
+ skipMetrics={this.props.skipMetrics}
+ />
+ )}
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/DeleteButton.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/DeleteButton.tsx
new file mode 100644
index 00000000000..f4f2ff15d24
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/DeleteButton.tsx
@@ -0,0 +1,53 @@
+/*
+ * 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 { CustomMeasure } from '../../../app/types';
+import ConfirmButton from '../../../components/controls/ConfirmButton';
+import { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+ measure: CustomMeasure;
+ onDelete: (measureId: string) => Promise<void>;
+}
+
+export default function DeleteButton({ measure, onDelete }: Props) {
+ return (
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ confirmData={measure.id}
+ isDestructive={true}
+ modalBody={translateWithParameters(
+ 'custom_measures.delete_custom_measure.confirmation',
+ measure.metric.name
+ )}
+ modalHeader={translate('custom_measures.delete_custom_measure')}
+ onConfirm={onDelete}>
+ {({ onClick }) => (
+ <ActionsDropdownItem
+ className="js-custom-measure-delete"
+ destructive={true}
+ onClick={onClick}>
+ {translate('delete')}
+ </ActionsDropdownItem>
+ )}
+ </ConfirmButton>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/EditButton.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/EditButton.tsx
new file mode 100644
index 00000000000..ac0bef49808
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/EditButton.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 from './Form';
+import { CustomMeasure } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
+
+interface Props {
+ measure: CustomMeasure;
+ onEdit: (data: { description: string; id: string; value: string }) => Promise<void>;
+}
+
+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: { description: string; value: string }) => {
+ return this.props.onEdit({ id: this.props.measure.id, ...data });
+ };
+
+ render() {
+ return (
+ <>
+ <ActionsDropdownItem className="js-custom-measure-update" onClick={this.handleClick}>
+ {translate('update_verb')}
+ </ActionsDropdownItem>
+ {this.state.modal && (
+ <Form
+ confirmButtonText={translate('update_verb')}
+ header={translate('custom_measures.update_custom_measure')}
+ measure={this.props.measure}
+ onClose={this.handleClose}
+ onSubmit={this.handleSubmit}
+ />
+ )}
+ </>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx
new file mode 100644
index 00000000000..97aceef43da
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx
@@ -0,0 +1,209 @@
+/*
+ * 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 { getAllMetrics } from '../../../api/metrics';
+import { CustomMeasure, Metric } from '../../../app/types';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import Select from '../../../components/controls/Select';
+import SimpleModal from '../../../components/controls/SimpleModal';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ confirmButtonText: string;
+ header: string;
+ measure?: CustomMeasure;
+ onClose: () => void;
+ onSubmit: (data: { description: string; metricKey: string; value: string }) => Promise<void>;
+ skipMetrics?: string[];
+}
+
+interface State {
+ description: string;
+ loading: boolean;
+ metricKey?: string;
+ metrics?: Metric[];
+ value: string;
+}
+
+export default class Form extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ description: (props.measure && props.measure.description) || '',
+ loading: false,
+ metricKey: props.measure && props.measure.metric.key,
+ value: (props.measure && props.measure.value) || ''
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ if (!this.props.measure) {
+ this.fetchCustomMetrics();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleSubmit = () => {
+ return this.state.metricKey
+ ? this.props
+ .onSubmit({
+ description: this.state.description,
+ metricKey: this.state.metricKey,
+ value: this.state.value
+ })
+ .then(this.props.onClose)
+ : Promise.reject(undefined);
+ };
+
+ fetchCustomMetrics = () => {
+ this.setState({ loading: true });
+ getAllMetrics({ isCustom: true }).then(
+ metrics => {
+ if (this.mounted) {
+ this.setState({ loading: false, metrics });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ handleMetricSelect = ({ value }: { value: string }) => {
+ this.setState({ metricKey: value });
+ };
+
+ handleDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
+ this.setState({ description: event.currentTarget.value });
+ };
+
+ handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
+ this.setState({ value: event.currentTarget.value });
+ };
+
+ renderMetricSelect = (options: { label: string; value: string }[]) => {
+ if (!options.length && !this.state.loading) {
+ return (
+ <div className="alert alert-warning">{translate('custom_measures.all_metrics_taken')}</div>
+ );
+ }
+ return (
+ <div className="modal-field">
+ <label htmlFor="create-custom-measure-metric">
+ {translate('custom_measures.metric')}
+ <em className="mandatory">*</em>
+ </label>
+ {this.state.loading ? (
+ <i className="spinner" />
+ ) : (
+ <Select
+ autofocus={true}
+ clearable={false}
+ onChange={this.handleMetricSelect}
+ options={options}
+ value={this.state.metricKey}
+ />
+ )}
+ </div>
+ );
+ };
+
+ render() {
+ const { skipMetrics = [] } = this.props;
+ const { metrics = [] } = this.state;
+ const options = metrics
+ .filter(metric => !skipMetrics.includes(metric.key))
+ .map(metric => ({ label: metric.name, value: metric.key }));
+ const forbidSubmitting = !this.props.measure && !options.length;
+
+ 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">
+ {!this.props.measure && this.renderMetricSelect(options)}
+
+ <div className="modal-field">
+ <label htmlFor="create-custom-measure-value">
+ {translate('value')}
+ <em className="mandatory">*</em>
+ </label>
+ <input
+ autoFocus={this.props.measure !== undefined}
+ id="create-custom-measure-value"
+ maxLength={200}
+ name="value"
+ onChange={this.handleValueChange}
+ required={true}
+ type="text"
+ value={this.state.value}
+ />
+ </div>
+ <div className="modal-field">
+ <label htmlFor="create-custom-measure-description">
+ {translate('description')}
+ </label>
+ <textarea
+ id="create-custom-measure-description"
+ name="description"
+ onChange={this.handleDescriptionChange}
+ value={this.state.description}
+ />
+ </div>
+ </div>
+
+ <footer className="modal-foot">
+ <DeferredSpinner className="spacer-right" loading={submitting} />
+ <button
+ disabled={forbidSubmitting || submitting}
+ id="create-custom-measure-submit"
+ type="submit">
+ {this.props.confirmButtonText}
+ </button>
+ <button
+ className="button-link"
+ disabled={submitting}
+ id="create-custom-measure-cancel"
+ onClick={onCloseClick}
+ type="reset">
+ {translate('cancel')}
+ </button>
+ </footer>
+ </form>
+ )}
+ </SimpleModal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js b/server/sonar-web/src/main/js/apps/custom-measures/components/Header.tsx
index 3decb585813..ce71ae65f19 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/Header.tsx
@@ -17,22 +17,26 @@
* 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 Helmet from 'react-helmet';
-import init from '../init';
+import * as React from 'react';
+import CreateButton from './CreateButton';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { translate } from '../../../helpers/l10n';
-export default class CustomMeasuresAppContainer extends React.PureComponent {
- componentDidMount() {
- init(this.refs.container, this.props.component);
- }
+interface Props {
+ loading: boolean;
+ onCreate: (data: { description: string; metricKey: string; value: string }) => Promise<void>;
+ skipMetrics: string[] | undefined;
+}
- render() {
- return (
- <div>
- <Helmet title={translate('custom_measures.page')} />
- <div ref="container" />
+export default function Header({ loading, onCreate, skipMetrics }: Props) {
+ return (
+ <header className="page-header" id="custom-measures-header">
+ <h1 className="page-title">{translate('custom_measures.page')}</h1>
+ <DeferredSpinner loading={loading} />
+ <div className="page-actions">
+ <CreateButton onCreate={onCreate} skipMetrics={skipMetrics} />
</div>
- );
- }
+ <p className="page-description">{translate('custom_measures.page.description')}</p>
+ </header>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/List.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/List.tsx
new file mode 100644
index 00000000000..239b4eeec6c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/List.tsx
@@ -0,0 +1,125 @@
+/*
+ * 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 { CustomMeasure } from '../../../app/types';
+import ActionsDropdown, {
+ ActionsDropdownDivider
+} from '../../../components/controls/ActionsDropdown';
+import { translate } from '../../../helpers/l10n';
+import Tooltip from '../../../components/controls/Tooltip';
+import { formatMeasure } from '../../../helpers/measures';
+import DateFormatter from '../../../components/intl/DateFormatter';
+
+interface Props {
+ measures: CustomMeasure[];
+ onDelete: (measureId: string) => Promise<void>;
+ onEdit: (data: { description: string; id: string; value: string }) => Promise<void>;
+}
+
+export default function List({ measures, onDelete, onEdit }: Props) {
+ return (
+ <div className="boxed-group boxed-group-inner" id="custom-measures-list">
+ {measures.length > 0 ? (
+ <table className="data zebra zebra-hover">
+ <thead>
+ <tr>
+ <th>{translate('custom_measures.metric')}</th>
+ <th>{translate('value')}</th>
+ <th>{translate('description')}</th>
+ <th>{translate('date')}</th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ {sortBy(measures, measure => measure.metric.name.toLowerCase()).map(measure => (
+ <tr data-metric={measure.metric.key} key={measure.id}>
+ <td className="nowrap">
+ <div>
+ <span className="js-custom-measure-metric-name">{measure.metric.name}</span>
+ {measure.pending && (
+ <Tooltip overlay={translate('custom_measures.pending_tooltip')}>
+ <span className="js-custom-measure-pending badge badge-warning spacer-left">
+ {translate('custom_measures.pending')}
+ </span>
+ </Tooltip>
+ )}
+ </div>
+ <span className="js-custom-measure-domain note">{measure.metric.domain}</span>
+ </td>
+
+ <td className="nowrap">
+ <strong className="js-custom-measure-value">
+ {formatMeasure(measure.value, measure.metric.type)}
+ </strong>
+ </td>
+
+ <td>
+ <span className="js-custom-measure-description">{measure.description}</span>
+ </td>
+
+ <td>
+ <MeasureDate measure={measure} /> {translate('by_')}{' '}
+ <span className="js-custom-measure-user">{measure.user.name}</span>
+ </td>
+
+ <td className="thin nowrap">
+ <ActionsDropdown>
+ <EditButton measure={measure} onEdit={onEdit} />
+ <ActionsDropdownDivider />
+ <DeleteButton measure={measure} onDelete={onDelete} />
+ </ActionsDropdown>
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ ) : (
+ <p>{translate('no_results')}</p>
+ )}
+ </div>
+ );
+}
+
+function MeasureDate({ measure }: { measure: CustomMeasure }) {
+ if (measure.updatedAt) {
+ return (
+ <>
+ {translate('updated_on')}{' '}
+ <span className="js-custom-measure-created-at">
+ <DateFormatter date={measure.updatedAt} />
+ </span>
+ </>
+ );
+ } else if (measure.createdAt) {
+ return (
+ <>
+ {translate('created_on')}{' '}
+ <span className="js-custom-measure-created-at">
+ <DateFormatter date={measure.createdAt} />
+ </span>
+ </>
+ );
+ } else {
+ return <>{translate('created')}</>;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx
new file mode 100644
index 00000000000..a264af89975
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.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 { shallow } from 'enzyme';
+import App from '../App';
+
+jest.mock('../../../../api/measures', () => ({
+ getCustomMeasures: () =>
+ Promise.resolve({
+ customMeasures: [
+ {
+ createdAt: '2017-01-01',
+ description: 'my custom measure',
+ id: '1',
+ metric: { key: 'custom', name: 'custom-metric', type: 'STRING' },
+ projectKey: 'foo',
+ user: { active: true, login: 'user', name: 'user' },
+ value: 'custom-value'
+ }
+ ],
+ paging: { pageIndex: 1, pageSize: 1, total: 1 }
+ }),
+ createCustomMeasure: () =>
+ Promise.resolve({
+ createdAt: '2018-01-01',
+ description: 'description',
+ id: '2',
+ metric: { key: 'metricKey', name: 'Metric Name', type: 'STRING' },
+ projectKey: 'foo',
+ user: { active: true, login: 'user', name: 'user' },
+ value: 'value'
+ }),
+ updateCustomMeasure: () => Promise.resolve(),
+ deleteCustomMeasure: () => Promise.resolve()
+}));
+
+it('should work', async () => {
+ const wrapper = shallow(<App component={{ key: 'foo' }} />);
+ expect(wrapper).toMatchSnapshot();
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ // create
+ wrapper.find('Header').prop<Function>('onCreate')({
+ description: 'description',
+ metricKey: 'metricKey',
+ value: 'value'
+ });
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state().measures).toMatchSnapshot();
+ expect(wrapper.state().paging.total).toBe(2);
+
+ // edit
+ wrapper.find('List').prop<Function>('onEdit')({
+ description: 'another',
+ id: '2',
+ value: 'other'
+ });
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state().measures).toMatchSnapshot();
+ expect(wrapper.state().paging.total).toBe(2);
+
+ // delete
+ wrapper.find('List').prop<Function>('onDelete')('2');
+ await new Promise(setImmediate);
+ wrapper.update();
+ expect(wrapper.state().measures).toMatchSnapshot();
+ expect(wrapper.state().paging.total).toBe(1);
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/CreateButton-test.tsx
index 0b66b167373..f939e14f03c 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/list-footer-view.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/CreateButton-test.tsx
@@ -17,35 +17,27 @@
* 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/custom-measures-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 custom measure', () => {
+ const onCreate = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(<CreateButton onCreate={onCreate} skipMetrics={[]} />);
+ expect(wrapper).toMatchSnapshot();
- collectionEvents: {
- all: 'render'
- },
+ click(wrapper.find('#custom-measures-create'));
+ expect(wrapper).toMatchSnapshot();
- events: {
- 'click #custom-measures-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')({
+ description: 'description',
+ metricKey: 'metricKey',
+ value: 'value'
+ });
+ expect(onCreate).toBeCalledWith({
+ description: 'description',
+ metricKey: 'metricKey',
+ value: 'value'
+ });
});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/header-view.js b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/DeleteButton-test.tsx
index bfe9ee89909..3c032dafc62 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/header-view.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/DeleteButton-test.tsx
@@ -17,26 +17,24 @@
* 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/custom-measures-header.hbs';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import DeleteButton from '../DeleteButton';
-export default Marionette.ItemView.extend({
- template: Template,
+it('should delete custom measure', () => {
+ const measure = {
+ createdAt: '2017-01-01',
+ description: 'my custom measure',
+ id: '1',
+ metric: { key: 'custom', name: 'custom-metric', type: 'STRING' },
+ projectKey: 'foo',
+ user: { active: true, login: 'user', name: 'user' },
+ value: 'custom-value'
+ };
+ const onDelete = jest.fn();
+ const wrapper = shallow(<DeleteButton measure={measure} onDelete={onDelete} />);
+ expect(wrapper).toMatchSnapshot();
- events: {
- 'click #custom-measures-create': 'onCreateClick'
- },
-
- onCreateClick(e) {
- e.preventDefault();
- this.createCustomMeasure();
- },
-
- createCustomMeasure() {
- new CreateView({
- collection: this.collection,
- projectId: this.options.projectId
- }).render();
- }
+ wrapper.find('ConfirmButton').prop<Function>('onConfirm')('1');
+ expect(onDelete).toBeCalledWith('1');
});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/EditButton-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/EditButton-test.tsx
new file mode 100644
index 00000000000..f1efbc3a527
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/EditButton-test.tsx
@@ -0,0 +1,50 @@
+/*
+ * 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 EditButton from '../EditButton';
+import { click } from '../../../../helpers/testUtils';
+
+it('should edit metric', () => {
+ const measure = {
+ createdAt: '2017-01-01',
+ description: 'my custom measure',
+ id: '1',
+ metric: { key: 'custom', name: 'custom-metric', type: 'STRING' },
+ projectKey: 'foo',
+ user: { active: true, login: 'user', name: 'user' },
+ value: 'custom-value'
+ };
+ const onEdit = jest.fn();
+
+ const wrapper = shallow(<EditButton measure={measure} onEdit={onEdit} />);
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.js-custom-measure-update'));
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.find('Form').prop<Function>('onSubmit')({
+ ...measure,
+ description: 'new-description',
+ value: 'new-value'
+ });
+ expect(onEdit).toBeCalledWith({ ...measure, description: 'new-description', value: 'new-value' });
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx
new file mode 100644
index 00000000000..3af7a3c000a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx
@@ -0,0 +1,68 @@
+/*
+ * 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';
+
+jest.mock('../../../../api/metrics', () => ({
+ getAllMetrics: () =>
+ Promise.resolve([
+ { id: '1', key: 'custom-metric', name: 'Custom Metric', type: 'STRING' },
+ { id: '2', key: 'skipped-metric', name: 'Skipped Metric', type: 'FLOAT' }
+ ])
+}));
+
+it('should render form', async () => {
+ const onClose = jest.fn();
+ const onSubmit = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(
+ <Form
+ confirmButtonText="confirmButtonText"
+ header="header"
+ onClose={onClose}
+ onSubmit={onSubmit}
+ skipMetrics={['skipped-metric']}
+ />
+ );
+ expect(wrapper.dive()).toMatchSnapshot();
+
+ await new Promise(setImmediate);
+ wrapper.update();
+ const form = wrapper.dive();
+ expect(form).toMatchSnapshot();
+
+ form.find('Select').prop<Function>('onChange')({ value: 'custom-metric' });
+ change(form.find('[name="value"]'), 'Foo');
+ change(form.find('[name="description"]'), 'bar');
+ submit(form.find('form'));
+ expect(onSubmit).toBeCalledWith({
+ description: 'bar',
+ metricKey: 'custom-metric',
+ value: 'Foo'
+ });
+
+ await new Promise(setImmediate);
+ expect(onClose).toBeCalled();
+
+ onClose.mockClear();
+ click(form.find('button[type="reset"]'));
+ expect(onClose).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/layout.js b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Header-test.tsx
index 73f5ee6060d..f4b9a77c3a3 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/layout.js
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Header-test.tsx
@@ -17,15 +17,19 @@
* 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/custom-measures-layout.hbs';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Header from '../Header';
-export default Marionette.LayoutView.extend({
- template: Template,
+it('should create new custom measure', () => {
+ const onCreate = jest.fn(() => Promise.resolve());
+ const wrapper = shallow(<Header loading={false} onCreate={onCreate} skipMetrics={[]} />);
+ expect(wrapper).toMatchSnapshot();
- regions: {
- headerRegion: '#custom-measures-header',
- listRegion: '#custom-measures-list',
- listFooterRegion: '#custom-measures-list-footer'
- }
+ wrapper.find('CreateButton').prop<Function>('onCreate')({
+ description: 'bla',
+ metricKey: 'custom-metric',
+ name: 'Foo'
+ });
+ expect(onCreate).toBeCalledWith({ description: 'bla', metricKey: 'custom-metric', name: 'Foo' });
});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/List-test.tsx b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/List-test.tsx
new file mode 100644
index 00000000000..adf68588a0a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/List-test.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 List from '../List';
+
+it('should render', () => {
+ const measures = [
+ {
+ createdAt: '2017-01-01',
+ description: 'my custom measure',
+ id: '1',
+ metric: { key: 'custom', name: 'custom-metric', type: 'STRING' },
+ projectKey: 'foo',
+ user: { active: true, login: 'user', name: 'user' },
+ value: 'custom-value'
+ },
+ {
+ createdAt: '2017-01-01',
+ id: '2',
+ metric: { key: 'another', name: 'another-metric', type: 'STRING' },
+ projectKey: 'foo',
+ user: { active: true, login: 'user', name: 'user' },
+ value: 'another-value'
+ }
+ ];
+ expect(
+ shallow(<List measures={measures} onDelete={jest.fn()} onEdit={jest.fn()} />)
+ ).toMatchSnapshot();
+});
+
+it('should render no results', () => {
+ expect(shallow(<List measures={[]} onDelete={jest.fn()} onEdit={jest.fn()} />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/App-test.tsx.snap
new file mode 100644
index 00000000000..cb97413281e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/App-test.tsx.snap
@@ -0,0 +1,173 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should work 1`] = `
+<React.Fragment>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="custom_measures.page"
+ />
+ <div
+ className="page page-limited"
+ >
+ <Header
+ loading={true}
+ onCreate={[Function]}
+ />
+ </div>
+</React.Fragment>
+`;
+
+exports[`should work 2`] = `
+<React.Fragment>
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="custom_measures.page"
+ />
+ <div
+ className="page page-limited"
+ >
+ <Header
+ loading={false}
+ onCreate={[Function]}
+ skipMetrics={
+ Array [
+ "custom",
+ ]
+ }
+ />
+ <List
+ measures={
+ Array [
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ },
+ ]
+ }
+ onDelete={[Function]}
+ onEdit={[Function]}
+ />
+ <ListFooter
+ count={1}
+ loadMore={[Function]}
+ ready={true}
+ total={1}
+ />
+ </div>
+</React.Fragment>
+`;
+
+exports[`should work 3`] = `
+Array [
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ },
+ Object {
+ "createdAt": "2018-01-01",
+ "description": "description",
+ "id": "2",
+ "metric": Object {
+ "key": "metricKey",
+ "name": "Metric Name",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "value",
+ },
+]
+`;
+
+exports[`should work 4`] = `
+Array [
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ },
+ Object {
+ "createdAt": "2018-01-01",
+ "description": "another",
+ "id": "2",
+ "metric": Object {
+ "key": "metricKey",
+ "name": "Metric Name",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "other",
+ },
+]
+`;
+
+exports[`should work 5`] = `
+Array [
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ },
+]
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap
new file mode 100644
index 00000000000..78c55c67890
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should create new custom measure 1`] = `
+<React.Fragment>
+ <button
+ id="custom-measures-create"
+ onClick={[Function]}
+ type="button"
+ >
+ create
+ </button>
+</React.Fragment>
+`;
+
+exports[`should create new custom measure 2`] = `
+<React.Fragment>
+ <button
+ id="custom-measures-create"
+ onClick={[Function]}
+ type="button"
+ >
+ create
+ </button>
+ <Form
+ confirmButtonText="create"
+ header="custom_measures.create_custom_measure"
+ onClose={[Function]}
+ onSubmit={[MockFunction]}
+ skipMetrics={Array []}
+ />
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap
new file mode 100644
index 00000000000..c9fc5439615
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/DeleteButton-test.tsx.snap
@@ -0,0 +1,12 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should delete custom measure 1`] = `
+<ConfirmButton
+ confirmButtonText="delete"
+ confirmData="1"
+ isDestructive={true}
+ modalBody="custom_measures.delete_custom_measure.confirmation.custom-metric"
+ modalHeader="custom_measures.delete_custom_measure"
+ onConfirm={[MockFunction]}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/EditButton-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/EditButton-test.tsx.snap
new file mode 100644
index 00000000000..bd0a8035083
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/EditButton-test.tsx.snap
@@ -0,0 +1,48 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should edit metric 1`] = `
+<React.Fragment>
+ <ActionsDropdownItem
+ className="js-custom-measure-update"
+ onClick={[Function]}
+ >
+ update_verb
+ </ActionsDropdownItem>
+</React.Fragment>
+`;
+
+exports[`should edit metric 2`] = `
+<React.Fragment>
+ <ActionsDropdownItem
+ className="js-custom-measure-update"
+ onClick={[Function]}
+ >
+ update_verb
+ </ActionsDropdownItem>
+ <Form
+ confirmButtonText="update_verb"
+ header="custom_measures.update_custom_measure"
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ }
+ }
+ onClose={[Function]}
+ onSubmit={[Function]}
+ />
+</React.Fragment>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap
new file mode 100644
index 00000000000..17057ba3704
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap
@@ -0,0 +1,219 @@
+// 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-custom-measure-metric"
+ >
+ custom_measures.metric
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <i
+ className="spinner"
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-custom-measure-value"
+ >
+ value
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={false}
+ id="create-custom-measure-value"
+ maxLength={200}
+ name="value"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-custom-measure-description"
+ >
+ description
+ </label>
+ <textarea
+ id="create-custom-measure-description"
+ name="description"
+ onChange={[Function]}
+ value=""
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <DeferredSpinner
+ className="spacer-right"
+ loading={false}
+ timeout={100}
+ />
+ <button
+ disabled={true}
+ id="create-custom-measure-submit"
+ type="submit"
+ >
+ confirmButtonText
+ </button>
+ <button
+ className="button-link"
+ disabled={false}
+ id="create-custom-measure-cancel"
+ onClick={[Function]}
+ type="reset"
+ >
+ cancel
+ </button>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`should render form 2`] = `
+<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-custom-measure-metric"
+ >
+ custom_measures.metric
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <Select
+ autofocus={true}
+ clearable={false}
+ onChange={[Function]}
+ options={
+ Array [
+ Object {
+ "label": "Custom Metric",
+ "value": "custom-metric",
+ },
+ ]
+ }
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-custom-measure-value"
+ >
+ value
+ <em
+ className="mandatory"
+ >
+ *
+ </em>
+ </label>
+ <input
+ autoFocus={false}
+ id="create-custom-measure-value"
+ maxLength={200}
+ name="value"
+ onChange={[Function]}
+ required={true}
+ type="text"
+ value=""
+ />
+ </div>
+ <div
+ className="modal-field"
+ >
+ <label
+ htmlFor="create-custom-measure-description"
+ >
+ description
+ </label>
+ <textarea
+ id="create-custom-measure-description"
+ name="description"
+ onChange={[Function]}
+ value=""
+ />
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <DeferredSpinner
+ className="spacer-right"
+ loading={false}
+ timeout={100}
+ />
+ <button
+ disabled={false}
+ id="create-custom-measure-submit"
+ type="submit"
+ >
+ confirmButtonText
+ </button>
+ <button
+ className="button-link"
+ disabled={false}
+ id="create-custom-measure-cancel"
+ onClick={[Function]}
+ type="reset"
+ >
+ cancel
+ </button>
+ </footer>
+ </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Header-test.tsx.snap
new file mode 100644
index 00000000000..0ab190272e2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Header-test.tsx.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should create new custom measure 1`] = `
+<header
+ className="page-header"
+ id="custom-measures-header"
+>
+ <h1
+ className="page-title"
+ >
+ custom_measures.page
+ </h1>
+ <DeferredSpinner
+ loading={false}
+ timeout={100}
+ />
+ <div
+ className="page-actions"
+ >
+ <CreateButton
+ onCreate={[MockFunction]}
+ skipMetrics={Array []}
+ />
+ </div>
+ <p
+ className="page-description"
+ >
+ custom_measures.page.description
+ </p>
+</header>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/List-test.tsx.snap b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/List-test.tsx.snap
new file mode 100644
index 00000000000..005bedff1e0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/List-test.tsx.snap
@@ -0,0 +1,272 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<div
+ className="boxed-group boxed-group-inner"
+ id="custom-measures-list"
+>
+ <table
+ className="data zebra zebra-hover"
+ >
+ <thead>
+ <tr>
+ <th>
+ custom_measures.metric
+ </th>
+ <th>
+ value
+ </th>
+ <th>
+ description
+ </th>
+ <th>
+ date
+ </th>
+ <th />
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ data-metric="another"
+ key="2"
+ >
+ <td
+ className="nowrap"
+ >
+ <div>
+ <span
+ className="js-custom-measure-metric-name"
+ >
+ another-metric
+ </span>
+ </div>
+ <span
+ className="js-custom-measure-domain note"
+ />
+ </td>
+ <td
+ className="nowrap"
+ >
+ <strong
+ className="js-custom-measure-value"
+ >
+ another-value
+ </strong>
+ </td>
+ <td>
+ <span
+ className="js-custom-measure-description"
+ />
+ </td>
+ <td>
+ <MeasureDate
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "id": "2",
+ "metric": Object {
+ "key": "another",
+ "name": "another-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "another-value",
+ }
+ }
+ />
+
+ by_
+
+ <span
+ className="js-custom-measure-user"
+ >
+ user
+ </span>
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <ActionsDropdown>
+ <EditButton
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "id": "2",
+ "metric": Object {
+ "key": "another",
+ "name": "another-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "another-value",
+ }
+ }
+ onEdit={[MockFunction]}
+ />
+ <ActionsDropdownDivider />
+ <DeleteButton
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "id": "2",
+ "metric": Object {
+ "key": "another",
+ "name": "another-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "another-value",
+ }
+ }
+ onDelete={[MockFunction]}
+ />
+ </ActionsDropdown>
+ </td>
+ </tr>
+ <tr
+ data-metric="custom"
+ key="1"
+ >
+ <td
+ className="nowrap"
+ >
+ <div>
+ <span
+ className="js-custom-measure-metric-name"
+ >
+ custom-metric
+ </span>
+ </div>
+ <span
+ className="js-custom-measure-domain note"
+ />
+ </td>
+ <td
+ className="nowrap"
+ >
+ <strong
+ className="js-custom-measure-value"
+ >
+ custom-value
+ </strong>
+ </td>
+ <td>
+ <span
+ className="js-custom-measure-description"
+ >
+ my custom measure
+ </span>
+ </td>
+ <td>
+ <MeasureDate
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ }
+ }
+ />
+
+ by_
+
+ <span
+ className="js-custom-measure-user"
+ >
+ user
+ </span>
+ </td>
+ <td
+ className="thin nowrap"
+ >
+ <ActionsDropdown>
+ <EditButton
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ }
+ }
+ onEdit={[MockFunction]}
+ />
+ <ActionsDropdownDivider />
+ <DeleteButton
+ measure={
+ Object {
+ "createdAt": "2017-01-01",
+ "description": "my custom measure",
+ "id": "1",
+ "metric": Object {
+ "key": "custom",
+ "name": "custom-metric",
+ "type": "STRING",
+ },
+ "projectKey": "foo",
+ "user": Object {
+ "active": true,
+ "login": "user",
+ "name": "user",
+ },
+ "value": "custom-value",
+ }
+ }
+ onDelete={[MockFunction]}
+ />
+ </ActionsDropdown>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+`;
+
+exports[`should render no results 1`] = `
+<div
+ className="boxed-group boxed-group-inner"
+ id="custom-measures-list"
+>
+ <p>
+ no_results
+ </p>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/create-view.js b/server/sonar-web/src/main/js/apps/custom-measures/create-view.js
deleted file mode 100644
index 3d36638e362..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/create-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 CustomMeasure from './custom-measure';
-import FormView from './form-view';
-
-export default FormView.extend({
- sendRequest() {
- const that = this;
- const customMeasure = new CustomMeasure({
- metricId: this.$('#create-custom-measure-metric').val(),
- value: this.$('#create-custom-measure-value').val(),
- description: this.$('#create-custom-measure-description').val(),
- projectId: this.options.projectId
- });
- this.disableForm();
- return customMeasure
- .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/custom-measures/custom-measure.js b/server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js
deleted file mode 100644
index b6b36915cd0..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/custom-measure.js
+++ /dev/null
@@ -1,55 +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 { defaults, pick } from 'lodash';
-import Backbone from 'backbone';
-
-export default Backbone.Model.extend({
- idAttribute: 'id',
-
- urlRoot() {
- return window.baseUrl + '/api/custom_measures';
- },
-
- sync(method, model, options) {
- const opts = options || {};
- if (method === 'create') {
- defaults(opts, {
- url: this.urlRoot() + '/create',
- type: 'POST',
- data: pick(model.toJSON(), 'metricId', 'value', 'description', 'projectId')
- });
- }
- if (method === 'update') {
- defaults(opts, {
- url: this.urlRoot() + '/update',
- type: 'POST',
- data: pick(model.toJSON(), 'id', 'value', 'description')
- });
- }
- if (method === 'delete') {
- defaults(opts, {
- url: this.urlRoot() + '/delete',
- type: 'POST',
- data: { id: this.id }
- });
- }
- return Backbone.ajax(opts);
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js b/server/sonar-web/src/main/js/apps/custom-measures/custom-measures.js
deleted file mode 100644
index 98f31eb95f2..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/custom-measures.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 { uniq } from 'lodash';
-import Backbone from 'backbone';
-import CustomMeasure from './custom-measure';
-
-export default Backbone.Collection.extend({
- model: CustomMeasure,
-
- initialize(options) {
- this.projectId = options.projectId;
- },
-
- url() {
- return window.baseUrl + '/api/custom_measures/search';
- },
-
- parse(r) {
- this.total = r.total;
- this.p = r.p;
- this.ps = r.ps;
- return r.customMeasures;
- },
-
- fetch(options) {
- const opts = { data: {}, ...options };
- this.q = opts.data.q;
- opts.data.projectId = this.projectId;
- return Backbone.Collection.prototype.fetch.call(this, opts);
- },
-
- fetchMore() {
- const p = this.p + 1;
- return this.fetch({ add: true, remove: false, data: { p, ps: this.ps, q: this.q } });
- },
-
- refresh() {
- return this.fetch({ reset: true, data: { q: this.q } });
- },
-
- hasMore() {
- return this.total > this.p * this.ps;
- },
-
- getTakenMetrics() {
- const metrics = this.map(model => model.get('metric').id);
- return uniq(metrics);
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/delete-view.js b/server/sonar-web/src/main/js/apps/custom-measures/delete-view.js
deleted file mode 100644
index e910f280e55..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/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/custom-measures-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/custom-measures/form-view.js b/server/sonar-web/src/main/js/apps/custom-measures/form-view.js
deleted file mode 100644
index 35e2e05ca7f..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/form-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 Metrics from './metrics';
-import Template from './templates/custom-measures-form.hbs';
-import ModalForm from '../../components/common/modal-form';
-
-export default ModalForm.extend({
- template: Template,
-
- initialize() {
- this.metrics = new Metrics();
- this.listenTo(this.metrics, 'reset', this.render);
- this.metrics.fetch({ reset: true });
- },
-
- onRender() {
- ModalForm.prototype.onRender.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- this.$('#create-custom-measure-metric').select2({
- width: '250px',
- minimumResultsForSearch: 20
- });
- },
-
- onDestroy() {
- ModalForm.prototype.onDestroy.apply(this, arguments);
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onFormSubmit() {
- ModalForm.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- },
-
- getAvailableMetrics() {
- const takenMetrics = this.collection.getTakenMetrics();
- return this.metrics.toJSON().filter(metric => takenMetrics.indexOf(metric.id) === -1);
- },
-
- serializeData() {
- const metrics = this.getAvailableMetrics();
- const isNew = !this.model;
- return {
- ...ModalForm.prototype.serializeData.apply(this, arguments),
- metrics,
- canCreateMetric: !isNew || metrics.length > 0
- };
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/init.js b/server/sonar-web/src/main/js/apps/custom-measures/init.js
deleted file mode 100644
index a1dc8d23101..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/init.js
+++ /dev/null
@@ -1,67 +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 CustomMeasures from './custom-measures';
-import HeaderView from './header-view';
-import ListView from './list-view';
-import ListFooterView from './list-footer-view';
-
-const App = new Marionette.Application();
-const init = function(el, component) {
- // Layout
- this.layout = new Layout({ el });
- this.layout.render();
-
- // Collection
- this.customMeasures = new CustomMeasures({
- projectId: component.id
- });
-
- // Header View
- this.headerView = new HeaderView({
- collection: this.customMeasures,
- projectId: component.id
- });
- this.layout.headerRegion.show(this.headerView);
-
- // List View
- this.listView = new ListView({
- collection: this.customMeasures
- });
- this.layout.listRegion.show(this.listView);
-
- // List Footer View
- this.listFooterView = new ListFooterView({
- collection: this.customMeasures
- });
- this.layout.listFooterRegion.show(this.listFooterView);
-
- // Go!
- this.customMeasures.fetch();
-};
-
-App.on('start', options => {
- init.call(App, options.el, options.component);
-});
-
-export default function(el, component) {
- App.start({ el, component });
-}
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js b/server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js
deleted file mode 100644
index e8a43f89245..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/list-item-view.js
+++ /dev/null
@@ -1,63 +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/custom-measures-list-item.hbs';
-
-export default Marionette.ItemView.extend({
- tagName: 'tr',
- template: Template,
-
- events: {
- 'click .js-custom-measure-update': 'onUpdateClick',
- 'click .js-custom-measure-delete': 'onDeleteClick'
- },
-
- onRender() {
- this.$el.attr('data-id', this.model.id);
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body', placement: 'bottom' });
- },
-
- onDestroy() {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onUpdateClick(e) {
- e.preventDefault();
- this.updateCustomMeasure();
- },
-
- onDeleteClick(e) {
- e.preventDefault();
- this.deleteCustomMeasure();
- },
-
- updateCustomMeasure() {
- new UpdateView({
- model: this.model,
- collection: this.model.collection
- }).render();
- },
-
- deleteCustomMeasure() {
- new DeleteView({ model: this.model }).render();
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/list-view.js b/server/sonar-web/src/main/js/apps/custom-measures/list-view.js
deleted file mode 100644
index 211eb1428a9..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/list-view.js
+++ /dev/null
@@ -1,28 +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 ListItemView from './list-item-view';
-import Template from './templates/custom-measures-list.hbs';
-
-export default Marionette.CompositeView.extend({
- template: Template,
- childView: ListItemView,
- childViewContainer: 'tbody'
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/metric.js b/server/sonar-web/src/main/js/apps/custom-measures/metric.js
deleted file mode 100644
index 29fdb20beb2..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/metric.js
+++ /dev/null
@@ -1,55 +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 { defaults, pick } from 'lodash';
-import Backbone from 'backbone';
-
-export default Backbone.Model.extend({
- idAttribute: 'id',
-
- urlRoot() {
- return window.baseUrl + '/api/metrics';
- },
-
- sync(method, model, options) {
- const opts = options || {};
- if (method === 'create') {
- defaults(opts, {
- url: this.urlRoot() + '/create',
- type: 'POST',
- data: pick(model.toJSON(), 'key', 'name', 'description', 'domain', 'type')
- });
- }
- if (method === 'update') {
- defaults(opts, {
- url: this.urlRoot() + '/update',
- type: 'POST',
- data: pick(model.toJSON(), 'id', 'key', 'name', 'description', 'domain', 'type')
- });
- }
- if (method === 'delete') {
- defaults(opts, {
- url: this.urlRoot() + '/delete',
- type: 'POST',
- data: { ids: this.id }
- });
- }
- return Backbone.ajax(opts);
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/metrics.js b/server/sonar-web/src/main/js/apps/custom-measures/metrics.js
deleted file mode 100644
index 0fb8c5e00c7..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/metrics.js
+++ /dev/null
@@ -1,56 +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 Backbone from 'backbone';
-import Metric from './metric';
-
-export default Backbone.Collection.extend({
- model: Metric,
-
- url() {
- return window.baseUrl + '/api/metrics/search';
- },
-
- parse(r) {
- this.total = r.total;
- this.p = r.p;
- this.ps = r.ps;
- return r.metrics;
- },
-
- fetch(options) {
- const opts = { data: {}, ...options };
- this.q = opts.data.q;
- opts.data.isCustom = true;
- return Backbone.Collection.prototype.fetch.call(this, opts);
- },
-
- fetchMore() {
- const p = this.p + 1;
- return this.fetch({ add: true, remove: false, data: { p, ps: this.ps, q: this.q } });
- },
-
- refresh() {
- return this.fetch({ reset: true, data: { q: this.q } });
- },
-
- hasMore() {
- return this.total > this.p * this.ps;
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/routes.ts b/server/sonar-web/src/main/js/apps/custom-measures/routes.ts
index 5651d247031..ad7784f9906 100644
--- a/server/sonar-web/src/main/js/apps/custom-measures/routes.ts
+++ b/server/sonar-web/src/main/js/apps/custom-measures/routes.ts
@@ -22,9 +22,7 @@ import { RouterState, IndexRouteProps } from 'react-router';
const routes = [
{
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
- import('./components/CustomMeasuresAppContainer').then(i =>
- callback(null, { component: (i as any).default })
- );
+ import('./components/App').then(i => callback(null, { component: (i as any).default }));
}
}
];
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs
deleted file mode 100644
index c216329b3b1..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-delete.hbs
+++ /dev/null
@@ -1,13 +0,0 @@
-<form id="delete-custom-measure-form">
- <div class="modal-head">
- <h2>{{t 'custom_measures.delete_custom_measure'}}</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
- {{tp 'custom_measures.delete_custom_measure.confirmation' metric.name}}
- </div>
- <div class="modal-foot">
- <button id="delete-custom-measure-submit" class="button-red">{{t 'delete'}}</button>
- <a href="#" class="js-modal-close" id="delete-custom-measure-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs
deleted file mode 100644
index 48f42005b6d..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-form.hbs
+++ /dev/null
@@ -1,39 +0,0 @@
-<form id="create-custom-measure-form" autocomplete="off">
- <div class="modal-head">
- <h2>{{#if id}}{{t 'custom_measures.update_custom_measure'}}{{else}}{{t 'custom_measures.create_custom_measure'}}{{/if}}</h2>
- </div>
- <div class="modal-body">
- <div class="js-modal-messages"></div>
-
- {{#unless id}}
- {{#if canCreateMetric}}
- <div class="modal-field">
- <label for="create-custom-measure-metric">{{t 'custom_measures.metric'}}<em class="mandatory">*</em></label>
- <select id="create-custom-measure-metric" name="metric" required>
- {{#each metrics}}
- <option value="{{id}}" {{#eq id ../metric.id}}selected{{/eq}}>{{name}}</option>
- {{/each}}
- </select>
- </div>
- {{else}}
- <div class="alert alert-warning">{{t 'custom_measures.all_metrics_taken'}}</div>
- {{/if}}
- {{/unless}}
-
- <div class="modal-field">
- <label for="create-custom-measure-value">{{t 'value'}}<em class="mandatory">*</em></label>
- <input id="create-custom-measure-value" name="value" type="text" maxlength="200" required value="{{value}}">
- </div>
-
- <div class="modal-field">
- <label for="create-custom-measure-description">{{t 'description'}}</label>
- <textarea id="create-custom-measure-description" name="description">{{description}}</textarea>
- </div>
- </div>
- <div class="modal-foot">
- <button id="create-custom-measure-submit" {{#unless canCreateMetric}}disabled{{/unless}}>
- {{#if id}}{{t 'update_verb'}}{{else}}{{t 'create'}}{{/if}}
- </button>
- <a href="#" class="js-modal-close" id="create-custom-measure-cancel">{{t 'cancel'}}</a>
- </div>
-</form>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs
deleted file mode 100644
index 840706f77e2..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-header.hbs
+++ /dev/null
@@ -1,7 +0,0 @@
-<header class="page-header">
- <h1 class="page-title">{{t 'custom_measures.page'}}</h1>
- <div class="page-actions">
- <button id="custom-measures-create">{{t 'create'}}</button>
- </div>
- <p class="page-description">{{t 'custom_measures.page.description'}}</p>
-</header>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs
deleted file mode 100644
index 14efdfad1f4..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-layout.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-<div class="page page-limited">
- <div id="custom-measures-header"></div>
- <div id="custom-measures-list"></div>
- <div id="custom-measures-list-footer"></div>
-</div>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-footer.hbs
deleted file mode 100644
index 91d1dbcfee5..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-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="custom-measures-fetch-more" class="spacer-left" href="#">{{t 'show_more'}}</a>
- {{/if}}
-</footer>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs
deleted file mode 100644
index 0d40e51c4db..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list-item.hbs
+++ /dev/null
@@ -1,49 +0,0 @@
-<td class="nowrap">
- <div>
- <span class="js-custom-measure-metric-name">{{metric.name}}</span>
- {{#if pending}}
- <span class="js-custom-measure-pending badge badge-warning spacer-left"
- title="{{t 'custom_measures.pending_tooltip'}}"
- data-toggle="tooltip" data-placement="bottom">{{t 'custom_measures.pending'}}</span>
- {{/if}}
- </div>
- <span class="js-custom-measure-domain note">{{metric.domain}}</span>
-</td>
-
-<td class="nowrap">
- <strong class="js-custom-measure-value">{{formatMeasure value metric.type}}</strong>
-</td>
-
-<td class="">
- <span class="js-custom-measure-description">{{description}}</span>
-</td>
-
-<td class="">
- {{#if updatedAt }}
- {{t 'updated_on'}} <span class="js-custom-measure-created-at">{{d updatedAt}}</span>
- {{else}}
- {{#if createdAt }}
- {{t 'created_on'}} <span class="js-custom-measure-created-at">{{d createdAt}}</span>
- {{else}}
- {{t 'created'}}
- {{/if}}
- {{/if}}
- {{t 'by_'}} <span class="js-custom-measure-user">{{user.name}}</span>
-</td>
-
-<td class="thin 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-custom-measure-update" href="#">{{t 'update_verb'}}</a>
- </li>
- <li class="divider" />
- <li>
- <a class="js-custom-measure-delete text-danger" href="#">{{t 'delete'}}</a>
- </li>
- </ul>
- </div>
-</td>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list.hbs b/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list.hbs
deleted file mode 100644
index 97c513248eb..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/templates/custom-measures-list.hbs
+++ /dev/null
@@ -1,14 +0,0 @@
-<div class="boxed-group boxed-group-inner">
- <table class="data zebra">
- <thead>
- <tr>
- <th>{{t 'custom_measures.metric'}}</th>
- <th>{{t 'value'}}</th>
- <th>{{t 'description'}}</th>
- <th>{{t 'date'}}</th>
- <th>&nbsp;</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
-</div>
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/update-view.js b/server/sonar-web/src/main/js/apps/custom-measures/update-view.js
deleted file mode 100644
index 26716f5d25e..00000000000
--- a/server/sonar-web/src/main/js/apps/custom-measures/update-view.js
+++ /dev/null
@@ -1,46 +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({
- value: this.$('#create-custom-measure-value').val(),
- description: this.$('#create-custom-measure-description').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/custom-metrics/components/App.tsx b/server/sonar-web/src/main/js/apps/custom-metrics/components/App.tsx
index 09dffecab93..a616a996a85 100644
--- 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
@@ -45,7 +45,7 @@ interface State {
types?: string[];
}
-const PAGE_SIZE = 500;
+const PAGE_SIZE = 50;
export default class App extends React.PureComponent<Props, State> {
mounted: boolean;