瀏覽代碼

SONAR-10762 Drop Custom Measures UI

tags/9.1.0.47736
Zipeng WU 2 年之前
父節點
當前提交
71fa583b70
共有 34 個檔案被更改,包括 1 行新增2465 行删除
  1. 1
    34
      server/sonar-web/src/main/js/api/measures.ts
  2. 0
    16
      server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
  3. 0
    2
      server/sonar-web/src/main/js/app/utils/startReactApp.tsx
  4. 0
    159
      server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx
  5. 0
    74
      server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx
  6. 0
    63
      server/sonar-web/src/main/js/apps/custom-measures/components/DeleteForm.tsx
  7. 0
    208
      server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx
  8. 0
    48
      server/sonar-web/src/main/js/apps/custom-measures/components/Header.tsx
  9. 0
    163
      server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx
  10. 0
    56
      server/sonar-web/src/main/js/apps/custom-measures/components/List.tsx
  11. 0
    50
      server/sonar-web/src/main/js/apps/custom-measures/components/MeasureDate.tsx
  12. 0
    87
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx
  13. 0
    43
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/CreateButton-test.tsx
  14. 0
    37
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/DeleteForm-test.tsx
  15. 0
    67
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx
  16. 0
    35
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Header-test.tsx
  17. 0
    73
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Item-test.tsx
  18. 0
    51
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/List-test.tsx
  19. 0
    179
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/App-test.tsx.snap
  20. 0
    30
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap
  21. 0
    45
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap
  22. 0
    198
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap
  23. 0
    38
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Header-test.tsx.snap
  24. 0
    176
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Item-test.tsx.snap
  25. 0
    90
      server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/List-test.tsx.snap
  26. 0
    28
      server/sonar-web/src/main/js/apps/custom-measures/routes.ts
  27. 0
    1
      server/sonar-web/src/main/js/types/types.d.ts
  28. 0
    28
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/measure/custom/ws/example-metrics.json
  29. 0
    64
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/measure/custom/ws/example-search.json
  30. 0
    37
      server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/MetricsActionTest/metrics.json
  31. 0
    64
      server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/SearchActionTest/custom-measures.json
  32. 0
    7
      server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/SearchActionTest/empty.json
  33. 0
    17
      server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json
  34. 0
    197
      sonar-ws-generator/src/main/resources/snapshot-of-api.json

+ 1
- 34
server/sonar-web/src/main/js/api/measures.ts 查看文件

@@ -17,7 +17,7 @@
* 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, post, postJSON } from 'sonar-ui-common/helpers/request';
import { getJSON } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import { BranchParameters } from '../types/branch-like';
import {
@@ -82,36 +82,3 @@ export function getMeasuresForProjects(
metricKeys: metricKeys.join()
}).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);
}

+ 0
- 16
server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx 查看文件

