diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-02-13 09:19:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-13 09:19:41 +0100 |
commit | ab801b91e6f61f587a3e8f059b7c0953814617fa (patch) | |
tree | def42912ababd4a436c88e248da0dbd1901ab257 | |
parent | 40f9260e999f65532a82096a7995f14fad8f695f (diff) | |
download | sonarqube-ab801b91e6f61f587a3e8f059b7c0953814617fa.tar.gz sonarqube-ab801b91e6f61f587a3e8f059b7c0953814617fa.zip |
rewrite custom measures app in react (#3052)
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> </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; |