* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { getJSON, post, postJSON } from 'sonar-ui-common/helpers/request'; | |||||
import { getJSON } from 'sonar-ui-common/helpers/request'; | |||||
import throwGlobalError from '../app/utils/throwGlobalError'; | import throwGlobalError from '../app/utils/throwGlobalError'; | ||||
import { BranchParameters } from '../types/branch-like'; | import { BranchParameters } from '../types/branch-like'; | ||||
import { | import { | ||||
metricKeys: metricKeys.join() | metricKeys: metricKeys.join() | ||||
}).then(r => r.measures); | }).then(r => r.measures); | ||||
} | } | ||||
export function getCustomMeasures(data: { | |||||
f?: string; | |||||
p?: number; | |||||
projectKey: string; | |||||
ps?: number; | |||||
}): Promise<{ customMeasures: T.CustomMeasure[]; paging: T.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<T.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); | |||||
} |
import { hasMessage, translate } from 'sonar-ui-common/helpers/l10n'; | import { hasMessage, translate } from 'sonar-ui-common/helpers/l10n'; | ||||
import { withAppState } from '../../../../components/hoc/withAppState'; | import { withAppState } from '../../../../components/hoc/withAppState'; | ||||
import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like'; | import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like'; | ||||
import { isSonarCloud } from '../../../../helpers/system'; | |||||
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls'; | import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls'; | ||||
import { BranchLike, BranchParameters } from '../../../../types/branch-like'; | import { BranchLike, BranchParameters } from '../../../../types/branch-like'; | ||||
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component'; | import { ComponentQualifier, isPortfolioLike } from '../../../../types/component'; | ||||
'/project/settings', | '/project/settings', | ||||
'/project/quality_profiles', | '/project/quality_profiles', | ||||
'/project/quality_gate', | '/project/quality_gate', | ||||
'/custom_measures', | |||||
'/project/links', | '/project/links', | ||||
'/project_roles', | '/project_roles', | ||||
'/project/history', | '/project/history', | ||||
...this.renderAdminExtensions(query, isApplication), | ...this.renderAdminExtensions(query, isApplication), | ||||
this.renderProfilesLink(query), | this.renderProfilesLink(query), | ||||
this.renderQualityGateLink(query), | this.renderQualityGateLink(query), | ||||
this.renderCustomMeasuresLink(query), | |||||
this.renderLinksLink(query), | this.renderLinksLink(query), | ||||
this.renderPermissionsLink(query), | this.renderPermissionsLink(query), | ||||
this.renderBackgroundTasksLink(query), | this.renderBackgroundTasksLink(query), | ||||
); | ); | ||||
}; | }; | ||||
renderCustomMeasuresLink = (query: Query) => { | |||||
if (isSonarCloud() || !this.getConfiguration().showManualMeasures) { | |||||
return null; | |||||
} | |||||
return ( | |||||
<li key="custom_measures"> | |||||
<Link activeClassName="active" to={{ pathname: '/custom_measures', query }}> | |||||
{translate('custom_measures.page')} | |||||
</Link> | |||||
</li> | |||||
); | |||||
}; | |||||
renderLinksLink = (query: Query) => { | renderLinksLink = (query: Query) => { | ||||
if (!this.getConfiguration().showLinks) { | if (!this.getConfiguration().showLinks) { | ||||
return null; | return null; |
import codeRoutes from '../../apps/code/routes'; | import codeRoutes from '../../apps/code/routes'; | ||||
import codingRulesRoutes from '../../apps/coding-rules/routes'; | import codingRulesRoutes from '../../apps/coding-rules/routes'; | ||||
import componentMeasuresRoutes from '../../apps/component-measures/routes'; | import componentMeasuresRoutes from '../../apps/component-measures/routes'; | ||||
import customMeasuresRoutes from '../../apps/custom-measures/routes'; | |||||
import customMetricsRoutes from '../../apps/custom-metrics/routes'; | import customMetricsRoutes from '../../apps/custom-metrics/routes'; | ||||
import documentationRoutes from '../../apps/documentation/routes'; | import documentationRoutes from '../../apps/documentation/routes'; | ||||
import groupsRoutes from '../../apps/groups/routes'; | import groupsRoutes from '../../apps/groups/routes'; | ||||
/> | /> | ||||
<RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} /> | <RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} /> | ||||
<Route component={lazyLoadComponent(() => import('../components/ProjectAdminContainer'))}> | <Route component={lazyLoadComponent(() => import('../components/ProjectAdminContainer'))}> | ||||
<RouteWithChildRoutes path="custom_measures" childRoutes={customMeasuresRoutes} /> | |||||
<Route | <Route | ||||
path="project/admin/extension/:pluginKey/:extensionKey" | path="project/admin/extension/:pluginKey/:extensionKey" | ||||
component={lazyLoadComponent(() => | component={lazyLoadComponent(() => |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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-async'; | |||||
import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import { | |||||
createCustomMeasure, | |||||
deleteCustomMeasure, | |||||
getCustomMeasures, | |||||
updateCustomMeasure | |||||
} from '../../../api/measures'; | |||||
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | |||||
import Header from './Header'; | |||||
import List from './List'; | |||||
interface Props { | |||||
component: { key: string }; | |||||
} | |||||
interface State { | |||||
loading: boolean; | |||||
measures?: T.CustomMeasure[]; | |||||
paging?: T.Paging; | |||||
} | |||||
const PAGE_SIZE = 50; | |||||
export default class App extends React.PureComponent<Props, State> { | |||||
mounted = false; | |||||
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 ( | |||||
<> | |||||
<Suggestions suggestions="custom_measures" /> | |||||
<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> | |||||
</> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { Button } from 'sonar-ui-common/components/controls/buttons'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import Form from './Form'; | |||||
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 = false; | |||||
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}> | |||||
{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} | |||||
/> | |||||
)} | |||||
</> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||||
import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal'; | |||||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||||
interface Props { | |||||
measure: T.CustomMeasure; | |||||
onClose: () => void; | |||||
onSubmit: () => Promise<void>; | |||||
} | |||||
export default function DeleteForm({ measure, onClose, onSubmit }: Props) { | |||||
const header = translate('custom_measures.delete_custom_measure'); | |||||
return ( | |||||
<SimpleModal header={header} onClose={onClose} onSubmit={onSubmit}> | |||||
{({ onCloseClick, onFormSubmit, submitting }) => ( | |||||
<form onSubmit={onFormSubmit}> | |||||
<header className="modal-head"> | |||||
<h2>{header}</h2> | |||||
</header> | |||||
<div className="modal-body"> | |||||
{translateWithParameters( | |||||
'custom_measures.delete_custom_measure.confirmation', | |||||
measure.metric.name | |||||
)} | |||||
</div> | |||||
<footer className="modal-foot"> | |||||
<DeferredSpinner className="spacer-right" loading={submitting} /> | |||||
<SubmitButton className="button-red" disabled={submitting}> | |||||
{translate('delete')} | |||||
</SubmitButton> | |||||
<ResetButtonLink disabled={submitting} onClick={onCloseClick}> | |||||
{translate('cancel')} | |||||
</ResetButtonLink> | |||||
</footer> | |||||
</form> | |||||
)} | |||||
</SimpleModal> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; | |||||
import Select from 'sonar-ui-common/components/controls/Select'; | |||||
import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal'; | |||||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||||
import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import { getAllMetrics } from '../../../api/metrics'; | |||||
interface Props { | |||||
confirmButtonText: string; | |||||
header: string; | |||||
measure?: T.CustomMeasure; | |||||
onClose: () => void; | |||||
onSubmit: (data: { description: string; metricKey: string; value: string }) => Promise<void>; | |||||
skipMetrics?: string[]; | |||||
} | |||||
interface State { | |||||
description: string; | |||||
loading: boolean; | |||||
metricKey?: string; | |||||
metrics?: T.Metric[]; | |||||
value: string; | |||||
} | |||||
export default class Form extends React.PureComponent<Props, State> { | |||||
mounted = false; | |||||
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 <Alert variant="warning">{translate('custom_measures.all_metrics_taken')}</Alert>; | |||||
} | |||||
return ( | |||||
<div className="modal-field"> | |||||
<label htmlFor="create-custom-measure-metric"> | |||||
{translate('custom_measures.metric')} | |||||
<MandatoryFieldMarker /> | |||||
</label> | |||||
{this.state.loading ? ( | |||||
<i className="spinner" /> | |||||
) : ( | |||||
<Select | |||||
autoFocus={true} | |||||
clearable={false} | |||||
id="create-custom-measure-metric" | |||||
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} | |||||
size="small"> | |||||
{({ 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')} | |||||
<MandatoryFieldMarker /> | |||||
</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} /> | |||||
<SubmitButton | |||||
disabled={forbidSubmitting || submitting} | |||||
id="create-custom-measure-submit"> | |||||
{this.props.confirmButtonText} | |||||
</SubmitButton> | |||||
<ResetButtonLink | |||||
disabled={submitting} | |||||
id="create-custom-measure-cancel" | |||||
onClick={onCloseClick}> | |||||
{translate('cancel')} | |||||
</ResetButtonLink> | |||||
</footer> | |||||
</form> | |||||
)} | |||||
</SimpleModal> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||||
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import CreateButton from './CreateButton'; | |||||
interface Props { | |||||
loading: boolean; | |||||
onCreate: (data: { description: string; metricKey: string; value: string }) => Promise<void>; | |||||
skipMetrics: string[] | undefined; | |||||
} | |||||
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> | |||||
<div className="page-description"> | |||||
<Alert display="inline" variant="error"> | |||||
{translate('custom_measures.deprecated')} | |||||
</Alert> | |||||
<p>{translate('custom_measures.page.description')}</p> | |||||
</div> | |||||
</header> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 ActionsDropdown, { | |||||
ActionsDropdownDivider, | |||||
ActionsDropdownItem | |||||
} from 'sonar-ui-common/components/controls/ActionsDropdown'; | |||||
import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||||
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; | |||||
import { formatMeasure } from 'sonar-ui-common/helpers/measures'; | |||||
import { isUserActive } from '../../../helpers/users'; | |||||
import DeleteForm from './DeleteForm'; | |||||
import Form from './Form'; | |||||
import MeasureDate from './MeasureDate'; | |||||
interface Props { | |||||
measure: T.CustomMeasure; | |||||
onDelete: (measureId: string) => Promise<void>; | |||||
onEdit: (data: { description: string; id: string; value: string }) => Promise<void>; | |||||
} | |||||
interface State { | |||||
deleteForm: boolean; | |||||
editForm: boolean; | |||||
} | |||||
export default class Item extends React.PureComponent<Props, State> { | |||||
mounted = false; | |||||
state: State = { | |||||
deleteForm: false, | |||||
editForm: false | |||||
}; | |||||
componentDidMount() { | |||||
this.mounted = true; | |||||
} | |||||
componentWillUnmount() { | |||||
this.mounted = false; | |||||
} | |||||
handleEditClick = () => { | |||||
this.setState({ editForm: true }); | |||||
}; | |||||
handleDeleteClick = () => { | |||||
this.setState({ deleteForm: true }); | |||||
}; | |||||
closeEditForm = () => { | |||||
if (this.mounted) { | |||||
this.setState({ editForm: false }); | |||||
} | |||||
}; | |||||
closeDeleteForm = () => { | |||||
if (this.mounted) { | |||||
this.setState({ deleteForm: false }); | |||||
} | |||||
}; | |||||
handleEditFormSubmit = (data: { description: string; value: string }) => { | |||||
return this.props.onEdit({ id: this.props.measure.id, ...data }); | |||||
}; | |||||
handleDeleteFormSubmit = () => { | |||||
return this.props.onDelete(this.props.measure.id); | |||||
}; | |||||
render() { | |||||
const { measure } = this.props; | |||||
const userName = measure.user.name || measure.user.login; | |||||
return ( | |||||
<tr data-metric={measure.metric.key}> | |||||
<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"> | |||||
{isUserActive(measure.user) | |||||
? userName | |||||
: translateWithParameters('user.x_deleted', userName)} | |||||
</span> | |||||
</td> | |||||
<td className="thin nowrap"> | |||||
<ActionsDropdown> | |||||
<ActionsDropdownItem | |||||
className="js-custom-measure-update" | |||||
onClick={this.handleEditClick}> | |||||
{translate('update_verb')} | |||||
</ActionsDropdownItem> | |||||
<ActionsDropdownDivider /> | |||||
<ActionsDropdownItem | |||||
className="js-custom-measure-delete" | |||||
destructive={true} | |||||
onClick={this.handleDeleteClick}> | |||||
{translate('delete')} | |||||
</ActionsDropdownItem> | |||||
</ActionsDropdown> | |||||
</td> | |||||
{this.state.editForm && ( | |||||
<Form | |||||
confirmButtonText={translate('update_verb')} | |||||
header={translate('custom_measures.update_custom_measure')} | |||||
measure={this.props.measure} | |||||
onClose={this.closeEditForm} | |||||
onSubmit={this.handleEditFormSubmit} | |||||
/> | |||||
)} | |||||
{this.state.deleteForm && ( | |||||
<DeleteForm | |||||
measure={this.props.measure} | |||||
onClose={this.closeDeleteForm} | |||||
onSubmit={this.handleDeleteFormSubmit} | |||||
/> | |||||
)} | |||||
</tr> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { sortBy } from 'lodash'; | |||||
import * as React from 'react'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
import Item from './Item'; | |||||
interface Props { | |||||
measures: T.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 => ( | |||||
<Item key={measure.id} measure={measure} onDelete={onDelete} onEdit={onEdit} /> | |||||
))} | |||||
</tbody> | |||||
</table> | |||||
) : ( | |||||
<p>{translate('no_results')}</p> | |||||
)} | |||||
</div> | |||||
); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; | |||||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||||
interface Props { | |||||
measure: T.CustomMeasure; | |||||
} | |||||
export default function MeasureDate({ measure }: Props) { | |||||
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')}</>; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||||
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>(<App component={{ key: 'foo' }} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
await waitAndUpdate(wrapper); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
// create | |||||
wrapper.find('Header').prop<Function>('onCreate')({ | |||||
description: 'description', | |||||
metricKey: 'metricKey', | |||||
value: 'value' | |||||
}); | |||||
await waitAndUpdate(wrapper); | |||||
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 waitAndUpdate(wrapper); | |||||
expect(wrapper.state().measures).toMatchSnapshot(); | |||||
expect(wrapper.state().paging!.total).toBe(2); | |||||
// delete | |||||
wrapper.find('List').prop<Function>('onDelete')('2'); | |||||
await waitAndUpdate(wrapper); | |||||
expect(wrapper.state().measures).toMatchSnapshot(); | |||||
expect(wrapper.state().paging!.total).toBe(1); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||||
import CreateButton from '../CreateButton'; | |||||
it('should create new custom measure', () => { | |||||
const onCreate = jest.fn(() => Promise.resolve()); | |||||
const wrapper = shallow(<CreateButton onCreate={onCreate} skipMetrics={[]} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
click(wrapper.find('#custom-measures-create')); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
wrapper.find('Form').prop<Function>('onSubmit')({ | |||||
description: 'description', | |||||
metricKey: 'metricKey', | |||||
value: 'value' | |||||
}); | |||||
expect(onCreate).toBeCalledWith({ | |||||
description: 'description', | |||||
metricKey: 'metricKey', | |||||
value: 'value' | |||||
}); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import DeleteForm from '../DeleteForm'; | |||||
it('should render', () => { | |||||
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' | |||||
}; | |||||
expect( | |||||
shallow(<DeleteForm measure={measure} onClose={jest.fn()} onSubmit={jest.fn()} />).dive() | |||||
).toMatchSnapshot(); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import { change, click, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; | |||||
import Form from '../Form'; | |||||
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 waitAndUpdate(wrapper); | |||||
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('ResetButtonLink')); | |||||
expect(onClose).toBeCalled(); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import Header from '../Header'; | |||||
it('should create new custom measure', () => { | |||||
const onCreate = jest.fn(() => Promise.resolve()); | |||||
const wrapper = shallow(<Header loading={false} onCreate={onCreate} skipMetrics={[]} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
wrapper.find('CreateButton').prop<Function>('onCreate')({ | |||||
description: 'bla', | |||||
metricKey: 'custom-metric', | |||||
name: 'Foo' | |||||
}); | |||||
expect(onCreate).toBeCalledWith({ description: 'bla', metricKey: 'custom-metric', name: 'Foo' }); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
import { click } from 'sonar-ui-common/helpers/testUtils'; | |||||
import Item from '../Item'; | |||||
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' | |||||
}; | |||||
it('should render', () => { | |||||
expect(shallowRender()).toMatchSnapshot(); | |||||
}); | |||||
it('should edit metric', () => { | |||||
const onEdit = jest.fn(); | |||||
const wrapper = shallowRender({ onEdit }); | |||||
click(wrapper.find('.js-custom-measure-update')); | |||||
wrapper.update(); | |||||
wrapper.find('Form').prop<Function>('onSubmit')({ | |||||
...measure, | |||||
description: 'new-description', | |||||
value: 'new-value' | |||||
}); | |||||
expect(onEdit).toBeCalledWith({ ...measure, description: 'new-description', value: 'new-value' }); | |||||
}); | |||||
it('should delete custom measure', () => { | |||||
const onDelete = jest.fn(); | |||||
const wrapper = shallowRender({ onDelete }); | |||||
click(wrapper.find('.js-custom-measure-delete')); | |||||
wrapper.update(); | |||||
wrapper.find('DeleteForm').prop<Function>('onSubmit')(); | |||||
expect(onDelete).toBeCalledWith('1'); | |||||
}); | |||||
it('should render correctly for deleted user', () => { | |||||
expect( | |||||
shallowRender({ measure: { ...measure, user: { active: false, login: 'user' } } }) | |||||
).toMatchSnapshot(); | |||||
}); | |||||
function shallowRender(props: Partial<Item['props']> = {}) { | |||||
return shallow(<Item measure={measure} onDelete={jest.fn()} onEdit={jest.fn()} {...props} />); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU Lesser General Public | |||||
* License as published by the Free Software Foundation; either | |||||
* version 3 of the License, or (at your option) any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||||
* Lesser General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU Lesser General Public License | |||||
* along with this program; if not, write to the Free Software Foundation, | |||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||||
*/ | |||||
import { shallow } from 'enzyme'; | |||||
import * as React from 'react'; | |||||
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(); | |||||
}); |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should work 1`] = ` | |||||
<Fragment> | |||||
<Suggestions | |||||
suggestions="custom_measures" | |||||
/> | |||||
<Helmet | |||||
defer={true} | |||||
encodeSpecialCharacters={true} | |||||
title="custom_measures.page" | |||||
/> | |||||
<div | |||||
className="page page-limited" | |||||
> | |||||
<Header | |||||
loading={true} | |||||
onCreate={[Function]} | |||||
/> | |||||
</div> | |||||
</Fragment> | |||||
`; | |||||
exports[`should work 2`] = ` | |||||
<Fragment> | |||||
<Suggestions | |||||
suggestions="custom_measures" | |||||
/> | |||||
<Helmet | |||||
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> | |||||
</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", | |||||
}, | |||||
] | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should create new custom measure 1`] = ` | |||||
<Fragment> | |||||
<Button | |||||
id="custom-measures-create" | |||||
onClick={[Function]} | |||||
> | |||||
create | |||||
</Button> | |||||
</Fragment> | |||||
`; | |||||
exports[`should create new custom measure 2`] = ` | |||||
<Fragment> | |||||
<Button | |||||
id="custom-measures-create" | |||||
onClick={[Function]} | |||||
> | |||||
create | |||||
</Button> | |||||
<Form | |||||
confirmButtonText="create" | |||||
header="custom_measures.create_custom_measure" | |||||
onClose={[Function]} | |||||
onSubmit={[MockFunction]} | |||||
skipMetrics={Array []} | |||||
/> | |||||
</Fragment> | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render 1`] = ` | |||||
<Modal | |||||
contentLabel="custom_measures.delete_custom_measure" | |||||
onRequestClose={[MockFunction]} | |||||
> | |||||
<form | |||||
onSubmit={[Function]} | |||||
> | |||||
<header | |||||
className="modal-head" | |||||
> | |||||
<h2> | |||||
custom_measures.delete_custom_measure | |||||
</h2> | |||||
</header> | |||||
<div | |||||
className="modal-body" | |||||
> | |||||
custom_measures.delete_custom_measure.confirmation.custom-metric | |||||
</div> | |||||
<footer | |||||
className="modal-foot" | |||||
> | |||||
<DeferredSpinner | |||||
className="spacer-right" | |||||
loading={false} | |||||
/> | |||||
<SubmitButton | |||||
className="button-red" | |||||
disabled={false} | |||||
> | |||||
delete | |||||
</SubmitButton> | |||||
<ResetButtonLink | |||||
disabled={false} | |||||
onClick={[Function]} | |||||
> | |||||
cancel | |||||
</ResetButtonLink> | |||||
</footer> | |||||
</form> | |||||
</Modal> | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render form 1`] = ` | |||||
<Modal | |||||
contentLabel="header" | |||||
onRequestClose={[MockFunction]} | |||||
size="small" | |||||
> | |||||
<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 | |||||
<MandatoryFieldMarker /> | |||||
</label> | |||||
<i | |||||
className="spinner" | |||||
/> | |||||
</div> | |||||
<div | |||||
className="modal-field" | |||||
> | |||||
<label | |||||
htmlFor="create-custom-measure-value" | |||||
> | |||||
value | |||||
<MandatoryFieldMarker /> | |||||
</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} | |||||
/> | |||||
<SubmitButton | |||||
disabled={true} | |||||
id="create-custom-measure-submit" | |||||
> | |||||
confirmButtonText | |||||
</SubmitButton> | |||||
<ResetButtonLink | |||||
disabled={false} | |||||
id="create-custom-measure-cancel" | |||||
onClick={[Function]} | |||||
> | |||||
cancel | |||||
</ResetButtonLink> | |||||
</footer> | |||||
</form> | |||||
</Modal> | |||||
`; | |||||
exports[`should render form 2`] = ` | |||||
<Modal | |||||
contentLabel="header" | |||||
onRequestClose={[MockFunction]} | |||||
size="small" | |||||
> | |||||
<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 | |||||
<MandatoryFieldMarker /> | |||||
</label> | |||||
<Select | |||||
autoFocus={true} | |||||
clearable={false} | |||||
id="create-custom-measure-metric" | |||||
onChange={[Function]} | |||||
options={ | |||||
Array [ | |||||
Object { | |||||
"label": "Custom Metric", | |||||
"value": "custom-metric", | |||||
}, | |||||
] | |||||
} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="modal-field" | |||||
> | |||||
<label | |||||
htmlFor="create-custom-measure-value" | |||||
> | |||||
value | |||||
<MandatoryFieldMarker /> | |||||
</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} | |||||
/> | |||||
<SubmitButton | |||||
disabled={false} | |||||
id="create-custom-measure-submit" | |||||
> | |||||
confirmButtonText | |||||
</SubmitButton> | |||||
<ResetButtonLink | |||||
disabled={false} | |||||
id="create-custom-measure-cancel" | |||||
onClick={[Function]} | |||||
> | |||||
cancel | |||||
</ResetButtonLink> | |||||
</footer> | |||||
</form> | |||||
</Modal> | |||||
`; |
// 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} | |||||
/> | |||||
<div | |||||
className="page-actions" | |||||
> | |||||
<CreateButton | |||||
onCreate={[MockFunction]} | |||||
skipMetrics={Array []} | |||||
/> | |||||
</div> | |||||
<div | |||||
className="page-description" | |||||
> | |||||
<Alert | |||||
display="inline" | |||||
variant="error" | |||||
> | |||||
custom_measures.deprecated | |||||
</Alert> | |||||
<p> | |||||
custom_measures.page.description | |||||
</p> | |||||
</div> | |||||
</header> | |||||
`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render 1`] = ` | |||||
<tr | |||||
data-metric="custom" | |||||
> | |||||
<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> | |||||
<ActionsDropdownItem | |||||
className="js-custom-measure-update" | |||||
onClick={[Function]} | |||||
> | |||||
update_verb | |||||
</ActionsDropdownItem> | |||||
<ActionsDropdownDivider /> | |||||
<ActionsDropdownItem | |||||
className="js-custom-measure-delete" | |||||
destructive={true} | |||||
onClick={[Function]} | |||||
> | |||||
delete | |||||
</ActionsDropdownItem> | |||||
</ActionsDropdown> | |||||
</td> | |||||
</tr> | |||||
`; | |||||
exports[`should render correctly for deleted user 1`] = ` | |||||
<tr | |||||
data-metric="custom" | |||||
> | |||||
<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": false, | |||||
"login": "user", | |||||
}, | |||||
"value": "custom-value", | |||||
} | |||||
} | |||||
/> | |||||
by_ | |||||
<span | |||||
className="js-custom-measure-user" | |||||
> | |||||
user.x_deleted.user | |||||
</span> | |||||
</td> | |||||
<td | |||||
className="thin nowrap" | |||||
> | |||||
<ActionsDropdown> | |||||
<ActionsDropdownItem | |||||
className="js-custom-measure-update" | |||||
onClick={[Function]} | |||||
> | |||||
update_verb | |||||
</ActionsDropdownItem> | |||||
<ActionsDropdownDivider /> | |||||
<ActionsDropdownItem | |||||
className="js-custom-measure-delete" | |||||
destructive={true} | |||||
onClick={[Function]} | |||||
> | |||||
delete | |||||
</ActionsDropdownItem> | |||||
</ActionsDropdown> | |||||
</td> | |||||
</tr> | |||||
`; |
// 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> | |||||
<Item | |||||
key="2" | |||||
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]} | |||||
onEdit={[MockFunction]} | |||||
/> | |||||
<Item | |||||
key="1" | |||||
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]} | |||||
onEdit={[MockFunction]} | |||||
/> | |||||
</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> | |||||
`; |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2021 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 { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; | |||||
const routes = [ | |||||
{ | |||||
indexRoute: { component: lazyLoadComponent(() => import('./components/App')) } | |||||
} | |||||
]; | |||||
export default routes; |
showBackgroundTasks?: boolean; | showBackgroundTasks?: boolean; | ||||
showHistory?: boolean; | showHistory?: boolean; | ||||
showLinks?: boolean; | showLinks?: boolean; | ||||
showManualMeasures?: boolean; | |||||
showQualityGates?: boolean; | showQualityGates?: boolean; | ||||
showQualityProfiles?: boolean; | showQualityProfiles?: boolean; | ||||
showPermissions?: boolean; | showPermissions?: boolean; |
{ | |||||
"metrics": [ | |||||
{ | |||||
"id": "AU-Tpxb--iU5OvuD2FLy", | |||||
"key": "team_size", | |||||
"name": "Team size", | |||||
"description": "Number of people in the team", | |||||
"domain": "Management", | |||||
"type": "INT", | |||||
"direction": 0, | |||||
"qualitative": false, | |||||
"hidden": false, | |||||
"custom": true | |||||
}, | |||||
{ | |||||
"id": "AU-Tpxb--iU5OvuD3FLz", | |||||
"key": "uncovered_lines", | |||||
"name": "Uncovered lines", | |||||
"description": "Uncovered lines", | |||||
"domain": "Tests", | |||||
"type": "INT", | |||||
"direction": 1, | |||||
"qualitative": true, | |||||
"hidden": false, | |||||
"custom": false | |||||
} | |||||
] | |||||
} |
{ | |||||
"customMeasures": [ | |||||
{ | |||||
"description": "New arrivals", | |||||
"metric": { | |||||
"key": "team_size", | |||||
"name": "Team Size", | |||||
"domain": "Tests", | |||||
"type": "INT" | |||||
}, | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"pending": true, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Stan Smith" | |||||
}, | |||||
"value": "42" | |||||
}, | |||||
{ | |||||
"description": "New funds", | |||||
"metric": { | |||||
"key": "burned_budget", | |||||
"name": "Burned Budget", | |||||
"domain": "Activity", | |||||
"type": "INT" | |||||
}, | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"pending": false, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Stan Smith" | |||||
}, | |||||
"value": "1000000" | |||||
}, | |||||
{ | |||||
"description": "Great coverage", | |||||
"metric": { | |||||
"key": "uncovered_lines", | |||||
"name": "Uncovered lines", | |||||
"domain": "Code Coverage", | |||||
"type": "INT" | |||||
}, | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"pending": false, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Stan Smith" | |||||
}, | |||||
"value": "1" | |||||
} | |||||
], | |||||
"p": 1, | |||||
"ps": 100, | |||||
"total": 3 | |||||
} |
{ | |||||
"metrics": [ | |||||
{ | |||||
"key": "custom-key-1", | |||||
"type": "INT", | |||||
"name": "custom-name-1", | |||||
"description": "custom-description-1", | |||||
"domain": "custom-domain-1", | |||||
"direction": 1, | |||||
"qualitative": false, | |||||
"hidden": false, | |||||
"custom": true | |||||
}, | |||||
{ | |||||
"key": "custom-key-2", | |||||
"type": "INT", | |||||
"name": "custom-name-2", | |||||
"description": "custom-description-2", | |||||
"domain": "custom-domain-2", | |||||
"direction": -1, | |||||
"qualitative": true, | |||||
"hidden": true, | |||||
"custom": true | |||||
}, | |||||
{ | |||||
"key": "custom-key-3", | |||||
"type": "INT", | |||||
"name": "custom-name-3", | |||||
"description": "custom-description-3", | |||||
"domain": "custom-domain-3", | |||||
"direction": 0, | |||||
"qualitative": false, | |||||
"hidden": false, | |||||
"custom": true | |||||
} | |||||
] | |||||
} |
{ | |||||
"customMeasures": [ | |||||
{ | |||||
"description": "description-1", | |||||
"metric": { | |||||
"key": "metric-key-1", | |||||
"name": "metric-key-1-name", | |||||
"domain": "metric-key-1-domain", | |||||
"type": "STRING" | |||||
}, | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"pending":true, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Login" | |||||
}, | |||||
"value": "text-value-1" | |||||
}, | |||||
{ | |||||
"description": "description-2", | |||||
"metric": { | |||||
"key": "metric-key-2", | |||||
"name": "metric-key-2-name", | |||||
"domain": "metric-key-2-domain", | |||||
"type": "STRING" | |||||
}, | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"pending":true, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Login" | |||||
}, | |||||
"value": "text-value-2" | |||||
}, | |||||
{ | |||||
"description": "description-3", | |||||
"metric": { | |||||
"key": "metric-key-3", | |||||
"name": "metric-key-3-name", | |||||
"domain": "metric-key-3-domain", | |||||
"type": "STRING" | |||||
}, | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"pending":true, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Login" | |||||
}, | |||||
"value": "text-value-3" | |||||
} | |||||
], | |||||
"p": 1, | |||||
"ps": 100, | |||||
"total": 3 | |||||
} |
{ | |||||
"customMeasures": [ | |||||
], | |||||
"p": 1, | |||||
"ps": 100, | |||||
"total": 0 | |||||
} |
{ | |||||
"projectId": "project-uuid", | |||||
"projectKey": "project-key", | |||||
"metric": { | |||||
"key": "metric-key", | |||||
"type": "STRING" | |||||
}, | |||||
"value": "new-text-measure-value", | |||||
"description": "new-custom-measure-description", | |||||
"pending":true, | |||||
"user": { | |||||
"active": true, | |||||
"email": "login@login.com", | |||||
"login": "login", | |||||
"name": "Login" | |||||
} | |||||
} |
} | } | ||||
] | ] | ||||
}, | }, | ||||
{ | |||||
"path": "api/custom_measures", | |||||
"since": "5.2", | |||||
"description": "Manage custom measures for a project. See also api/metrics.", | |||||
"actions": [ | |||||
{ | |||||
"key": "create", | |||||
"description": "Create a custom measure.<br /> The project id or the project key must be provided (only project and module custom measures can be created). The metric id or the metric key must be provided.<br/>Requires 'Administer System' permission or 'Administer' permission on the project.", | |||||
"since": "5.2", | |||||
"internal": false, | |||||
"post": true, | |||||
"hasResponseExample": false, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "description", | |||||
"description": "Description", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "Team size growing." | |||||
}, | |||||
{ | |||||
"key": "metricId", | |||||
"description": "Metric id", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "16" | |||||
}, | |||||
{ | |||||
"key": "metricKey", | |||||
"description": "Metric key", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "ncloc" | |||||
}, | |||||
{ | |||||
"key": "projectId", | |||||
"description": "Project id", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "ce4c03d6-430f-40a9-b777-ad877c00aa4d" | |||||
}, | |||||
{ | |||||
"key": "projectKey", | |||||
"description": "Project key", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "my_project" | |||||
}, | |||||
{ | |||||
"key": "value", | |||||
"description": "Measure value. Value type depends on metric type:<ul><li>INT - type: integer</li><li>FLOAT - type: double</li><li>PERCENT - type: double</li><li>BOOL - the possible values are true or false</li><li>STRING - type: string</li><li>MILLISEC - type: integer</li><li>DATA - type: string</li><li>LEVEL - the possible values are OK, WARN, ERROR</li><li>DISTRIB - type: string</li><li>RATING - type: double</li><li>WORK_DUR - long representing the number of minutes</li></ul>", | |||||
"required": true, | |||||
"internal": false, | |||||
"exampleValue": "47" | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"key": "delete", | |||||
"description": "Delete a custom measure.<br /> Requires 'Administer System' permission or 'Administer' permission on the project.", | |||||
"since": "5.2", | |||||
"internal": false, | |||||
"post": true, | |||||
"hasResponseExample": false, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "id", | |||||
"description": "Id", | |||||
"required": true, | |||||
"internal": false, | |||||
"exampleValue": "24" | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"key": "metrics", | |||||
"description": "List all custom metrics for which no custom measure already exists on a given project.<br /> The project id or project key must be provided.<br />Requires 'Administer System' permission or 'Administer' permission on the project.", | |||||
"since": "5.2", | |||||
"internal": true, | |||||
"post": false, | |||||
"hasResponseExample": true, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "projectId", | |||||
"description": "Project id", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "ce4c03d6-430f-40a9-b777-ad877c00aa4d" | |||||
}, | |||||
{ | |||||
"key": "projectKey", | |||||
"description": "Project key", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "my_project" | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"key": "search", | |||||
"description": "List custom measures. The project id or project key must be provided.<br />Requires 'Administer System' permission or 'Administer' permission on the project.", | |||||
"since": "5.2", | |||||
"internal": false, | |||||
"post": false, | |||||
"hasResponseExample": true, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "f", | |||||
"description": "Comma-separated list of the fields to be returned in response. All the fields are returned by default.", | |||||
"required": false, | |||||
"internal": false, | |||||
"possibleValues": [ | |||||
"projectId", | |||||
"projectKey", | |||||
"value", | |||||
"description", | |||||
"metric", | |||||
"createdAt", | |||||
"updatedAt", | |||||
"user", | |||||
"pending" | |||||
] | |||||
}, | |||||
{ | |||||
"key": "p", | |||||
"description": "1-based page number", | |||||
"required": false, | |||||
"internal": false, | |||||
"defaultValue": "1", | |||||
"exampleValue": "42", | |||||
"deprecatedKey": "pageIndex", | |||||
"deprecatedKeySince": "5.2" | |||||
}, | |||||
{ | |||||
"key": "projectId", | |||||
"description": "Project id", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "ce4c03d6-430f-40a9-b777-ad877c00aa4d" | |||||
}, | |||||
{ | |||||
"key": "projectKey", | |||||
"description": "Project key", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "my_project" | |||||
}, | |||||
{ | |||||
"key": "ps", | |||||
"description": "Page size. Must be greater than 0 and less than 500", | |||||
"required": false, | |||||
"internal": false, | |||||
"defaultValue": "100", | |||||
"exampleValue": "20", | |||||
"deprecatedKey": "pageSize", | |||||
"deprecatedKeySince": "5.2", | |||||
"maximumValue": 500 | |||||
} | |||||
] | |||||
}, | |||||
{ | |||||
"key": "update", | |||||
"description": "Update a custom measure. Value and/or description must be provided<br />Requires 'Administer System' permission or 'Administer' permission on the project.", | |||||
"since": "5.2", | |||||
"internal": false, | |||||
"post": true, | |||||
"hasResponseExample": false, | |||||
"changelog": [], | |||||
"params": [ | |||||
{ | |||||
"key": "description", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "Team size growing." | |||||
}, | |||||
{ | |||||
"key": "id", | |||||
"description": "id", | |||||
"required": true, | |||||
"internal": false, | |||||
"exampleValue": "42" | |||||
}, | |||||
{ | |||||
"key": "value", | |||||
"description": "Measure value. Value type depends on metric type:<ul><li>INT - type: integer</li><li>FLOAT - type: double</li><li>PERCENT - type: double</li><li>BOOL - the possible values are true or false</li><li>STRING - type: string</li><li>MILLISEC - type: integer</li><li>DATA - type: string</li><li>LEVEL - the possible values are OK, WARN, ERROR</li><li>DISTRIB - type: string</li><li>RATING - type: double</li><li>WORK_DUR - long representing the number of minutes</li></ul>", | |||||
"required": false, | |||||
"internal": false, | |||||
"exampleValue": "true" | |||||
} | |||||
] | |||||
} | |||||
] | |||||
}, | |||||
{ | { | ||||
"path": "api/duplications", | "path": "api/duplications", | ||||
"since": "4.4", | "since": "4.4", |