diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-02-09 16:11:11 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-09 16:11:11 +0100 |
commit | 8f8e96192f36aa13496ebfe7bbaf333de8a92e35 (patch) | |
tree | 8a233650211ec26edabf027c49cf0ff5cc3878e7 /server | |
parent | 2e06a921076f73f901b333eb7fa236f7971ff3ac (diff) | |
download | sonarqube-8f8e96192f36aa13496ebfe7bbaf333de8a92e35.tar.gz sonarqube-8f8e96192f36aa13496ebfe7bbaf333de8a92e35.zip |
rewrite custom metrics app in react (#3036)
Diffstat (limited to 'server')
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()} /> |