@@ -28,7 +28,6 @@ import NavBarTabs from 'sonar-ui-common/components/ui/NavBarTabs';
import { hasMessage, translate } from 'sonar-ui-common/helpers/l10n';
import { withAppState } from '../../../../components/hoc/withAppState';
import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like';
import { isSonarCloud } from '../../../../helpers/system';
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
import { BranchLike, BranchParameters } from '../../../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../../../types/component';
@@ -42,7 +41,6 @@ const SETTINGS_URLS = [
'/project/settings',
'/project/quality_profiles',
'/project/quality_gate',
'/custom_measures',
'/project/links',
'/project_roles',
'/project/history',
@@ -296,7 +294,6 @@ export class Menu extends React.PureComponent<Props> {
...this.renderAdminExtensions(query, isApplication),
this.renderProfilesLink(query),
this.renderQualityGateLink(query),
this.renderCustomMeasuresLink(query),
this.renderLinksLink(query),
this.renderPermissionsLink(query),
this.renderBackgroundTasksLink(query),
@@ -414,19 +411,6 @@ export class Menu extends React.PureComponent<Props> {
);
};

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) => {
if (!this.getConfiguration().showLinks) {
return null;

+ 0
- 2
server/sonar-web/src/main/js/app/utils/startReactApp.tsx 查看文件

@@ -36,7 +36,6 @@ import backgroundTasksRoutes from '../../apps/background-tasks/routes';
import codeRoutes from '../../apps/code/routes';
import codingRulesRoutes from '../../apps/coding-rules/routes';
import componentMeasuresRoutes from '../../apps/component-measures/routes';
import customMeasuresRoutes from '../../apps/custom-measures/routes';
import customMetricsRoutes from '../../apps/custom-metrics/routes';
import documentationRoutes from '../../apps/documentation/routes';
import groupsRoutes from '../../apps/groups/routes';
@@ -192,7 +191,6 @@ function renderComponentRoutes() {
/>
<RouteWithChildRoutes path="tutorials" childRoutes={tutorialsRoutes} />
<Route component={lazyLoadComponent(() => import('../components/ProjectAdminContainer'))}>
<RouteWithChildRoutes path="custom_measures" childRoutes={customMeasuresRoutes} />
<Route
path="project/admin/extension/:pluginKey/:extensionKey"
component={lazyLoadComponent(() =>

+ 0
- 159
server/sonar-web/src/main/js/apps/custom-measures/components/App.tsx 查看文件

@@ -1,159 +0,0 @@
/*
* 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>
</>
);
}
}

+ 0
- 74
server/sonar-web/src/main/js/apps/custom-measures/components/CreateButton.tsx 查看文件

@@ -1,74 +0,0 @@
/*
* 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}
/>
)}
</>
);
}
}

+ 0
- 63
server/sonar-web/src/main/js/apps/custom-measures/components/DeleteForm.tsx 查看文件

@@ -1,63 +0,0 @@
/*
* 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>
);
}

+ 0
- 208
server/sonar-web/src/main/js/apps/custom-measures/components/Form.tsx 查看文件

@@ -1,208 +0,0 @@
/*
* 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>
);
}
}

+ 0
- 48
server/sonar-web/src/main/js/apps/custom-measures/components/Header.tsx 查看文件

@@ -1,48 +0,0 @@
/*
* 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>
);
}

+ 0
- 163
server/sonar-web/src/main/js/apps/custom-measures/components/Item.tsx 查看文件

@@ -1,163 +0,0 @@
/*
* 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>
);
}
}

+ 0
- 56
server/sonar-web/src/main/js/apps/custom-measures/components/List.tsx 查看文件

@@ -1,56 +0,0 @@
/*
* 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>
);
}

+ 0
- 50
server/sonar-web/src/main/js/apps/custom-measures/components/MeasureDate.tsx 查看文件

@@ -1,50 +0,0 @@
/*
* 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')}</>;
}
}

+ 0
- 87
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/App-test.tsx 查看文件

@@ -1,87 +0,0 @@
/*
* 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);
});

+ 0
- 43
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/CreateButton-test.tsx 查看文件

@@ -1,43 +0,0 @@
/*
* 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'
});
});

+ 0
- 37
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/DeleteForm-test.tsx 查看文件

@@ -1,37 +0,0 @@
/*
* 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();
});

+ 0
- 67
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Form-test.tsx 查看文件

@@ -1,67 +0,0 @@
/*
* 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();
});

+ 0
- 35
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Header-test.tsx 查看文件

@@ -1,35 +0,0 @@
/*
* 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' });
});

+ 0
- 73
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/Item-test.tsx 查看文件

@@ -1,73 +0,0 @@
/*
* 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} />);
}

+ 0
- 51
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/List-test.tsx 查看文件

@@ -1,51 +0,0 @@
/*
* 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();
});

+ 0
- 179
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/App-test.tsx.snap 查看文件

@@ -1,179 +0,0 @@
// 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",
},
]
`;

+ 0
- 30
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/CreateButton-test.tsx.snap 查看文件

@@ -1,30 +0,0 @@
// 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>
`;

+ 0
- 45
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/DeleteForm-test.tsx.snap 查看文件

@@ -1,45 +0,0 @@
// 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>
`;

+ 0
- 198
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Form-test.tsx.snap 查看文件

@@ -1,198 +0,0 @@
// 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>
`;

+ 0
- 38
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Header-test.tsx.snap 查看文件

@@ -1,38 +0,0 @@
// 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>
`;

+ 0
- 176
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/Item-test.tsx.snap 查看文件

@@ -1,176 +0,0 @@
// 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>
`;

+ 0
- 90
server/sonar-web/src/main/js/apps/custom-measures/components/__tests__/__snapshots__/List-test.tsx.snap 查看文件

@@ -1,90 +0,0 @@
// 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>
`;

+ 0
- 28
server/sonar-web/src/main/js/apps/custom-measures/routes.ts 查看文件

@@ -1,28 +0,0 @@
/*
* 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;

+ 0
- 1
server/sonar-web/src/main/js/types/types.d.ts 查看文件

@@ -136,7 +136,6 @@ declare namespace T {
showBackgroundTasks?: boolean;
showHistory?: boolean;
showLinks?: boolean;
showManualMeasures?: boolean;
showQualityGates?: boolean;
showQualityProfiles?: boolean;
showPermissions?: boolean;

+ 0
- 28
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/measure/custom/ws/example-metrics.json 查看文件

@@ -1,28 +0,0 @@
{
"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
}
]
}

+ 0
- 64
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/measure/custom/ws/example-search.json 查看文件

@@ -1,64 +0,0 @@
{
"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
}

+ 0
- 37
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/MetricsActionTest/metrics.json 查看文件

@@ -1,37 +0,0 @@
{
"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
}
]
}

+ 0
- 64
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/SearchActionTest/custom-measures.json 查看文件

@@ -1,64 +0,0 @@
{
"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
}

+ 0
- 7
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/SearchActionTest/empty.json 查看文件

@@ -1,7 +0,0 @@
{
"customMeasures": [
],
"p": 1,
"ps": 100,
"total": 0
}

+ 0
- 17
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json 查看文件

@@ -1,17 +0,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"
}
}

+ 0
- 197
sonar-ws-generator/src/main/resources/snapshot-of-api.json 查看文件

@@ -872,203 +872,6 @@
}
]
},
{
"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",
"since": "4.4",

Loading…
取消
儲存