瀏覽代碼

translate all routes files to ts (#2378)

tags/6.6-RC1
Stas Vilchik 6 年之前
父節點
當前提交
7983068e4d
共有 100 個檔案被更改,包括 1447 行新增1609 行删除
  1. 18
    3
      server/sonar-web/package.json
  2. 3
    1
      server/sonar-web/src/main/js/apps/about/routes.ts
  3. 0
    94
      server/sonar-web/src/main/js/apps/account/projects/ProjectCard.js
  4. 94
    0
      server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
  5. 0
    67
      server/sonar-web/src/main/js/apps/account/projects/Projects.js
  6. 65
    0
      server/sonar-web/src/main/js/apps/account/projects/Projects.tsx
  7. 17
    17
      server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx
  8. 0
    34
      server/sonar-web/src/main/js/apps/account/projects/propTypes.js
  9. 32
    0
      server/sonar-web/src/main/js/apps/account/projects/types.ts
  10. 9
    7
      server/sonar-web/src/main/js/apps/account/routes.ts
  11. 3
    1
      server/sonar-web/src/main/js/apps/background-tasks/routes.ts
  12. 3
    1
      server/sonar-web/src/main/js/apps/code/routes.ts
  13. 3
    1
      server/sonar-web/src/main/js/apps/coding-rules/routes.ts
  14. 5
    3
      server/sonar-web/src/main/js/apps/component-measures/routes.ts
  15. 10
    11
      server/sonar-web/src/main/js/apps/component/components/App.tsx
  16. 3
    1
      server/sonar-web/src/main/js/apps/component/routes.ts
  17. 3
    1
      server/sonar-web/src/main/js/apps/custom-measures/routes.ts
  18. 3
    1
      server/sonar-web/src/main/js/apps/groups/routes.ts
  19. 2
    1
      server/sonar-web/src/main/js/apps/issues/routes.ts
  20. 1
    1
      server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
  21. 1
    1
      server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
  22. 1
    1
      server/sonar-web/src/main/js/apps/maintenance/routes.tsx
  23. 1
    1
      server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx
  24. 3
    1
      server/sonar-web/src/main/js/apps/metrics/routes.ts
  25. 0
    2
      server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap
  26. 3
    1
      server/sonar-web/src/main/js/apps/overview/routes.ts
  27. 3
    1
      server/sonar-web/src/main/js/apps/permission-templates/routes.ts
  28. 4
    2
      server/sonar-web/src/main/js/apps/permissions/routes.ts
  29. 3
    1
      server/sonar-web/src/main/js/apps/projectActivity/routes.ts
  30. 3
    1
      server/sonar-web/src/main/js/apps/projects-admin/routes.ts
  31. 1
    2
      server/sonar-web/src/main/js/apps/projects/components/App.tsx
  32. 5
    4
      server/sonar-web/src/main/js/apps/projects/routes.ts
  33. 5
    3
      server/sonar-web/src/main/js/apps/quality-gates/routes.ts
  34. 12
    0
      server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.ts.snap
  35. 4
    3
      server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.ts
  36. 0
    122
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js
  37. 109
    0
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx
  38. 54
    58
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
  39. 7
    10
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.tsx
  40. 12
    17
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx
  41. 16
    25
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.tsx
  42. 17
    26
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.tsx
  43. 10
    18
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.tsx
  44. 11
    9
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx
  45. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx
  46. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.tsx
  47. 2
    2
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.tsx
  48. 1
    1
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.tsx
  49. 37
    33
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
  50. 7
    10
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.tsx
  51. 11
    16
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx
  52. 19
    21
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx
  53. 2
    3
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx
  54. 3
    1
      server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx
  55. 18
    24
      server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx
  56. 4
    4
      server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
  57. 9
    16
      server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.tsx
  58. 22
    28
      server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx
  59. 18
    23
      server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx
  60. 29
    35
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
  61. 15
    20
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
  62. 11
    26
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx
  63. 17
    24
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
  64. 16
    23
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx
  65. 21
    27
      server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx
  66. 17
    8
      server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx
  67. 28
    33
      server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx
  68. 17
    21
      server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx
  69. 24
    31
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
  70. 11
    16
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx
  71. 10
    15
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
  72. 47
    53
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
  73. 0
    93
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js
  74. 82
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.tsx
  75. 24
    29
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
  76. 31
    31
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
  77. 7
    6
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx
  78. 11
    14
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx
  79. 10
    13
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx
  80. 9
    12
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx
  81. 14
    15
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
  82. 1
    2
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.tsx
  83. 1
    2
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.tsx
  84. 1
    2
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.tsx
  85. 1
    2
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.tsx
  86. 0
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap
  87. 0
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap
  88. 0
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap
  89. 0
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap
  90. 0
    0
      server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap
  91. 37
    45
      server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
  92. 14
    23
      server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.tsx
  93. 0
    96
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js
  94. 86
    0
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx
  95. 26
    18
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
  96. 0
    80
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js
  97. 73
    0
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx
  98. 23
    30
      server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx
  99. 19
    26
      server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
  100. 0
    0
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx

+ 18
- 3
server/sonar-web/package.json 查看文件

@@ -44,13 +44,18 @@
"devDependencies": {
"@types/classnames": "2.2.0",
"@types/enzyme": "2.8.6",
"@types/escape-html": "0.0.19",
"@types/jest": "20.0.7",
"@types/jquery": "3.2.11",
"@types/lodash": "4.14.73",
"@types/prop-types": "15.5.1",
"@types/react": "16.0.2",
"@types/react-dom": "15.5.2",
"@types/react-helmet": "5.0.3",
"@types/react-modal": "2.2.0",
"@types/react-redux": "5.0.3",
"@types/react-router": "3.0.5",
"@types/react-select": "1.0.51",
"autoprefixer": "7.1.1",
"awesome-typescript-loader": "3.2.3",
"babel-core": "^6.22.1",
@@ -122,8 +127,16 @@
],
"jest": {
"coverageDirectory": "<rootDir>/target/coverage",
"coveragePathIgnorePatterns": ["<rootDir>/node_modules", "<rootDir>/tests"],
"moduleFileExtensions": ["ts", "tsx", "js", "json"],
"coveragePathIgnorePatterns": [
"<rootDir>/node_modules",
"<rootDir>/tests"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"json"
],
"moduleNameMapper": {
"^.+\\.(hbs|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/config/jest/FileStub.js",
"^.+\\.css$": "<rootDir>/config/jest/CSSStub.js"
@@ -132,7 +145,9 @@
"<rootDir>/config/polyfills.js",
"<rootDir>/config/jest/SetupTestEnvironment.js"
],
"snapshotSerializers": ["enzyme-to-json/serializer"],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"testPathIgnorePatterns": [
"<rootDir>/node_modules",
"<rootDir>/src/main/webapp",

server/sonar-web/src/main/js/apps/about/routes.js → server/sonar-web/src/main/js/apps/about/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/AboutApp').then(i => callback(null, { component: i.default }));
}
}

+ 0
- 94
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.js 查看文件

@@ -1,94 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import moment from 'moment';
import { sortBy } from 'lodash';
import { Link } from 'react-router';
import Level from '../../../components/ui/Level';
import { projectType } from './propTypes';
import { translateWithParameters, translate } from '../../../helpers/l10n';

export default class ProjectCard extends React.PureComponent {
static propTypes = {
project: projectType.isRequired
};

render() {
const { project } = this.props;
const isAnalyzed = project.lastAnalysisDate != null;
const analysisMoment = isAnalyzed && moment(project.lastAnalysisDate);
const links = sortBy(project.links, 'type');

return (
<div className="account-project-card clearfix">
<aside className="account-project-side">
{isAnalyzed
? <div className="account-project-analysis" title={analysisMoment.format('LLL')}>
{translateWithParameters(
'my_account.projects.analyzed_x',
analysisMoment.fromNow()
)}
</div>
: <div className="account-project-analysis">
{translate('my_account.projects.never_analyzed')}
</div>}

{project.qualityGate != null &&
<div className="account-project-quality-gate">
<Level level={project.qualityGate} />
</div>}
</aside>

<h3 className="account-project-name">
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>
{project.name}
</Link>
</h3>

{links.length > 0 &&
<div className="account-project-links">
<ul className="list-inline">
{links.map(link =>
<li key={link.type}>
<a
className="link-with-icon"
href={link.href}
title={link.name}
target="_blank"
rel="nofollow">
<i className={`icon-color-link icon-${link.type}`} />
</a>
</li>
)}
</ul>
</div>}

<div className="account-project-key">
{project.key}
</div>

{!!project.description &&
<div className="account-project-description">
{project.description}
</div>}
</div>
);
}
}

+ 94
- 0
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx 查看文件

@@ -0,0 +1,94 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 * as moment from 'moment';
import { sortBy } from 'lodash';
import { Link } from 'react-router';
import { IProject } from './types';
import Level from '../../../components/ui/Level';
import { translateWithParameters, translate } from '../../../helpers/l10n';

interface Props {
project: IProject;
}

export default function ProjectCard(props: Props) {
const { project } = props;
const isAnalyzed = project.lastAnalysisDate != null;
const analysisMoment = isAnalyzed && moment(project.lastAnalysisDate);
const links = sortBy(project.links, 'type');

return (
<div className="account-project-card clearfix">
<aside className="account-project-side">
{isAnalyzed
? <div
className="account-project-analysis"
title={analysisMoment ? analysisMoment.format('LLL') : undefined}>
{translateWithParameters(
'my_account.projects.analyzed_x',
analysisMoment ? analysisMoment.fromNow() : undefined
)}
</div>
: <div className="account-project-analysis">
{translate('my_account.projects.never_analyzed')}
</div>}

{project.qualityGate != null &&
<div className="account-project-quality-gate">
<Level level={project.qualityGate} />
</div>}
</aside>

<h3 className="account-project-name">
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>
{project.name}
</Link>
</h3>

{links.length > 0 &&
<div className="account-project-links">
<ul className="list-inline">
{links.map(link =>
<li key={link.type}>
<a
className="link-with-icon"
href={link.href}
title={link.name}
target="_blank"
rel="nofollow">
<i className={`icon-color-link icon-${link.type}`} />
</a>
</li>
)}
</ul>
</div>}

<div className="account-project-key">
{project.key}
</div>

{!!project.description &&
<div className="account-project-description">
{project.description}
</div>}
</div>
);
}

+ 0
- 67
server/sonar-web/src/main/js/apps/account/projects/Projects.js 查看文件

@@ -1,67 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import ProjectCard from './ProjectCard';
import ListFooter from '../../../components/controls/ListFooter';
import { projectsListType } from './propTypes';
import { translate } from '../../../helpers/l10n';

export default class Projects extends React.PureComponent {
static propTypes = {
projects: projectsListType.isRequired,
total: PropTypes.number.isRequired,
loading: PropTypes.bool.isRequired,
loadMore: PropTypes.func.isRequired
};

render() {
const { projects } = this.props;

return (
<div id="account-projects">
{projects.length === 0
? <div className="js-no-results">
{translate('my_account.projects.no_results')}
</div>
: <p>
{translate('my_account.projects.description')}
</p>}

{projects.length > 0 &&
<ul className="account-projects-list">
{projects.map(project =>
<li key={project.key}>
<ProjectCard project={project} />
</li>
)}
</ul>}

{projects.length > 0 &&
<ListFooter
count={projects.length}
total={this.props.total}
ready={!this.props.loading}
loadMore={this.props.loadMore}
/>}
</div>
);
}
}

+ 65
- 0
server/sonar-web/src/main/js/apps/account/projects/Projects.tsx 查看文件

@@ -0,0 +1,65 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 ProjectCard from './ProjectCard';
import { IProject } from './types';
import ListFooter from '../../../components/controls/ListFooter';
import { translate } from '../../../helpers/l10n';

interface Props {
loading: boolean;
loadMore: () => void;
projects: IProject[];
search: (query: string) => void;
total?: number;
}

export default function Projects(props: Props) {
const { projects } = props;

return (
<div id="account-projects">
{projects.length === 0
? <div className="js-no-results">
{translate('my_account.projects.no_results')}
</div>
: <p>
{translate('my_account.projects.description')}
</p>}

{projects.length > 0 &&
<ul className="account-projects-list">
{projects.map(project =>
<li key={project.key}>
<ProjectCard project={project} />
</li>
)}
</ul>}

{projects.length > 0 &&
<ListFooter
count={projects.length}
total={props.total || 0}
ready={!props.loading}
loadMore={props.loadMore}
/>}
</div>
);
}

server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js → server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx 查看文件

@@ -17,24 +17,28 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import Helmet from 'react-helmet';
import Projects from './Projects';
import { getMyProjects } from '../../../api/components';
import { translate } from '../../../helpers/l10n';

export default class ProjectsContainer extends React.PureComponent {
state = {
interface State {
loading: boolean;
page: number;
projects?: any[];
query: string;
total?: number;
}

export default class ProjectsContainer extends React.PureComponent<{}, State> {
mounted: boolean;
state: State = {
loading: true,
page: 1,
query: ''
};

componentWillMount() {
this.loadMore = this.loadMore.bind(this);
this.search = this.search.bind(this);
}

componentDidMount() {
this.mounted = true;
this.loadProjects();
@@ -46,15 +50,15 @@ export default class ProjectsContainer extends React.PureComponent {

loadProjects(page = this.state.page, query = this.state.query) {
this.setState({ loading: true });
const data = { ps: 100 };
const data: { [p: string]: any } = { ps: 100 };
if (page > 1) {
data.p = page;
}
if (query) {
data.q = query;
}
return getMyProjects(data).then(r => {
const projects = page > 1 ? [...this.state.projects, ...r.projects] : r.projects;
return getMyProjects(data).then((r: any) => {
const projects = page > 1 ? [...(this.state.projects || []), ...r.projects] : r.projects;
this.setState({
projects,
query,
@@ -65,13 +69,9 @@ export default class ProjectsContainer extends React.PureComponent {
});
}

loadMore() {
return this.loadProjects(this.state.page + 1);
}
loadMore = () => this.loadProjects(this.state.page + 1);

search(query) {
return this.loadProjects(1, query);
}
search = (query: string) => this.loadProjects(1, query);

render() {
const helmet = <Helmet title={translate('my_account.projects')} />;

+ 0
- 34
server/sonar-web/src/main/js/apps/account/projects/propTypes.js 查看文件

@@ -1,34 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 PropTypes from 'prop-types';

const { shape, string, array, arrayOf } = PropTypes;

export const projectType = shape({
id: string.isRequired,
key: string.isRequired,
name: string.isRequired,
lastAnalysisDate: string,
description: string,
links: array.isRequired,
qualityGate: string
});

export const projectsListType = arrayOf(projectType);

+ 32
- 0
server/sonar-web/src/main/js/apps/account/projects/types.ts 查看文件

@@ -0,0 +1,32 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.
*/
export interface IProject {
id: string;
key: string;
name: string;
lastAnalysisDate: string;
description: string;
links: Array<{
href: string;
name: string;
type: string;
}>;
qualityGate: string;
}

server/sonar-web/src/main/js/apps/account/routes.js → server/sonar-web/src/main/js/apps/account/routes.ts 查看文件

@@ -17,44 +17,46 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps, RouteComponent } from 'react-router';

const routes = [
{
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./components/Account').then(i => callback(null, i.default));
},
childRoutes: [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./profile/Profile').then(i => callback(null, { component: i.default }));
}
},
{
path: 'security',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./components/Security').then(i => callback(null, i.default));
}
},
{
path: 'projects',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./projects/ProjectsContainer').then(i => callback(null, i.default));
}
},
{
path: 'notifications',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./notifications/Notifications').then(i => callback(null, i.default));
}
},
{
path: 'organizations',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./organizations/UserOrganizations').then(i => callback(null, i.default));
},
childRoutes: [
{
path: 'create',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./organizations/CreateOrganizationForm').then(i => callback(null, i.default));
}
}

server/sonar-web/src/main/js/apps/background-tasks/routes.js → server/sonar-web/src/main/js/apps/background-tasks/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/BackgroundTasksApp').then(i => callback(null, { component: i.default }));
}
}

server/sonar-web/src/main/js/apps/code/routes.js → server/sonar-web/src/main/js/apps/code/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/App').then(i => callback(null, { component: i.default }));
}
}

server/sonar-web/src/main/js/apps/coding-rules/routes.js → server/sonar-web/src/main/js/apps/coding-rules/routes.ts 查看文件

@@ -17,10 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, RouteComponent } from 'react-router';

const routes = [
{
indexRoute: {
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./components/CodingRulesAppContainer').then(i => callback(null, i.default));
}
}

server/sonar-web/src/main/js/apps/component-measures/routes.js → server/sonar-web/src/main/js/apps/component-measures/routes.ts 查看文件

@@ -17,15 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps, RedirectFunction } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/AppContainer').then(i => callback(null, { component: i.default }));
}
},
{
path: 'domain/:domainName',
onEnter(nextState, replace) {
onEnter(nextState: RouterState, replace: RedirectFunction) {
replace({
pathname: '/component_measures',
query: {
@@ -37,7 +39,7 @@ const routes = [
},
{
path: 'metric/:metricKey(/:view)',
onEnter(nextState, replace) {
onEnter(nextState: RouterState, replace: RedirectFunction) {
if (nextState.params.view === 'history') {
replace({
pathname: '/project/activity',

server/sonar-web/src/main/js/apps/component/components/App.js → server/sonar-web/src/main/js/apps/component/components/App.tsx 查看文件

@@ -17,20 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';

export default class App extends React.PureComponent {
/*:: props: {
location: {
query: {
id: string,
line?: string
}
}
interface Props {
location: {
query: {
id: string;
line?: string;
};
};
*/
}

export default class App extends React.PureComponent<Props> {
scrollToLine = () => {
const { line } = this.props.location.query;
if (line) {

server/sonar-web/src/main/js/apps/component/routes.js → server/sonar-web/src/main/js/apps/component/routes.ts 查看文件

@@ -17,10 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, RouteComponent } from 'react-router';

const routes = [
{
indexRoute: {
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./components/App').then(i => callback(null, i.default));
}
}

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

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/CustomMeasuresAppContainer').then(i =>
callback(null, { component: i.default })
);

server/sonar-web/src/main/js/apps/groups/routes.js → server/sonar-web/src/main/js/apps/groups/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
Promise.all([
import('./components/GroupsAppContainer').then(i => i.default),
import('../organizations/forSingleOrganization').then(i => i.default)

server/sonar-web/src/main/js/apps/issues/routes.js → server/sonar-web/src/main/js/apps/issues/routes.ts 查看文件

@@ -17,11 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';
import { onEnter } from './redirects';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/AppContainer').then(i =>
callback(null, { component: i.default, onEnter })
);

server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.js → server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx 查看文件

@@ -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 React from 'react';
import * as React from 'react';
import init from '../init';

export default class MaintenanceAppContainer extends React.PureComponent {

server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.js → server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx 查看文件

@@ -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 React from 'react';
import * as React from 'react';
import init from '../init';

export default class SetupAppContainer extends React.PureComponent {

server/sonar-web/src/main/js/apps/maintenance/routes.js → server/sonar-web/src/main/js/apps/maintenance/routes.tsx 查看文件

@@ -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 React from 'react';
import * as React from 'react';
import { IndexRoute } from 'react-router';
import MaintenanceAppContainer from './components/MaintenanceAppContainer';
import SetupAppContainer from './components/SetupAppContainer';

server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js → server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx 查看文件

@@ -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 React from 'react';
import * as React from 'react';
import Helmet from 'react-helmet';
import init from '../init';
import { translate } from '../../../helpers/l10n';

server/sonar-web/src/main/js/apps/metrics/routes.js → server/sonar-web/src/main/js/apps/metrics/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/MetricsAppContainer').then(i =>
callback(null, { component: i.default })
);

+ 0
- 2
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap 查看文件

@@ -27,8 +27,6 @@ exports[`renders 2`] = `
overview.quality_gate
<Level
level="ERROR"
muted={false}
small={false}
/>
</h2>
<div

server/sonar-web/src/main/js/apps/overview/routes.js → server/sonar-web/src/main/js/apps/overview/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/AppContainer').then(i => callback(null, { component: i.default }));
}
}

server/sonar-web/src/main/js/apps/permission-templates/routes.js → server/sonar-web/src/main/js/apps/permission-templates/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
Promise.all([
import('./components/AppContainer').then(i => i.default),
import('../organizations/forSingleOrganization').then(i => i.default)

server/sonar-web/src/main/js/apps/permissions/routes.js → server/sonar-web/src/main/js/apps/permissions/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

export const globalPermissionsRoutes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
Promise.all([
import('./global/components/App').then(i => i.default),
import('../organizations/forSingleOrganization').then(i => i.default)
@@ -32,7 +34,7 @@ export const globalPermissionsRoutes = [

export const projectPermissionsRoutes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./project/components/AppContainer').then(i =>
callback(null, { component: i.default })
);

server/sonar-web/src/main/js/apps/projectActivity/routes.js → server/sonar-web/src/main/js/apps/projectActivity/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/ProjectActivityAppContainer').then(i =>
callback(null, { component: i.default })
);

server/sonar-web/src/main/js/apps/projects-admin/routes.js → server/sonar-web/src/main/js/apps/projects-admin/routes.ts 查看文件

@@ -17,9 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps } from 'react-router';

const routes = [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
Promise.all([
import('./AppContainer').then(i => i.default),
import('../organizations/forSingleOrganization').then(i => i.default)

server/sonar-web/src/main/js/apps/projects/components/App.js → server/sonar-web/src/main/js/apps/projects/components/App.tsx 查看文件

@@ -17,8 +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.
*/
//@flow
import React from 'react';
import * as React from 'react';

export default class App extends React.PureComponent {
componentDidMount() {

server/sonar-web/src/main/js/apps/projects/routes.js → server/sonar-web/src/main/js/apps/projects/routes.ts 查看文件

@@ -17,16 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps, RouteComponent, RedirectFunction } from 'react-router';
import { saveAll } from '../../helpers/storage';

const routes = [
{
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./components/App').then(i => callback(null, i.default));
},
childRoutes: [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/DefaultPageSelector').then(i =>
callback(null, { component: i.default })
);
@@ -34,14 +35,14 @@ const routes = [
},
{
path: 'all',
onEnter(_, replace) {
onEnter(_: RouterState, replace: RedirectFunction) {
saveAll();
replace('/projects');
}
},
{
path: 'favorite',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./components/FavoriteProjectsContainer').then(i => callback(null, i.default));
}
}

server/sonar-web/src/main/js/apps/quality-gates/routes.js → server/sonar-web/src/main/js/apps/quality-gates/routes.ts 查看文件

@@ -17,20 +17,22 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { RouterState, IndexRouteProps, RouteComponent } from 'react-router';

const routes = [
{
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./containers/QualityGatesAppContainer').then(i => callback(null, i.default));
},
childRoutes: [
{
getIndexRoute(_, callback) {
getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
import('./components/Intro').then(i => callback(null, { component: i.default }));
}
},
{
path: 'show/:id',
getComponent(_, callback) {
getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
import('./containers/DetailsContainer').then(i => callback(null, i.default));
}
}

server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.js.snap → server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.ts.snap 查看文件

@@ -105,6 +105,18 @@ Array [
`;

exports[`#sortProfiles sorts partial set of inherited profiles 1`] = `
Array [
Object {
"childrenCount": 0,
"depth": 1,
"key": "foo",
"name": "foo",
"parentKey": "bar",
},
]
`;

exports[`#sortProfiles sorts partial set of inherited profiles 2`] = `
Array [
Object {
"childrenCount": 0,

server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.js → server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.ts 查看文件

@@ -18,9 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { sortProfiles } from '../utils';
import { IProfile } from '../types';

function createProfile(key, parentKey) {
return { name: key, key, parentKey };
function createProfile(key: string, parentKey?: string) {
return { name: key, key, parentKey } as IProfile;
}

describe('#sortProfiles', () => {
@@ -54,7 +55,7 @@ describe('#sortProfiles', () => {

it('sorts partial set of inherited profiles', () => {
const foo = createProfile('foo', 'bar');
expect(sortProfiles([foo]), ['foo']);
expect(sortProfiles([foo])).toMatchSnapshot();

const profile1 = createProfile('profile1', 'x');
const profile2 = createProfile('profile2');

+ 0
- 122
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js 查看文件

@@ -1,122 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import moment from 'moment';
import ChangesList from './ChangesList';
import { translate } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';

/*::
type Props = {
events: Array<{
action: string,
authorName: string,
date: string,
params?: {},
ruleKey: string,
ruleName: string
}>,
organization: ?string
};
*/

export default class Changelog extends React.PureComponent {
/*:: props: Props; */

render() {
let isEvenRow = false;

const rows = this.props.events.map((event, index) => {
const prev = index > 0 ? this.props.events[index - 1] : null;
const isSameDate = prev != null && moment(prev.date).diff(event.date, 'seconds') < 10;
const isBulkChange =
prev != null &&
isSameDate &&
prev.authorName === event.authorName &&
prev.action === event.action;

if (!isBulkChange) {
isEvenRow = !isEvenRow;
}

const className = 'js-profile-changelog-event ' + (isEvenRow ? 'even' : 'odd');

return (
<tr key={index} className={className}>
<td className="thin nowrap">
{!isBulkChange && moment(event.date).format('LLL')}
</td>

<td className="thin nowrap">
{!isBulkChange &&
(event.authorName
? <span>
{event.authorName}
</span>
: <span className="note">System</span>)}
</td>

<td className="thin nowrap">
{!isBulkChange && translate('quality_profiles.changelog', event.action)}
</td>

<td style={{ lineHeight: '1.5' }}>
<Link to={getRulesUrl({ rule_key: event.ruleKey }, this.props.organization)}>
{event.ruleName}
</Link>
</td>

<td className="thin nowrap">
<ChangesList changes={event.params} />
</td>
</tr>
);
});

return (
<table className="data zebra-hover">
<thead>
<tr>
<th className="thin nowrap">
{translate('date')} <i className="icon-sort-desc" />
</th>
<th className="thin nowrap">
{translate('user')}
</th>
<th className="thin nowrap">
{translate('action')}
</th>
<th>
{translate('rule')}
</th>
<th className="thin nowrap">
{translate('parameters')}
</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
);
}
}

+ 109
- 0
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx 查看文件

@@ -0,0 +1,109 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 { Link } from 'react-router';
import * as moment from 'moment';
import ChangesList from './ChangesList';
import { translate } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';
import { IProfileChangelogEvent } from '../types';

interface Props {
events: IProfileChangelogEvent[];
organization: string | null;
}

export default function Changelog(props: Props) {
let isEvenRow = false;

const rows = props.events.map((event, index) => {
const prev = index > 0 ? props.events[index - 1] : null;
const isSameDate = prev != null && moment(prev.date).diff(event.date, 'seconds') < 10;
const isBulkChange =
prev != null &&
isSameDate &&
prev.authorName === event.authorName &&
prev.action === event.action;

if (!isBulkChange) {
isEvenRow = !isEvenRow;
}

const className = 'js-profile-changelog-event ' + (isEvenRow ? 'even' : 'odd');

return (
<tr key={index} className={className}>
<td className="thin nowrap">
{!isBulkChange && moment(event.date).format('LLL')}
</td>

<td className="thin nowrap">
{!isBulkChange &&
(event.authorName
? <span>
{event.authorName}
</span>
: <span className="note">System</span>)}
</td>

<td className="thin nowrap">
{!isBulkChange && translate('quality_profiles.changelog', event.action)}
</td>

<td style={{ lineHeight: '1.5' }}>
<Link to={getRulesUrl({ rule_key: event.ruleKey }, props.organization)}>
{event.ruleName}
</Link>
</td>

<td className="thin nowrap">
{event.params && <ChangesList changes={event.params} />}
</td>
</tr>
);
});

return (
<table className="data zebra-hover">
<thead>
<tr>
<th className="thin nowrap">
{translate('date')} <i className="icon-sort-desc" />
</th>
<th className="thin nowrap">
{translate('user')}
</th>
<th className="thin nowrap">
{translate('action')}
</th>
<th>
{translate('rule')}
</th>
<th className="thin nowrap">
{translate('parameters')}
</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx 查看文件

@@ -17,48 +17,42 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import Changelog from './Changelog';
import ChangelogSearch from './ChangelogSearch';
import ChangelogEmpty from './ChangelogEmpty';
import { getProfileChangelog } from '../../../api/quality-profiles';
import { translate } from '../../../helpers/l10n';
import { getProfileChangelogPath } from '../utils';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile, IProfileChangelogEvent } from '../types';

/*::
type Props = {
interface Props {
location: {
query: {
since?: string,
to?: string
}
},
organization: ?string,
profile: Profile
};
*/

/*::
type State = {
events?: Array<*>,
loading: boolean,
page?: number,
total?: number
};
*/

export default class ChangelogContainer extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
since?: string;
to?: string;
};
};
organization: string | null;
profile: IProfile;
}

interface State {
events?: IProfileChangelogEvent[];
loading: boolean;
page?: number;
total?: number;
}

export default class ChangelogContainer extends React.PureComponent<Props, State> {
mounted: boolean;

static contextTypes = {
router: PropTypes.object
};

state /*: State */ = {
state: State = {
loading: true
};

@@ -67,7 +61,7 @@ export default class ChangelogContainer extends React.PureComponent {
this.loadChangelog();
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.location !== this.props.location) {
this.loadChangelog();
}
@@ -80,7 +74,7 @@ export default class ChangelogContainer extends React.PureComponent {
loadChangelog() {
this.setState({ loading: true });
const { query } = this.props.location;
const data /*: Object */ = { profileKey: this.props.profile.key };
const data: any = { profileKey: this.props.profile.key };
if (query.since) {
data.since = query.since;
}
@@ -88,7 +82,7 @@ export default class ChangelogContainer extends React.PureComponent {
data.to = query.to;
}

getProfileChangelog(data).then(r => {
getProfileChangelog(data).then((r: any) => {
if (this.mounted) {
this.setState({
events: r.events,
@@ -100,36 +94,38 @@ export default class ChangelogContainer extends React.PureComponent {
});
}

loadMore(e /*: SyntheticInputEvent */) {
e.preventDefault();
e.target.blur();
loadMore(event: React.SyntheticEvent<HTMLElement>) {
event.preventDefault();
event.currentTarget.blur();

if (this.state.page != null) {
this.setState({ loading: true });
const { query } = this.props.location;
const data: any = {
profileKey: this.props.profile.key,
p: this.state.page + 1
};
if (query.since) {
data.since = query.since;
}
if (query.to) {
data.to = query.to;
}

this.setState({ loading: true });
const { query } = this.props.location;
const data /*: Object */ = {
profileKey: this.props.profile.key,
p: this.state.page + 1
};
if (query.since) {
data.since = query.since;
}
if (query.to) {
data.to = query.to;
getProfileChangelog(data).then((r: any) => {
if (this.mounted && this.state.events) {
this.setState({
events: [...this.state.events, ...r.events],
total: r.total,
page: r.p,
loading: false
});
}
});
}

getProfileChangelog(data).then(r => {
if (this.mounted && this.state.events) {
this.setState({
events: [...this.state.events, ...r.events],
total: r.total,
page: r.p,
loading: false
});
}
});
}

handleFromDateChange = (fromDate /*: string | void */) => {
handleFromDateChange = (fromDate?: string) => {
const path = getProfileChangelogPath(
this.props.profile.name,
this.props.profile.language,
@@ -142,7 +138,7 @@ export default class ChangelogContainer extends React.PureComponent {
this.context.router.push(path);
};

handleToDateChange = (toDate /*: string | void */) => {
handleToDateChange = (toDate?: string) => {
const path = getProfileChangelogPath(
this.props.profile.name,
this.props.profile.language,

server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.tsx 查看文件

@@ -17,16 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';

export default class ChangelogEmpty extends React.PureComponent {
render() {
return (
<div className="big-spacer-top">
{translate('no_results')}
</div>
);
}
export default function ChangelogEmpty() {
return (
<div className="big-spacer-top">
{translate('no_results')}
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx 查看文件

@@ -17,27 +17,22 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import DateInput from '../../../components/controls/DateInput';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
fromDate?: string,
toDate?: string,
onFromDateChange: () => void,
onReset: () => void,
onToDateChange: () => void
};
*/

export default class ChangelogSearch extends React.PureComponent {
/*:: props: Props; */
interface Props {
fromDate?: string;
toDate?: string;
onFromDateChange: () => void;
onReset: () => void;
onToDateChange: () => void;
}

handleResetClick(e /*: SyntheticInputEvent */) {
e.preventDefault();
e.target.blur();
export default class ChangelogSearch extends React.PureComponent<Props> {
handleResetClick(event: React.SyntheticEvent<HTMLElement>) {
event.preventDefault();
event.currentTarget.blur();
this.props.onReset();
}


server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.tsx 查看文件

@@ -17,33 +17,24 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import SeverityChange from './SeverityChange';
import ParameterChange from './ParameterChange';

/*::
type Props = {
changes: { [string]: ?string }
};
*/

export default class ChangesList extends React.PureComponent {
/*:: props: Props; */

render() {
const { changes } = this.props;
interface Props {
changes: { [change: string]: string | null };
}

return (
<ul>
{Object.keys(changes).map(key =>
<li key={key}>
{key === 'severity'
? <SeverityChange severity={changes[key]} />
: <ParameterChange name={key} value={changes[key]} />}
</li>
)}
</ul>
);
}
export default function ChangesList({ changes }: Props) {
return (
<ul>
{Object.keys(changes).map(key =>
<li key={key}>
{key === 'severity'
? <SeverityChange severity={changes[key]} />
: <ParameterChange name={key} value={changes[key]} />}
</li>
)}
</ul>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.tsx 查看文件

@@ -17,38 +17,29 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';

/*::
type Props = {
name: string,
value: ?string
};
*/

export default class ParameterChange extends React.PureComponent {
/*:: props: Props; */

render() {
const { name, value } = this.props;

if (value == null) {
return (
<div style={{ whiteSpace: 'normal' }}>
{translateWithParameters(
'quality_profiles.changelog.parameter_reset_to_default_value',
name
)}
</div>
);
}
interface Props {
name: string;
value: string | null;
}

export default function ParameterChange({ name, value }: Props) {
if (value == null) {
return (
<div style={{ whiteSpace: 'normal' }}>
{translateWithParameters('quality_profiles.parameter_set_to', name, value)}
{translateWithParameters(
'quality_profiles.changelog.parameter_reset_to_default_value',
name
)}
</div>
);
}

return (
<div style={{ whiteSpace: 'normal' }}>
{translateWithParameters('quality_profiles.parameter_set_to', name, value)}
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.tsx 查看文件

@@ -17,26 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
severity: ?string
};
*/

export default class SeverityChange extends React.PureComponent {
/*:: props: Props; */
interface Props {
severity: string | null;
}

render() {
return (
<div>
{translate('quality_profiles.severity_set_to')}{' '}
<SeverityHelper severity={this.props.severity} />
</div>
);
}
export default function SeverityChange({ severity }: Props) {
return (
<div>
{translate('quality_profiles.severity_set_to')} <SeverityHelper severity={severity} />
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx 查看文件

@@ -18,11 +18,12 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import Changelog from '../Changelog';
import ChangesList from '../ChangesList';
import { IProfileChangelogEvent } from '../../types';

function createEvent(overrides) {
function createEvent(overrides?: { [p: string]: any }): IProfileChangelogEvent {
return {
date: '2016-01-01',
authorName: 'John',
@@ -30,50 +31,51 @@ function createEvent(overrides) {
ruleKey: 'squid1234',
ruleName: 'Do not do this',
params: {},
organization: null,
...overrides
};
}

it('should render events', () => {
const events = [createEvent(), createEvent()];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
expect(changelog.find('tbody').find('tr').length).toBe(2);
});

it('should render event date', () => {
const events = [createEvent()];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
expect(changelog.text()).toContain('2016');
});

it('should render author', () => {
const events = [createEvent()];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
expect(changelog.text()).toContain('John');
});

it('should render system author', () => {
const events = [createEvent({ authorName: undefined })];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
expect(changelog.text()).toContain('System');
});

it('should render action', () => {
const events = [createEvent()];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
expect(changelog.text()).toContain('ACTIVATED');
});

it('should render rule', () => {
const events = [createEvent()];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
expect(changelog.find('Link').prop('to')).toContain('rule_key=squid1234');
});

it('should render ChangesList', () => {
const params = { severity: 'BLOCKER' };
const events = [createEvent({ params })];
const changelog = shallow(<Changelog events={events} />);
const changelog = shallow(<Changelog events={events} organization={null} />);
const changesList = changelog.find(ChangesList);
expect(changesList.length).toBe(1);
expect(changesList.prop('changes')).toBe(params);

server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import ChangelogSearch from '../ChangelogSearch';
import DateInput from '../../../../components/controls/DateInput';
import { click } from '../../../../helpers/testUtils';

server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.tsx 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import ChangesList from '../ChangesList';
import SeverityChange from '../SeverityChange';
import ParameterChange from '../ParameterChange';

server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.tsx 查看文件

@@ -18,11 +18,11 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import ParameterChange from '../ParameterChange';

it('should render different messages', () => {
const first = shallow(<ParameterChange name="foo" />);
const first = shallow(<ParameterChange name="foo" value={null} />);
const second = shallow(<ParameterChange name="foo" value="bar" />);
expect(first.text()).not.toBe(second.text());
});

server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.js → server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.tsx 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import SeverityChange from '../SeverityChange';
import SeverityHelper from '../../../../components/shared/SeverityHelper';


server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js → server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx 查看文件

@@ -17,45 +17,45 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import ComparisonForm from './ComparisonForm';
import ComparisonResults from './ComparisonResults';
import { compareProfiles } from '../../../api/quality-profiles';
import { getProfileComparePath } from '../utils';
/*:: import type { Profile } from '../propTypes'; */

/*::
type Props = {
location: { query: { withKey?: string } },
organization: ?string,
profile: Profile,
profiles: Array<Profile>
};
*/

/*::
type State = {
loading: boolean,
left?: { name: string },
right?: { name: string },
inLeft?: Array<*>,
inRight?: Array<*>,
modified?: Array<*>
};
*/

export default class ComparisonContainer extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
/*:: state: State; */
import { IProfile } from '../types';

interface Props {
location: { query: { withKey?: string } };
organization: string | null;
profile: IProfile;
profiles: IProfile[];
}

type Params = { [p: string]: string };

interface State {
loading: boolean;
left?: { name: string };
right?: { name: string };
inLeft?: Array<{ key: string; name: string; severity: string }>;
inRight?: Array<{ key: string; name: string; severity: string }>;
modified?: Array<{
key: string;
name: string;
left: { params: Params; severity: string };
right: { params: Params; severity: string };
}>;
}

export default class ComparisonContainer extends React.PureComponent<Props, State> {
mounted: boolean;

static contextTypes = {
router: PropTypes.object
};

constructor(props /*: Props */) {
constructor(props: Props) {
super(props);
this.state = { loading: false };
}
@@ -65,7 +65,7 @@ export default class ComparisonContainer extends React.PureComponent {
this.loadResults();
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile || prevProps.location !== this.props.location) {
this.loadResults();
}
@@ -83,7 +83,7 @@ export default class ComparisonContainer extends React.PureComponent {
}

this.setState({ loading: true });
compareProfiles(this.props.profile.key, withKey).then(r => {
compareProfiles(this.props.profile.key, withKey).then((r: any) => {
if (this.mounted) {
this.setState({
left: r.left,
@@ -97,7 +97,7 @@ export default class ComparisonContainer extends React.PureComponent {
});
}

handleCompare = (withKey /*: string */) => {
handleCompare = (withKey: string) => {
const path = getProfileComparePath(
this.props.profile.name,
this.props.profile.language,
@@ -126,6 +126,10 @@ export default class ComparisonContainer extends React.PureComponent {
</header>

{left != null &&
inLeft != null &&
right != null &&
inRight != null &&
modified != null &&
<ComparisonResults
left={left}
right={right}

server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js → server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.tsx 查看文件

@@ -17,16 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';

export default class ComparisonEmpty extends React.PureComponent {
render() {
return (
<div className="big-spacer-top">
{translate('quality_profile.empty_comparison')}
</div>
);
}
export default function ComparisonEmpty() {
return (
<div className="big-spacer-top">
{translate('quality_profile.empty_comparison')}
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js → server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx 查看文件

@@ -17,25 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import Select from 'react-select';
import * as React from 'react';
import * as Select from 'react-select';
import { translate } from '../../../helpers/l10n';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

/*::
type Props = {
profile: Profile,
profiles: Array<Profile>,
onCompare: string => void,
withKey: string
};
*/

export default class ComparisonForm extends React.PureComponent {
/*:: props: Props; */
interface Props {
profile: IProfile;
profiles: IProfile[];
onCompare: (rule: string) => void;
withKey?: string;
}

handleChange(option /*: { value: string } */) {
export default class ComparisonForm extends React.PureComponent<Props> {
handleChange(option: { value: string }) {
this.props.onCompare(option.value);
}


server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js → server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx 查看文件

@@ -17,33 +17,31 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import ComparisonEmpty from './ComparisonEmpty';
import SeverityIcon from '../../../components/shared/SeverityIcon';
import { translateWithParameters } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';

/*::
type Params = { [string]: string };
*/
type Params = { [p: string]: string };

/*::
type Props = {
left: { name: string },
right: { name: string },
inLeft: Array<*>,
inRight: Array<*>,
modified: Array<*>,
organization: ?string
};
*/
export default class ComparisonResults extends React.PureComponent {
/*:: props: Props; */
interface Props {
left: { name: string };
right: { name: string };
inLeft: Array<{ key: string; name: string; severity: string }>;
inRight: Array<{ key: string; name: string; severity: string }>;
modified: Array<{
key: string;
name: string;
left: { params: Params; severity: string };
right: { params: Params; severity: string };
}>;
organization: string | null;
}

renderRule(rule /*: { key: string, name: string } */, severity /*: string */) {
export default class ComparisonResults extends React.PureComponent<Props> {
renderRule(rule: { key: string; name: string }, severity: string) {
return (
<div>
<SeverityIcon severity={severity} />{' '}
@@ -52,7 +50,7 @@ export default class ComparisonResults extends React.PureComponent {
);
}

renderParameters(params /*: Params */) {
renderParameters(params: Params) {
if (!params) {
return null;
}
@@ -135,7 +133,7 @@ export default class ComparisonResults extends React.PureComponent {
}
const header = (
<tr key="modified-header">
<td colSpan="2" className="text-center">
<td colSpan={2} className="text-center">
<h6>
{translateWithParameters(
'quality_profiles.x_rules_have_different_configuration',

server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.js → server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx 查看文件

@@ -18,8 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import Select from 'react-select';
import * as React from 'react';
import ComparisonForm from '../ComparisonForm';
import { createFakeProfile } from '../../utils';

@@ -38,7 +37,7 @@ it('should render Select with right options', () => {
profiles={profiles}
onCompare={() => true}
/>
).find(Select);
).find('Select');
expect(output.length).toBe(1);
expect(output.prop('value')).toBe('another');
expect(output.prop('options')).toEqual([{ value: 'another', label: 'another name' }]);

server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.js → server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import ComparisonResults from '../ComparisonResults';
import ComparisonEmpty from '../ComparisonEmpty';
@@ -32,6 +32,7 @@ it('should render ComparisonEmpty', () => {
inLeft={[]}
inRight={[]}
modified={[]}
organization={null}
/>
);
expect(output.is(ComparisonEmpty)).toBe(true);
@@ -65,6 +66,7 @@ it('should compare', () => {
inLeft={inLeft}
inRight={inRight}
modified={modified}
organization={null}
/>
);


server/sonar-web/src/main/js/apps/quality-profiles/components/App.js → server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx 查看文件

@@ -17,37 +17,31 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { searchQualityProfiles, getExporters } from '../../../api/quality-profiles';
import { sortProfiles } from '../utils';
import { translate } from '../../../helpers/l10n';
import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
/*:: import type { Exporter } from '../propTypes'; */
import '../styles.css';
import { IExporter, IProfile } from '../types';

/*::
type Props = {
children: React.Element<*>,
currentUser: { permissions: { global: Array<string> } },
languages: Array<*>,
onRequestFail: Object => void,
organization: { name: string, canAdmin?: boolean, key: string } | null
};
*/
interface Props {
children: React.ReactElement<any>;
currentUser: { permissions: { global: Array<string> } };
languages: Array<{}>;
onRequestFail: (reasong: any) => void;
organization: { name: string; canAdmin?: boolean; key: string } | null;
}

/*::
type State = {
loading: boolean,
exporters?: Array<Exporter>,
profiles?: Array<*>
};
*/
interface State {
loading: boolean;
exporters?: IExporter[];
profiles?: IProfile[];
}

export default class App extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = { loading: true };
export default class App extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: true };

componentWillMount() {
const html = document.querySelector('html');
@@ -90,7 +84,7 @@ export default class App extends React.PureComponent {
}

updateProfiles = () => {
return this.fetchProfiles().then(profiles => {
return this.fetchProfiles().then((profiles: any) => {
if (this.mounted) {
this.setState({ profiles: sortProfiles(profiles) });
}

server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js → server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx 查看文件

@@ -22,7 +22,7 @@ import App from './App';
import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';
import { onFail } from '../../../store/rootActions';

const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state: any, ownProps: any) => ({
currentUser: getCurrentUser(state),
languages: getLanguages(state),
organization: ownProps.params.organizationKey
@@ -30,8 +30,8 @@ const mapStateToProps = (state, ownProps) => ({
: null
});

const mapDispatchToProps = dispatch => ({
onRequestFail: error => onFail(dispatch)(error)
const mapDispatchToProps = (dispatch: any) => ({
onRequestFail: (error: any) => onFail(dispatch)(error)
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
export default connect(mapStateToProps, mapDispatchToProps)(App as any);

server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.js → server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.tsx 查看文件

@@ -17,22 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import * as React from 'react';
import * as classNames from 'classnames';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {|
className?: string,
tooltip?: boolean
|};
*/
interface Props {
className?: string;
tooltip?: boolean;
}

export default function BuiltInBadge(props /*: Props */) {
export default function BuiltInBadge({ className, tooltip = true }: Props) {
const badge = (
<div className={classNames('outline-badge', props.className)}>
<div className={classNames('outline-badge', className)}>
{translate('quality_profiles.built_in')}
</div>
);
@@ -44,13 +41,9 @@ export default function BuiltInBadge(props /*: Props */) {
</span>
);

return props.tooltip
return tooltip
? <Tooltip overlay={overlay}>
{badge}
</Tooltip>
: badge;
}

BuiltInBadge.defaultProps = {
tooltip: true
};

server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js → server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx 查看文件

@@ -17,33 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
/*:: import type { Profile } from '../propTypes'; */
import { copyProfile } from '../../../api/quality-profiles';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { IProfile } from '../types';

/*::
type Props = {
onClose: () => void,
onCopy: string => void,
onRequestFail: Object => void,
profile: Profile
};
*/
interface Props {
onClose: () => void;
onCopy: (name: string) => void;
onRequestFail: (reasong: any) => void;
profile: IProfile;
}

/*::
type State = {
loading: boolean,
name: ?string
};
*/
interface State {
loading: boolean;
name: string | null;
}

export default class CopyProfileForm extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = { loading: false, name: null };
export default class CopyProfileForm extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: false, name: null };

componentDidMount() {
this.mounted = true;
@@ -53,16 +47,16 @@ export default class CopyProfileForm extends React.PureComponent {
this.mounted = false;
}

handleCancelClick = (event /*: Event */) => {
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => {
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
};

handleFormSubmit = (event /*: Event */) => {
handleFormSubmit = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();

const { name } = this.state;
@@ -70,8 +64,8 @@ export default class CopyProfileForm extends React.PureComponent {
if (name != null) {
this.setState({ loading: true });
copyProfile(this.props.profile.key, name).then(
profile => this.props.onCopy(profile.name),
error => {
(profile: any) => this.props.onCopy(profile.name),
(error: any) => {
if (this.mounted) {
this.setState({ loading: false });
}
@@ -113,11 +107,11 @@ export default class CopyProfileForm extends React.PureComponent {
<input
autoFocus={true}
id="copy-profile-name"
maxLength="100"
maxLength={100}
name="name"
onChange={this.handleNameChange}
required={true}
size="50"
size={50}
type="text"
value={this.state.name != null ? this.state.name : profile.name}
/>

server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js → server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx 查看文件

@@ -17,32 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
/*:: import type { Profile } from '../propTypes'; */
import { deleteProfile } from '../../../api/quality-profiles';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { IProfile } from '../types';

/*::
type Props = {
onClose: () => void,
onDelete: () => void,
onRequestFail: Object => void,
profile: Profile
};
*/
interface Props {
onClose: () => void;
onDelete: () => void;
onRequestFail: (reason: any) => void;
profile: IProfile;
}

/*::
type State = {
loading: boolean
};
*/
interface State {
loading: boolean;
name: string | null;
}

export default class DeleteProfileForm extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = { loading: false, name: null };
export default class DeleteProfileForm extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: false, name: null };

componentDidMount() {
this.mounted = true;
@@ -52,15 +47,15 @@ export default class DeleteProfileForm extends React.PureComponent {
this.mounted = false;
}

handleCancelClick = (event /*: Event */) => {
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleFormSubmit = (event /*: Event */) => {
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
this.setState({ loading: true });
deleteProfile(this.props.profile.key).then(this.props.onDelete, error => {
deleteProfile(this.props.profile.key).then(this.props.onDelete, (error: any) => {
if (this.mounted) {
this.setState({ loading: false });
}

server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js → server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx 查看文件

@@ -17,9 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import RenameProfileForm from './RenameProfileForm';
import CopyProfileForm from './CopyProfileForm';
@@ -28,31 +27,24 @@ import { translate } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';
import { setDefaultProfile } from '../../../api/quality-profiles';
import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils';
/*:: import type { Profile } from '../propTypes'; */

/*::
type Props = {
canAdmin: boolean,
fromList: boolean,
onRequestFail: Object => void,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};
*/

/*::
type State = {
copyFormOpen: boolean,
deleteFormOpen: boolean,
renameFormOpen: boolean
};
*/

export default class ProfileActions extends React.PureComponent {
/*:: props: Props; */
/*:: state: State; */
import { IProfile } from '../types';

interface Props {
canAdmin: boolean;
fromList?: boolean;
onRequestFail: (reasong: any) => void;
organization: string | null;
profile: IProfile;
updateProfiles: () => Promise<void>;
}

interface State {
copyFormOpen: boolean;
deleteFormOpen: boolean;
renameFormOpen: boolean;
}

export default class ProfileActions extends React.PureComponent<Props, State> {
static defaultProps = {
fromList: false
};
@@ -61,7 +53,7 @@ export default class ProfileActions extends React.PureComponent {
router: PropTypes.object
};

constructor(props /*: Props */) {
constructor(props: Props) {
super(props);
this.state = {
copyFormOpen: false,
@@ -70,12 +62,12 @@ export default class ProfileActions extends React.PureComponent {
};
}

handleRenameClick = (event /*: Event */) => {
handleRenameClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ renameFormOpen: true });
};

handleProfileRename = (name /*: string */) => {
handleProfileRename = (name: string) => {
this.closeRenameForm();
this.props.updateProfiles().then(() => {
if (!this.props.fromList) {
@@ -90,12 +82,12 @@ export default class ProfileActions extends React.PureComponent {
this.setState({ renameFormOpen: false });
};

handleCopyClick = (event /*: Event */) => {
handleCopyClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ copyFormOpen: true });
};

handleProfileCopy = (name /*: string */) => {
handleProfileCopy = (name: string) => {
this.props.updateProfiles().then(() => {
this.context.router.push(
getProfilePath(name, this.props.profile.language, this.props.organization)
@@ -107,12 +99,12 @@ export default class ProfileActions extends React.PureComponent {
this.setState({ copyFormOpen: false });
};

handleSetDefaultClick = (e /*: SyntheticInputEvent */) => {
handleSetDefaultClick = (e: React.SyntheticEvent<HTMLElement>) => {
e.preventDefault();
setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles);
};

handleDeleteClick = (event /*: Event */) => {
handleDeleteClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ deleteFormOpen: true });
};
@@ -131,7 +123,9 @@ export default class ProfileActions extends React.PureComponent {

// FIXME use org, name and lang
const backupUrl =
window.baseUrl + '/api/qualityprofiles/backup?profileKey=' + encodeURIComponent(profile.key);
(window as any).baseUrl +
'/api/qualityprofiles/backup?profileKey=' +
encodeURIComponent(profile.key);

const activateMoreUrl = getRulesUrl(
{

server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js → server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx 查看文件

@@ -17,32 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Helmet from 'react-helmet';
import ProfileNotFound from './ProfileNotFound';
import ProfileHeader from '../details/ProfileHeader';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

/*::
type Props = {
canAdmin: boolean,
children: React.Element<*>,
interface Props {
canAdmin: boolean;
children: React.ReactElement<any>;
location: {
pathname: string,
query: { key?: string, language: string, name: string }
},
onRequestFail: Object => void,
organization: ?string,
profiles: Array<Profile>,
router: { replace: ({}) => void },
updateProfiles: () => Promise<*>
};
*/

export default class ProfileContainer extends React.PureComponent {
/*:: props: Props; */
pathname: string;
query: { key?: string; language: string; name: string };
};
onRequestFail: (reasong: any) => void;
organization: string | null;
profiles: IProfile[];
router: { replace: ({}) => void };
updateProfiles: () => Promise<void>;
}

export default class ProfileContainer extends React.PureComponent<Props> {
componentDidMount() {
const { location, profiles, router } = this.props;
if (location.query.key) {

server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js → server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx 查看文件

@@ -17,35 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import moment from 'moment';
import * as React from 'react';
import * as moment from 'moment';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
date?: string
};
*/

export default class ProfileDate extends React.PureComponent {
/*:: props: Props; */

render() {
const { date } = this.props;

if (!date) {
return (
<span>
{translate('never')}
</span>
);
}
interface Props {
date?: string;
}

return (
<span title={moment(date).format('LLL')} data-toggle="tooltip">
export default function ProfileDate({ date }: Props) {
return date
? <span title={moment(date).format('LLL')} data-toggle="tooltip">
{moment(date).fromNow()}
</span>
);
}
: <span>
{translate('never')}
</span>;
}

server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js → server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx 查看文件

@@ -17,32 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import { getProfilePath } from '../utils';

/*::
type Props = {
children?: React.Element<*>,
language: string,
name: string,
organization: ?string
};
*/

export default class ProfileLink extends React.PureComponent {
/*:: props: Props; */
interface Props {
className?: string;
children?: React.ReactElement<any> | string;
language: string;
name: string;
organization: string | null;
}

render() {
const { name, language, organization, children, ...other } = this.props;
return (
<Link
to={getProfilePath(name, language, organization)}
activeClassName="link-no-underline"
{...other}>
{children}
</Link>
);
}
export default function ProfileLink({ name, language, organization, children, ...other }: Props) {
return (
<Link
to={getProfilePath(name, language, organization)}
activeClassName="link-no-underline"
{...other}>
{children}
</Link>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js → server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx 查看文件

@@ -17,34 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { IndexLink } from 'react-router';
import { translate } from '../../../helpers/l10n';
import { getProfilesPath } from '../utils';

/*::
type Props = {
organization: ?string
};
*/

export default class ProfileNotFound extends React.PureComponent {
/*:: props: Props; */
interface Props {
organization: string | null;
}

render() {
return (
<div className="quality-profile-not-found">
<div className="note spacer-bottom">
<IndexLink to={getProfilesPath(this.props.organization)} className="text-muted">
{translate('quality_profiles.page')}
</IndexLink>
</div>
export default function ProfileNotFound(props: Props) {
return (
<div className="quality-profile-not-found">
<div className="note spacer-bottom">
<IndexLink to={getProfilesPath(props.organization)} className="text-muted">
{translate('quality_profiles.page')}
</IndexLink>
</div>

<div>
{translate('quality_profiles.not_found')}
</div>
<div>
{translate('quality_profiles.not_found')}
</div>
);
}
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js → server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx 查看文件

@@ -17,33 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
/*:: import type { Profile } from '../propTypes'; */
import { renameProfile } from '../../../api/quality-profiles';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { IProfile } from '../types';

/*::
type Props = {
onClose: () => void,
onRename: string => void,
onRequestFail: Object => void,
profile: Profile
};
*/
interface Props {
onClose: () => void;
onRename: (name: string) => void;
onRequestFail: (reason: any) => void;
profile: IProfile;
}

/*::
type State = {
loading: boolean,
name: ?string
};
*/
interface State {
loading: boolean;
name: string | null;
}

export default class RenameProfileForm extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = { loading: false, name: null };
export default class RenameProfileForm extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = { loading: false, name: null };

componentDidMount() {
this.mounted = true;
@@ -53,16 +47,16 @@ export default class RenameProfileForm extends React.PureComponent {
this.mounted = false;
}

handleCancelClick = (event /*: Event */) => {
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => {
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
};

handleFormSubmit = (event /*: Event */) => {
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();

const { name } = this.state;
@@ -71,7 +65,7 @@ export default class RenameProfileForm extends React.PureComponent {
this.setState({ loading: true });
renameProfile(this.props.profile.key, name).then(
() => this.props.onRename(name),
error => {
(error: any) => {
if (this.mounted) {
this.setState({ loading: false });
}
@@ -113,11 +107,11 @@ export default class RenameProfileForm extends React.PureComponent {
<input
autoFocus={true}
id="rename-profile-name"
maxLength="100"
maxLength={100}
name="name"
onChange={this.handleNameChange}
required={true}
size="50"
size={50}
type="text"
value={this.state.name != null ? this.state.name : profile.name}
/>

server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.js → server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx 查看文件

@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import React from 'react';
import * as React from 'react';
import Helmet from 'react-helmet';
import ProfileContainer from '../ProfileContainer';
import ProfileNotFound from '../ProfileNotFound';
@@ -31,9 +31,12 @@ it('should render ProfileHeader', () => {
const updateProfiles = jest.fn();
const output = shallow(
<ProfileContainer
location={{ query: { language: 'js', name: 'fake' } }}
profiles={profiles}
canAdmin={false}
location={{ pathname: '', query: { language: 'js', name: 'fake' } }}
onRequestFail={jest.fn()}
organization={null}
profiles={profiles}
router={{} as any}
updateProfiles={updateProfiles}>
<div />
</ProfileContainer>
@@ -52,10 +55,13 @@ it('should render ProfileNotFound', () => {
];
const output = shallow(
<ProfileContainer
location={{ query: { language: 'js', name: 'random' } }}
profiles={profiles}
canAdmin={false}
updateProfiles={() => true}>
location={{ pathname: '', query: { language: 'js', name: 'random' } }}
onRequestFail={jest.fn()}
organization={null}
profiles={profiles}
router={{} as any}
updateProfiles={jest.fn()}>
<div />
</ProfileContainer>
);
@@ -67,9 +73,12 @@ it('should render Helmet', () => {
const updateProfiles = jest.fn();
const output = shallow(
<ProfileContainer
location={{ query: { language: 'js', name: 'First Profile' } }}
profiles={profiles}
canAdmin={false}
location={{ pathname: '', query: { language: 'js', name: 'First Profile' } }}
onRequestFail={jest.fn()}
organization={null}
profiles={profiles}
router={{} as any}
updateProfiles={updateProfiles}>
<div />
</ProfileContainer>

server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx 查看文件

@@ -17,36 +17,30 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
import Select from 'react-select';
import * as Select from 'react-select';
import { sortBy } from 'lodash';
import { changeProfileParent } from '../../../api/quality-profiles';
import { translate } from '../../../helpers/l10n';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

/*::
type Props = {
onChange: () => void,
onClose: () => void,
onRequestFail: Object => void,
profile: Profile,
profiles: Array<Profile>
};
*/
interface Props {
onChange: () => void;
onClose: () => void;
onRequestFail: (reasong: any) => void;
profile: IProfile;
profiles: IProfile[];
}

/*::
type State = {
loading: boolean,
selected: ?string
};
*/
interface State {
loading: boolean;
selected: string | null;
}

export default class ChangeParentForm extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = {
export default class ChangeParentForm extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = {
loading: false,
selected: null
};
@@ -59,28 +53,30 @@ export default class ChangeParentForm extends React.PureComponent {
this.mounted = false;
}

handleCancelClick = (event /*: Event */) => {
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleSelectChange = (option /*: { value: string } */) => {
handleSelectChange = (option: { value: string }) => {
this.setState({ selected: option.value });
};

handleFormSubmit = (event /*: Event */) => {
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();

const parent = this.state.selected;

if (parent != null) {
this.setState({ loading: true });
changeProfileParent(this.props.profile.key, parent).then(this.props.onChange).catch(error => {
if (this.mounted) {
this.setState({ loading: false });
}
this.props.onRequestFail(error);
});
changeProfileParent(this.props.profile.key, parent)
.then(this.props.onChange)
.catch((error: any) => {
if (this.mounted) {
this.setState({ loading: false });
}
this.props.onRequestFail(error);
});
}
};

@@ -122,7 +118,6 @@ export default class ChangeParentForm extends React.PureComponent {
</label>
<Select
clearable={false}
id="change-profile-parent"
name="parentKey"
onChange={this.handleSelectChange}
options={options}

server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx 查看文件

@@ -17,27 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
import escapeHtml from 'escape-html';
/*:: import type { Profile } from '../propTypes'; */
import * as escapeHtml from 'escape-html';
import SelectList from '../../../components/SelectList';
import { translate } from '../../../helpers/l10n';
import { IProfile } from '../types';

/*::
type Props = {
onClose: () => void,
organization: ?string,
profile: Profile
};
*/
interface Props {
onClose: () => void;
organization: string | null;
profile: IProfile;
}

export default class ChangeProjectsForm extends React.PureComponent {
/*:: container: HTMLElement; */
/*:: props: Props; */
export default class ChangeProjectsForm extends React.PureComponent<Props> {
container: HTMLElement;

handleCloseClick = (event /*: Event */) => {
handleCloseClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};
@@ -46,17 +42,17 @@ export default class ChangeProjectsForm extends React.PureComponent {
const { key } = this.props.profile;

const searchUrl =
window.baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key);
(window as any).baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key);

new SelectList({
new (SelectList as any)({
searchUrl,
el: this.container,
width: '100%',
readOnly: false,
focusSearch: false,
dangerouslyUnescapedHtmlFormat: item => escapeHtml(item.name),
selectUrl: window.baseUrl + '/api/qualityprofiles/add_project',
deselectUrl: window.baseUrl + '/api/qualityprofiles/remove_project',
dangerouslyUnescapedHtmlFormat: (item: { name: string }) => escapeHtml(item.name),
selectUrl: (window as any).baseUrl + '/api/qualityprofiles/add_project',
deselectUrl: (window as any).baseUrl + '/api/qualityprofiles/remove_project',
extra: { profileKey: key },
selectParameter: 'projectUuid',
selectParameterValue: 'uuid',
@@ -91,7 +87,7 @@ export default class ChangeProjectsForm extends React.PureComponent {
</div>

<div className="modal-body">
<div id="profile-projects" ref={node => (this.container = node)} />
<div id="profile-projects" ref={node => (this.container = node as HTMLElement)} />
</div>

<div className="modal-foot">

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx 查看文件

@@ -17,43 +17,36 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import ProfileRules from './ProfileRules';
import ProfileProjects from './ProfileProjects';
import ProfileInheritance from './ProfileInheritance';
import ProfileExporters from './ProfileExporters';
/*:: import type { Profile, Exporter } from '../propTypes'; */
import { IExporter, IProfile } from '../types';

/*::
type Props = {
canAdmin: boolean,
exporters: Array<Exporter>,
onRequestFail: Object => void,
organization: ?string,
profile: Profile,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};
*/

export default class ProfileDetails extends React.PureComponent {
/*:: props: Props; */
interface Props {
canAdmin: boolean;
exporters: IExporter[];
onRequestFail: (reasong: any) => void;
organization: string | null;
profile: IProfile;
profiles: IProfile[];
updateProfiles: () => Promise<void>;
}

render() {
return (
<div>
<div className="quality-profile-grid">
<div className="quality-profile-grid-left">
<ProfileRules {...this.props} />
<ProfileExporters {...this.props} />
</div>
<div className="quality-profile-grid-right">
<ProfileInheritance {...this.props} />
<ProfileProjects {...this.props} />
</div>
export default function ProfileDetails(props: Props) {
return (
<div>
<div className="quality-profile-grid">
<div className="quality-profile-grid-left">
<ProfileRules {...props} />
<ProfileExporters {...props} />
</div>
<div className="quality-profile-grid-right">
<ProfileInheritance {...props} />
<ProfileProjects {...props} />
</div>
</div>
);
}
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx 查看文件

@@ -17,28 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import { stringify } from 'querystring';
import React from 'react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
/*:: import type { Profile, Exporter } from '../propTypes'; */
import { IProfile, IExporter } from '../types';

/*::
type Props = {
exporters: Array<Exporter>,
organization: ?string,
profile: Profile
};
*/

export default class ProfileExporters extends React.PureComponent {
/*:: props: Props; */
interface Props {
exporters: IExporter[];
organization: string | null;
profile: IProfile;
}

getExportUrl(exporter /*: Exporter */) {
export default class ProfileExporters extends React.PureComponent<Props> {
getExportUrl(exporter: IExporter) {
const { organization, profile } = this.props;

const path = '/api/qualityprofiles/export';
const parameters /*: { [string]: string } */ = {
const parameters = {
exporterKey: exporter.key,
language: profile.language,
name: profile.name
@@ -46,7 +41,7 @@ export default class ProfileExporters extends React.PureComponent {
if (organization) {
Object.assign(parameters, { organization });
}
return window.baseUrl + path + '?' + stringify(parameters);
return (window as any).baseUrl + path + '?' + stringify(parameters);
}

render() {

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx 查看文件

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link, IndexLink } from 'react-router';
import ProfileLink from '../components/ProfileLink';
import ProfileActions from '../components/ProfileActions';
@@ -31,21 +30,17 @@ import {
getProfilesForLanguagePath,
getProfileChangelogPath
} from '../utils';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

/*::
type Props = {
canAdmin: boolean,
onRequestFail: Object => void,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};
*/

export default class ProfileHeader extends React.PureComponent {
/*:: props: Props; */
interface Props {
canAdmin: boolean;
onRequestFail: (reasong: any) => void;
profile: IProfile;
organization: string | null;
updateProfiles: () => Promise<void>;
}

export default class ProfileHeader extends React.PureComponent<Props> {
renderUpdateDate() {
const { profile } = this.props;
let inner = (

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx 查看文件

@@ -17,51 +17,44 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import * as React from 'react';
import * as classNames from 'classnames';
import ProfileInheritanceBox from './ProfileInheritanceBox';
import ChangeParentForm from './ChangeParentForm';
import { translate } from '../../../helpers/l10n';
import { getProfileInheritance } from '../../../api/quality-profiles';
/*:: import type { Profile } from '../propTypes'; */

/*::
type Props = {
canAdmin: boolean,
onRequestFail: Object => void,
organization: ?string,
profile: Profile,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};
*/

/*::
type ProfileInheritanceDetails = {
activeRuleCount: number,
isBuiltIn: boolean,
key: string,
language: string,
name: string,
overridingRuleCount?: number
};
*/

/*::
type State = {
ancestors?: Array<ProfileInheritanceDetails>,
children?: Array<ProfileInheritanceDetails>,
formOpen: boolean,
loading: boolean,
profile?: ProfileInheritanceDetails
};
*/

export default class ProfileInheritance extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = {
import { IProfile } from '../types';

interface Props {
canAdmin: boolean;
onRequestFail: (reason: any) => void;
organization: string | null;
profile: IProfile;
profiles: IProfile[];
updateProfiles: () => Promise<void>;
}

interface ProfileInheritanceDetails {
activeRuleCount: number;
isBuiltIn: boolean;
key: string;
language: string;
name: string;
overridingRuleCount?: number;
}

interface State {
ancestors?: Array<ProfileInheritanceDetails>;
children?: Array<ProfileInheritanceDetails>;
formOpen: boolean;
loading: boolean;
profile?: ProfileInheritanceDetails;
}

export default class ProfileInheritance extends React.PureComponent<Props, State> {
mounted: boolean;

state: State = {
formOpen: false,
loading: true
};
@@ -71,7 +64,7 @@ export default class ProfileInheritance extends React.PureComponent {
this.loadData();
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile) {
this.loadData();
}
@@ -82,7 +75,7 @@ export default class ProfileInheritance extends React.PureComponent {
}

loadData() {
getProfileInheritance(this.props.profile.key).then(r => {
getProfileInheritance(this.props.profile.key).then((r: any) => {
if (this.mounted) {
const { ancestors, children } = r;
this.setState({
@@ -95,7 +88,7 @@ export default class ProfileInheritance extends React.PureComponent {
});
}

handleChangeParentClick = (event /*: Event */) => {
handleChangeParentClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ formOpen: true });
};
@@ -153,15 +146,16 @@ export default class ProfileInheritance extends React.PureComponent {
/>
)}

<ProfileInheritanceBox
className={currentClassName}
depth={ancestors ? ancestors.length : 0}
displayLink={false}
extendsBuiltIn={extendsBuiltIn}
language={profile.language}
organization={this.props.organization}
profile={this.state.profile}
/>
{this.state.profile != null &&
<ProfileInheritanceBox
className={currentClassName}
depth={ancestors ? ancestors.length : 0}
displayLink={false}
extendsBuiltIn={extendsBuiltIn}
language={profile.language}
organization={this.props.organization}
profile={this.state.profile}
/>}

{this.state.children != null &&
this.state.children.map(child =>

+ 0
- 93
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js 查看文件

@@ -1,93 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
// @flow
import React from 'react';
import ProfileLink from '../components/ProfileLink';
import BuiltInBadge from '../components/BuiltInBadge';
import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n';

/*::
type Props = {|
className?: string,
depth: number,
displayLink?: boolean,
extendsBuiltIn?: boolean,
language: string,
organization: ?string,
profile: {
activeRuleCount: number,
isBuiltIn: boolean,
key: string,
language: string,
name: string,
overridingRuleCount?: number
}
|};
*/

export default class ProfileInheritanceBox extends React.PureComponent {
/*:: props: Props; */

static defaultProps = {
displayLink: true
};

render() {
const { profile, className, extendsBuiltIn } = this.props;
const offset = 25 * this.props.depth;

return (
<tr className={className}>
<td>
<div style={{ paddingLeft: offset }}>
{this.props.displayLink
? <ProfileLink
language={this.props.language}
name={profile.name}
organization={this.props.organization}>
{profile.name}
</ProfileLink>
: profile.name}
{profile.isBuiltIn && <BuiltInBadge className="spacer-left" />}
{extendsBuiltIn &&
<Tooltip overlay={translate('quality_profiles.extends_built_in')}>
<i className="icon-help spacer-left" />
</Tooltip>}
</div>
</td>

<td>
{translateWithParameters('quality_profile.x_active_rules', profile.activeRuleCount)}
</td>

<td>
{profile.overridingRuleCount != null &&
<p>
{translateWithParameters(
'quality_profiles.x_overridden_rules',
profile.overridingRuleCount
)}
</p>}
</td>
</tr>
);
}
}

+ 82
- 0
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.tsx 查看文件

@@ -0,0 +1,82 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 ProfileLink from '../components/ProfileLink';
import BuiltInBadge from '../components/BuiltInBadge';
import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n';

interface Props {
className?: string;
depth: number;
displayLink?: boolean;
extendsBuiltIn?: boolean;
language: string;
organization: string | null;
profile: {
activeRuleCount: number;
isBuiltIn: boolean;
key: string;
language: string;
name: string;
overridingRuleCount?: number;
};
}

export default function ProfileInheritanceBox({ displayLink = true, ...props }: Props) {
const { profile, className, extendsBuiltIn } = props;
const offset = 25 * props.depth;

return (
<tr className={className}>
<td>
<div style={{ paddingLeft: offset }}>
{displayLink
? <ProfileLink
language={props.language}
name={profile.name}
organization={props.organization}>
{profile.name}
</ProfileLink>
: profile.name}
{profile.isBuiltIn && <BuiltInBadge className="spacer-left" />}
{extendsBuiltIn &&
<Tooltip overlay={translate('quality_profiles.extends_built_in')}>
<i className="icon-help spacer-left" />
</Tooltip>}
</div>
</td>

<td>
{translateWithParameters('quality_profile.x_active_rules', profile.activeRuleCount)}
</td>

<td>
{profile.overridingRuleCount != null &&
<p>
{translateWithParameters(
'quality_profiles.x_overridden_rules',
profile.overridingRuleCount
)}
</p>}
</td>
</tr>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx 查看文件

@@ -17,37 +17,32 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import ChangeProjectsForm from './ChangeProjectsForm';
import QualifierIcon from '../../../components/shared/QualifierIcon';
import { getProfileProjects } from '../../../api/quality-profiles';
import { translate } from '../../../helpers/l10n';
/*:: import type { Profile } from '../propTypes'; */

/*::
type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};
*/

/*::
type State = {
formOpen: boolean,
loading: boolean,
more?: boolean,
projects: ?Array<*>
};
*/

export default class ProfileProjects extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = {
import { IProfile } from '../types';

interface Props {
canAdmin: boolean;
organization: string | null;
profile: IProfile;
updateProfiles: () => Promise<void>;
}

interface State {
formOpen: boolean;
loading: boolean;
more?: boolean;
projects: Array<{ key: string; name: string; uuid: string }> | null;
}

export default class ProfileProjects extends React.PureComponent<Props, State> {
mounted: boolean;

state: State = {
formOpen: false,
loading: true,
projects: null
@@ -58,7 +53,7 @@ export default class ProfileProjects extends React.PureComponent {
this.loadProjects();
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile) {
this.loadProjects();
}
@@ -74,7 +69,7 @@ export default class ProfileProjects extends React.PureComponent {
}

const data = { key: this.props.profile.key };
getProfileProjects(data).then(r => {
getProfileProjects(data).then((r: any) => {
if (this.mounted) {
this.setState({
projects: r.results,
@@ -85,7 +80,7 @@ export default class ProfileProjects extends React.PureComponent {
});
}

handleChangeClick = (event /*: Event */) => {
handleChangeClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ formOpen: true });
};

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx 查看文件

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import { keyBy } from 'lodash';
import ProfileRulesRowOfType from './ProfileRulesRowOfType';
@@ -29,33 +28,34 @@ import { searchRules, takeFacet } from '../../../api/rules';
import { getQualityProfiles } from '../../../api/quality-profiles';
import { getRulesUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];

/*::
type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile
};
*/

/*::
type State = {
activatedTotal: ?number,
activatedByType?: { [string]: ?{ val: string, count: ?number } },
allByType?: { [string]: ?{ val: string, count: ?number } },
compareToSonarWay: ?{ profile: string, profileName: string, missingRuleCount: number },
loading: boolean,
total: ?number
};
*/

export default class ProfileRules extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = {
interface Props {
canAdmin: boolean;
organization: string | null;
profile: IProfile;
}

interface ByType {
val: string;
count: number | null;
}

interface State {
activatedTotal: number | null;
activatedByType: { [type: string]: ByType };
allByType: { [type: string]: ByType };
compareToSonarWay: { profile: string; profileName: string; missingRuleCount: number } | null;
loading: boolean;
total: number | null;
}

export default class ProfileRules extends React.PureComponent<Props, State> {
mounted: boolean;

state: State = {
activatedTotal: null,
activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
@@ -69,7 +69,7 @@ export default class ProfileRules extends React.PureComponent {
this.loadRules();
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile) {
this.loadRules();
}
@@ -117,8 +117,8 @@ export default class ProfileRules extends React.PureComponent {
const [allRules, activatedRules, showProfile] = responses;
this.setState({
activatedTotal: activatedRules.total,
allByType: keyBy(takeFacet(allRules, 'types'), 'val'),
activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val'),
allByType: keyBy<ByType>(takeFacet(allRules, 'types'), 'val'),
activatedByType: keyBy<ByType>(takeFacet(activatedRules, 'types'), 'val'),
compareToSonarWay: showProfile && showProfile.compareToSonarWay,
loading: false,
total: allRules.total
@@ -127,13 +127,13 @@ export default class ProfileRules extends React.PureComponent {
});
}

getRulesCountForType(type /*: string */) /*: ?number */ {
getRulesCountForType(type: string) {
return this.state.activatedByType && this.state.activatedByType[type]
? this.state.activatedByType[type].count
: null;
}

getRulesTotalForType(type /*: string */) /*: ?number */ {
getRulesTotalForType(type: string) {
return this.state.allByType && this.state.allByType[type]
? this.state.allByType[type].count
: null;

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx 查看文件

@@ -17,18 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import Tooltip from '../../../components/controls/Tooltip';
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';

/*::
type Props = { activeDeprecatedRules: number, organization: ?string, profile: string };
*/
interface Props {
activeDeprecatedRules: number;
organization: string | null;
profile: string;
}

export default function ProfileRulesDeprecatedWarning(props /*: Props */) {
export default function ProfileRulesDeprecatedWarning(props: Props) {
const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization);
return (
<div className="quality-profile-rules-deprecated clearfix">

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx 查看文件

@@ -17,25 +17,22 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import { formatMeasure } from '../../../helpers/measures';
import { getRulesUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
count: ?number,
organization: ?string,
qprofile: string,
total: ?number,
type: string
};
*/
interface Props {
count: number | null;
organization: string | null;
qprofile: string;
total: number | null;
type: string;
}

export default function ProfileRulesRowOfType(props /*: Props */) {
export default function ProfileRulesRowOfType(props: Props) {
const activeRulesUrl = getRulesUrl(
{ qprofile: props.qprofile, activation: 'true', types: props.type },
props.organization
@@ -60,14 +57,14 @@ export default function ProfileRulesRowOfType(props /*: Props */) {
<td className="thin nowrap text-right">
{props.count != null &&
<Link to={activeRulesUrl}>
{formatMeasure(props.count, 'SHORT_INT')}
{formatMeasure(props.count, 'SHORT_INT', null)}
</Link>}
</td>
<td className="thin nowrap text-right">
{inactiveCount != null &&
(inactiveCount > 0
? <Link to={inactiveRulesUrl} className="small text-muted">
{formatMeasure(inactiveCount, 'SHORT_INT')}
{formatMeasure(inactiveCount, 'SHORT_INT', null)}
</Link>
: <span className="note text-muted">0</span>)}
</td>

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx 查看文件

@@ -17,23 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import { formatMeasure } from '../../../helpers/measures';
import { getRulesUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
count: ?number,
organization: ?string,
qprofile: string,
total: ?number
};
*/
interface Props {
count: number | null;
organization: string | null;
qprofile: string;
total: number | null;
}

export default function ProfileRulesRowTotal(props /*: Props */) {
export default function ProfileRulesRowTotal(props: Props) {
const activeRulesUrl = getRulesUrl(
{ qprofile: props.qprofile, activation: 'true' },
props.organization
@@ -58,7 +55,7 @@ export default function ProfileRulesRowTotal(props /*: Props */) {
{props.count != null &&
<Link to={activeRulesUrl}>
<strong>
{formatMeasure(props.count, 'SHORT_INT')}
{formatMeasure(props.count, 'SHORT_INT', null)}
</strong>
</Link>}
</td>
@@ -67,7 +64,7 @@ export default function ProfileRulesRowTotal(props /*: Props */) {
(inactiveCount > 0
? <Link to={inactiveRulesUrl} className="small text-muted">
<strong>
{formatMeasure(inactiveCount, 'SHORT_INT')}
{formatMeasure(inactiveCount, 'SHORT_INT', null)}
</strong>
</Link>
: <span className="note text-muted">0</span>)}

server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js → server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx 查看文件

@@ -17,24 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import Tooltip from '../../../components/controls/Tooltip';
import { getRulesUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
language: string,
organization: ?string,
profile: string,
sonarway: string,
sonarWayMissingRules: number
};
*/
interface Props {
language: string;
organization: string | null;
profile: string;
sonarway: string;
sonarWayMissingRules: number;
}

export default function ProfileRulesSonarWayComparison(props /*: Props */) {
export default function ProfileRulesSonarWayComparison(props: Props) {
const url = getRulesUrl(
{
qprofile: props.profile,

server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx 查看文件

@@ -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 React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProfileRules from '../ProfileRules';
import { doAsync } from '../../../../helpers/testUtils';
@@ -68,11 +68,9 @@ const apiResponseActive = {
};

// Mock api some api functions
// eslint-disable-next-line
apiRules.searchRules = data =>
(apiRules as any).searchRules = (data: any) =>
Promise.resolve(data.activation === 'true' ? apiResponseActive : apiResponseAll);
// eslint-disable-next-line
apiQP.getQualityProfiles = () =>
(apiQP as any).getQualityProfiles = () =>
Promise.resolve({
compareToSonarWay: {
profile: 'sonarway',
@@ -83,8 +81,9 @@ apiQP.getQualityProfiles = () =>

it('should render the quality profiles rules with sonarway comparison', () => {
const wrapper = shallow(<ProfileRules canAdmin={false} organization="foo" profile={PROFILE} />);
wrapper.instance().mounted = true;
wrapper.instance().loadRules();
const instance = wrapper.instance() as any;
instance.mounted = true;
instance.loadRules();
return doAsync(() => {
wrapper.update();
expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1);
@@ -123,13 +122,13 @@ it('should not show a button to activate more rules on built in profiles', () =>
});

it('should not show sonarway comparison for built in profiles', () => {
// eslint-disable-next-line
apiQP.getQualityProfiles = jest.fn(() => Promise.resolve());
(apiQP as any).getQualityProfiles = jest.fn(() => Promise.resolve());
const wrapper = shallow(
<ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
);
wrapper.instance().mounted = true;
wrapper.instance().loadRules();
const instance = wrapper.instance() as any;
instance.mounted = true;
instance.loadRules();
return doAsync(() => {
wrapper.update();
expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(0);
@@ -138,8 +137,7 @@ it('should not show sonarway comparison for built in profiles', () => {
});

it('should not show sonarway comparison if there is no missing rules', () => {
// eslint-disable-next-line
apiQP.getQualityProfiles = jest.fn(() =>
(apiQP as any).getQualityProfiles = jest.fn(() =>
Promise.resolve({
compareToSonarWay: {
profile: 'sonarway',
@@ -149,8 +147,9 @@ it('should not show sonarway comparison if there is no missing rules', () => {
})
);
const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />);
wrapper.instance().mounted = true;
wrapper.instance().loadRules();
const instance = wrapper.instance() as any;
instance.mounted = true;
instance.loadRules();
return doAsync(() => {
wrapper.update();
expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(1);

server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.tsx 查看文件

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProfileRulesDeprecatedWarning from '../ProfileRulesDeprecatedWarning';


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.tsx 查看文件

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProfileRulesRowOfType from '../ProfileRulesRowOfType';


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.tsx 查看文件

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProfileRulesRowTotal from '../ProfileRulesRowTotal';


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.tsx 查看文件

@@ -17,8 +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.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProfileRulesSonarWayComparison from '../ProfileRulesSonarWayComparison';


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap → server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap 查看文件


server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js → server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx 查看文件

@@ -17,39 +17,33 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
import Select from 'react-select';
import * as Select from 'react-select';
import { sortBy } from 'lodash';
import { getImporters, createQualityProfile } from '../../../api/quality-profiles';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
languages: Array<{ key: string, name: string }>,
onClose: () => void,
onCreate: Function,
onRequestFail: Object => void,
organization: ?string
};
*/

/*::
type State = {
importers: Array<{ key: string, languages: Array<string>, name: string }>,
language?: string,
loading: boolean,
name: string,
preloading: boolean
};
*/

export default class CreateProfileForm extends React.PureComponent {
interface Props {
languages: Array<{ key: string; name: string }>;
onClose: () => void;
onCreate: Function;
onRequestFail: (reasong: any) => void;
organization: string | null;
}

interface State {
importers: Array<{ key: string; languages: Array<string>; name: string }>;
language?: string;
loading: boolean;
name: string;
preloading: boolean;
}

export default class CreateProfileForm extends React.PureComponent<Props, State> {
/*:: form: HTMLFormElement; */
/*:: mounted: boolean; */
/*:: props: Props; */
state /*: State */ = { importers: [], loading: false, name: '', preloading: true };
mounted: boolean;
state: State = { importers: [], loading: false, name: '', preloading: true };

componentDidMount() {
this.mounted = true;
@@ -61,39 +55,41 @@ export default class CreateProfileForm extends React.PureComponent {
}

fetchImporters() {
getImporters().then(importers => {
if (this.mounted) {
this.setState({ importers, preloading: false });
getImporters().then(
(importers: Array<{ key: string; languages: Array<string>; name: string }>) => {
if (this.mounted) {
this.setState({ importers, preloading: false });
}
}
});
);
}

handleCancelClick = (event /*: Event */) => {
handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.props.onClose();
};

handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => {
handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
this.setState({ name: event.currentTarget.value });
};

handleLanguageChange = (option /*: { value: string } */) => {
handleLanguageChange = (option: { value: string }) => {
this.setState({ language: option.value });
};

handleFormSubmit = (event /*: Event */) => {
handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();

this.setState({ loading: true });

const data = new FormData(this.form);
const data = new FormData(event.currentTarget);
if (this.props.organization) {
data.append('organization', this.props.organization);
}

createQualityProfile(data).then(
response => this.props.onCreate(response.profile),
error => {
(response: any) => this.props.onCreate(response.profile),
(error: any) => {
if (this.mounted) {
this.setState({ loading: false });
}
@@ -118,10 +114,7 @@ export default class CreateProfileForm extends React.PureComponent {
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.props.onClose}>
<form
id="create-profile-form"
onSubmit={this.handleFormSubmit}
ref={node => (this.form = node)}>
<form id="create-profile-form" onSubmit={this.handleFormSubmit}>
<div className="modal-head">
<h2>
{header}
@@ -141,11 +134,11 @@ export default class CreateProfileForm extends React.PureComponent {
<input
autoFocus={true}
id="create-profile-name"
maxLength="100"
maxLength={100}
name="name"
onChange={this.handleNameChange}
required={true}
size="50"
size={50}
type="text"
value={this.state.name}
/>
@@ -157,7 +150,6 @@ export default class CreateProfileForm extends React.PureComponent {
</label>
<Select
clearable={false}
id="create-profile-language"
name="language"
onChange={this.handleLanguageChange}
options={languages.map(language => ({

server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js → server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.tsx 查看文件

@@ -17,32 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import EvolutionDeprecated from './EvolutionDeprecated';
import EvolutionStagnant from './EvolutionStagnant';
import EvolutionRules from './EvolutionRules';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

/*::
type Props = {
organization: ?string,
profiles: Array<Profile>
};
*/

export default class Evolution extends React.PureComponent {
/*:: props: Props; */

render() {
const { organization, profiles } = this.props;
interface Props {
organization: string | null;
profiles: IProfile[];
}

return (
<div className="quality-profiles-evolution">
<EvolutionDeprecated organization={organization} profiles={profiles} />
<EvolutionStagnant organization={organization} profiles={profiles} />
<EvolutionRules organization={organization} />
</div>
);
}
export default function Evolution({ organization, profiles }: Props) {
return (
<div className="quality-profiles-evolution">
<EvolutionDeprecated organization={organization} profiles={profiles} />
<EvolutionStagnant organization={organization} profiles={profiles} />
<EvolutionRules organization={organization} />
</div>
);
}

+ 0
- 96
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js 查看文件

@@ -1,96 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import { sortBy } from 'lodash';
import ProfileLink from '../components/ProfileLink';
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
import { translateWithParameters, translate } from '../../../helpers/l10n';
/*:: import type { Profile } from '../propTypes'; */

/*::
type Props = {
organization: ?string,
profiles: Array<Profile>
};
*/

export default class EvolutionDeprecated extends React.PureComponent {
/*:: props: Props; */

render() {
const profilesWithDeprecations = this.props.profiles.filter(
profile => profile.activeDeprecatedRuleCount > 0
);

if (profilesWithDeprecations.length === 0) {
return null;
}

const sortedProfiles = sortBy(profilesWithDeprecations, p => -p.activeDeprecatedRuleCount);

return (
<div className="quality-profile-box quality-profiles-evolution-deprecated">
<div className="spacer-bottom">
<strong>
{translate('quality_profiles.deprecated_rules')}
</strong>
</div>
<div className="spacer-bottom">
{translateWithParameters(
'quality_profiles.deprecated_rules_are_still_activated',
profilesWithDeprecations.length
)}
</div>
<ul>
{sortedProfiles.map(profile =>
<li key={profile.key} className="spacer-top">
<div className="text-ellipsis">
<ProfileLink
className="link-no-underline"
language={profile.language}
name={profile.name}
organization={this.props.organization}>
{profile.name}
</ProfileLink>
</div>
<div className="note">
{profile.languageName}
{', '}
<Link
to={getDeprecatedActiveRulesUrl(
{ qprofile: profile.key },
this.props.organization
)}
className="text-muted">
{translateWithParameters(
'quality_profile.x_rules',
profile.activeDeprecatedRuleCount
)}
</Link>
</div>
</li>
)}
</ul>
</div>
);
}
}

+ 86
- 0
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx 查看文件

@@ -0,0 +1,86 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 { Link } from 'react-router';
import { sortBy } from 'lodash';
import ProfileLink from '../components/ProfileLink';
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
import { translateWithParameters, translate } from '../../../helpers/l10n';
import { IProfile } from '../types';

interface Props {
organization: string | null;
profiles: IProfile[];
}

export default function EvolutionDeprecated(props: Props) {
const profilesWithDeprecations = props.profiles.filter(
profile => profile.activeDeprecatedRuleCount > 0
);

if (profilesWithDeprecations.length === 0) {
return null;
}

const sortedProfiles = sortBy(profilesWithDeprecations, p => -p.activeDeprecatedRuleCount);

return (
<div className="quality-profile-box quality-profiles-evolution-deprecated">
<div className="spacer-bottom">
<strong>
{translate('quality_profiles.deprecated_rules')}
</strong>
</div>
<div className="spacer-bottom">
{translateWithParameters(
'quality_profiles.deprecated_rules_are_still_activated',
profilesWithDeprecations.length
)}
</div>
<ul>
{sortedProfiles.map(profile =>
<li key={profile.key} className="spacer-top">
<div className="text-ellipsis">
<ProfileLink
className="link-no-underline"
language={profile.language}
name={profile.name}
organization={props.organization}>
{profile.name}
</ProfileLink>
</div>
<div className="note">
{profile.languageName}
{', '}
<Link
to={getDeprecatedActiveRulesUrl({ qprofile: profile.key }, props.organization)}
className="text-muted">
{translateWithParameters(
'quality_profile.x_rules',
profile.activeDeprecatedRuleCount
)}
</Link>
</div>
</li>
)}
</ul>
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js → server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx 查看文件

@@ -17,10 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import moment from 'moment';
import * as moment from 'moment';
import { sortBy } from 'lodash';
import { searchRules } from '../../../api/rules';
import { translateWithParameters, translate } from '../../../helpers/l10n';
@@ -31,24 +30,33 @@ const RULES_LIMIT = 10;

const PERIOD_START_MOMENT = moment().subtract(1, 'year');

function parseRules(r) {
function parseRules(r: any) {
const { rules, actives } = r;
return rules.map(rule => {
return rules.map((rule: any) => {
const activations = actives[rule.key];
return { ...rule, activations: activations ? activations.length : 0 };
});
}

/*::
type Props = {
organization: ?string
};
*/
interface Props {
organization: string | null;
}

interface IRule {
activations: number;
key: string;
langName: string;
name: string;
}

interface State {
latestRules?: Array<IRule>;
latestRulesTotal?: number;
}

export default class EvolutionRules extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
state = {};
export default class EvolutionRules extends React.PureComponent<Props, State> {
mounted: boolean;
state: State = {};

componentDidMount() {
this.mounted = true;
@@ -68,10 +76,10 @@ export default class EvolutionRules extends React.PureComponent {
f: 'name,langName,actives'
};

searchRules(data).then(r => {
searchRules(data).then((r: any) => {
if (this.mounted) {
this.setState({
latestRules: sortBy(parseRules(r), 'langName'),
latestRules: sortBy<IRule>(parseRules(r), 'langName'),
latestRulesTotal: r.total
});
}
@@ -79,7 +87,7 @@ export default class EvolutionRules extends React.PureComponent {
}

render() {
if (!this.state.latestRulesTotal) {
if (!this.state.latestRulesTotal || !this.state.latestRules) {
return null;
}

@@ -125,7 +133,7 @@ export default class EvolutionRules extends React.PureComponent {
{this.state.latestRulesTotal > RULES_LIMIT &&
<div className="spacer-top">
<Link to={newRulesUrl} className="small">
{translate('see_all')} {formatMeasure(this.state.latestRulesTotal, 'SHORT_INT')}
{translate('see_all')} {formatMeasure(this.state.latestRulesTotal, 'SHORT_INT', null)}
</Link>
</div>}
</div>

+ 0
- 80
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js 查看文件

@@ -1,80 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.
*/
// @flow
import React from 'react';
import moment from 'moment';
import ProfileLink from '../components/ProfileLink';
import { translate } from '../../../helpers/l10n';
import { isStagnant } from '../utils';
/*:: import type { Profile } from '../propTypes'; */

/*::
type Props = {
organization: ?string,
profiles: Array<Profile>
};
*/

export default class EvolutionStagnant extends React.PureComponent {
/*:: props: Props; */

render() {
// TODO filter built-in out

const outdated = this.props.profiles.filter(isStagnant);

if (outdated.length === 0) {
return null;
}

return (
<div className="quality-profile-box quality-profiles-evolution-stagnant">
<div className="spacer-bottom">
<strong>
{translate('quality_profiles.stagnant_profiles')}
</strong>
</div>
<div className="spacer-bottom">
{translate('quality_profiles.not_updated_more_than_year')}
</div>
<ul>
{outdated.map(profile =>
<li key={profile.key} className="spacer-top">
<div className="text-ellipsis">
<ProfileLink
className="link-no-underline"
language={profile.language}
name={profile.name}
organization={this.props.organization}>
{profile.name}
</ProfileLink>
</div>
<div className="note">
{profile.languageName}
{', '}
updated on {moment(profile.rulesUpdatedAt).format('LL')}
</div>
</li>
)}
</ul>
</div>
);
}
}

+ 73
- 0
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx 查看文件

@@ -0,0 +1,73 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 * as moment from 'moment';
import ProfileLink from '../components/ProfileLink';
import { translate } from '../../../helpers/l10n';
import { isStagnant } from '../utils';
import { IProfile } from '../types';

interface Props {
organization: string | null;
profiles: IProfile[];
}

export default function EvolutionStagnan(props: Props) {
// TODO filter built-in out

const outdated = props.profiles.filter(isStagnant);

if (outdated.length === 0) {
return null;
}

return (
<div className="quality-profile-box quality-profiles-evolution-stagnant">
<div className="spacer-bottom">
<strong>
{translate('quality_profiles.stagnant_profiles')}
</strong>
</div>
<div className="spacer-bottom">
{translate('quality_profiles.not_updated_more_than_year')}
</div>
<ul>
{outdated.map(profile =>
<li key={profile.key} className="spacer-top">
<div className="text-ellipsis">
<ProfileLink
className="link-no-underline"
language={profile.language}
name={profile.name}
organization={props.organization}>
{profile.name}
</ProfileLink>
</div>
<div className="note">
{profile.languageName}
{', '}
updated on {moment(profile.rulesUpdatedAt).format('LL')}
</div>
</li>
)}
</ul>
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js → server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx 查看文件

@@ -17,42 +17,35 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import PageHeader from './PageHeader';
import Evolution from './Evolution';
import ProfilesList from './ProfilesList';
/*:: import type { Profile } from '../propTypes'; */
import { IProfile } from '../types';

/*::
type Props = {
canAdmin: boolean,
languages: Array<{ key: string, name: string }>,
location: { query: { [string]: string } },
onRequestFail: Object => void,
organization?: string,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};
*/

export default class HomeContainer extends React.PureComponent {
/*:: props: Props; */
interface Props {
canAdmin: boolean;
languages: Array<{ key: string; name: string }>;
location: { query: { [p: string]: string } };
onRequestFail: (reason: any) => void;
organization: string | null;
profiles: Array<IProfile>;
updateProfiles: () => Promise<void>;
}

render() {
return (
<div>
<PageHeader {...this.props} />
export default function HomeContainer(props: Props) {
return (
<div>
<PageHeader {...props} />

<div className="page-with-sidebar">
<div className="page-main">
<ProfilesList {...this.props} />
</div>
<div className="page-sidebar">
<Evolution {...this.props} />
</div>
<div className="page-with-sidebar">
<div className="page-main">
<ProfilesList {...props} />
</div>
<div className="page-sidebar">
<Evolution {...props} />
</div>
</div>
);
}
</div>
);
}

server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js → server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx 查看文件

@@ -17,51 +17,44 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import CreateProfileForm from './CreateProfileForm';
import RestoreProfileForm from './RestoreProfileForm';
/*:: import type { Profile } from '../propTypes'; */
import { getProfilePath } from '../utils';
import { translate } from '../../../helpers/l10n';
import { IProfile } from '../types';

/*::
type Props = {
canAdmin: boolean,
languages: Array<{ key: string, name: string }>,
onRequestFail: Object => void,
organization: ?string,
updateProfiles: () => Promise<*>
};
*/

/*::
type State = {
createFormOpen: boolean,
restoreFormOpen: boolean
};
*/
interface Props {
canAdmin: boolean;
languages: Array<{ key: string; name: string }>;
onRequestFail: (reason: any) => void;
organization: string | null;
updateProfiles: () => Promise<void>;
}

export default class PageHeader extends React.PureComponent {
/*:: props: Props; */
interface State {
createFormOpen: boolean;
restoreFormOpen: boolean;
}

export default class PageHeader extends React.PureComponent<Props, State> {
static contextTypes = {
router: PropTypes.object
};

state /*: State */ = {
state = {
createFormOpen: false,
restoreFormOpen: false
};

handleCreateClick = (event /*: Event & { currentTarget: HTMLButtonElement } */) => {
handleCreateClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
event.currentTarget.blur();
this.setState({ createFormOpen: true });
};

handleCreate = (profile /*: Profile */) => {
handleCreate = (profile: IProfile) => {
this.props.updateProfiles().then(() => {
this.context.router.push(
getProfilePath(profile.name, profile.language, this.props.organization)
@@ -73,7 +66,7 @@ export default class PageHeader extends React.PureComponent {
this.setState({ createFormOpen: false });
};

handleRestoreClick = (event /*: Event */) => {
handleRestoreClick = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
this.setState({ restoreFormOpen: true });
};

server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js → server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx 查看文件


部分文件因文件數量過多而無法顯示

Loading…
取消
儲存