]> source.dussan.org Git - sonarqube.git/commitdiff
translate all routes files to ts (#2378)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Fri, 18 Aug 2017 16:53:44 +0000 (18:53 +0200)
committerGitHub <noreply@github.com>
Fri, 18 Aug 2017 16:53:44 +0000 (18:53 +0200)
232 files changed:
server/sonar-web/package.json
server/sonar-web/src/main/js/apps/about/routes.js [deleted file]
server/sonar-web/src/main/js/apps/about/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.js [deleted file]
server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/projects/Projects.js [deleted file]
server/sonar-web/src/main/js/apps/account/projects/Projects.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js [deleted file]
server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/projects/propTypes.js [deleted file]
server/sonar-web/src/main/js/apps/account/projects/types.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/account/routes.js [deleted file]
server/sonar-web/src/main/js/apps/account/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/routes.js [deleted file]
server/sonar-web/src/main/js/apps/background-tasks/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/code/routes.js [deleted file]
server/sonar-web/src/main/js/apps/code/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/coding-rules/routes.js [deleted file]
server/sonar-web/src/main/js/apps/coding-rules/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/component-measures/routes.js [deleted file]
server/sonar-web/src/main/js/apps/component-measures/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/component/components/App.js [deleted file]
server/sonar-web/src/main/js/apps/component/components/App.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/component/routes.js [deleted file]
server/sonar-web/src/main/js/apps/component/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/custom-measures/routes.js [deleted file]
server/sonar-web/src/main/js/apps/custom-measures/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/groups/routes.js [deleted file]
server/sonar-web/src/main/js/apps/groups/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/routes.js [deleted file]
server/sonar-web/src/main/js/apps/issues/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.js [deleted file]
server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.js [deleted file]
server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/maintenance/routes.js [deleted file]
server/sonar-web/src/main/js/apps/maintenance/routes.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js [deleted file]
server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/metrics/routes.js [deleted file]
server/sonar-web/src/main/js/apps/metrics/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/ApplicationQualityGate-test.js.snap
server/sonar-web/src/main/js/apps/overview/routes.js [deleted file]
server/sonar-web/src/main/js/apps/overview/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/permission-templates/routes.js [deleted file]
server/sonar-web/src/main/js/apps/permission-templates/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/permissions/routes.js [deleted file]
server/sonar-web/src/main/js/apps/permissions/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/routes.js [deleted file]
server/sonar-web/src/main/js/apps/projectActivity/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects-admin/routes.js [deleted file]
server/sonar-web/src/main/js/apps/projects-admin/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/App.js [deleted file]
server/sonar-web/src/main/js/apps/projects/components/App.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/routes.js [deleted file]
server/sonar-web/src/main/js/apps/projects/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-gates/routes.js [deleted file]
server/sonar-web/src/main/js/apps/quality-gates/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.ts.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/App.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/routes.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/types.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/quality-profiles/utils.js [deleted file]
server/sonar-web/src/main/js/apps/quality-profiles/utils.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js [deleted file]
server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/sessions/routes.js [deleted file]
server/sonar-web/src/main/js/apps/sessions/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/routes.js [deleted file]
server/sonar-web/src/main/js/apps/settings/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/system/routes.js [deleted file]
server/sonar-web/src/main/js/apps/system/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/update-center/routes.js [deleted file]
server/sonar-web/src/main/js/apps/update-center/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/users/routes.js [deleted file]
server/sonar-web/src/main/js/apps/users/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/web-api/routes.js [deleted file]
server/sonar-web/src/main/js/apps/web-api/routes.ts [new file with mode: 0644]
server/sonar-web/src/main/js/components/SelectList/index.js
server/sonar-web/src/main/js/components/controls/DateInput.js [deleted file]
server/sonar-web/src/main/js/components/controls/DateInput.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/ListFooter.js [deleted file]
server/sonar-web/src/main/js/components/controls/ListFooter.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/Tooltip.js [deleted file]
server/sonar-web/src/main/js/components/controls/Tooltip.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/BugIcon.js [deleted file]
server/sonar-web/src/main/js/components/icons-components/BugIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/CodeSmellIcon.js [deleted file]
server/sonar-web/src/main/js/components/icons-components/CodeSmellIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/VulnerabilityIcon.js [deleted file]
server/sonar-web/src/main/js/components/icons-components/VulnerabilityIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/shared/QualifierIcon.js [deleted file]
server/sonar-web/src/main/js/components/shared/QualifierIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/ui/IssueTypeIcon.js [deleted file]
server/sonar-web/src/main/js/components/ui/IssueTypeIcon.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/ui/Level.js [deleted file]
server/sonar-web/src/main/js/components/ui/Level.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/typings/rc-tooltip.d.ts [new file with mode: 0644]
server/sonar-web/tsconfig.json
server/sonar-web/yarn.lock

index ae03fe49562c3e50e6659f5942d84c6b1842d823..e9019ccbe6326aca72d9f8c183709b6f2e21ad4c 100644 (file)
   "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",
   ],
   "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"
       "<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",
diff --git a/server/sonar-web/src/main/js/apps/about/routes.js b/server/sonar-web/src/main/js/apps/about/routes.js
deleted file mode 100644 (file)
index 0748dad..0000000
+++ /dev/null
@@ -1,28 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/AboutApp').then(i => callback(null, { component: i.default }));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/about/routes.ts b/server/sonar-web/src/main/js/apps/about/routes.ts
new file mode 100644 (file)
index 0000000..6da3a2d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/AboutApp').then(i => callback(null, { component: i.default }));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.js b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.js
deleted file mode 100644 (file)
index 13826f8..0000000
+++ /dev/null
@@ -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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx
new file mode 100644 (file)
index 0000000..93a3a78
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/account/projects/Projects.js b/server/sonar-web/src/main/js/apps/account/projects/Projects.js
deleted file mode 100644 (file)
index 89c151f..0000000
+++ /dev/null
@@ -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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx b/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx
new file mode 100644 (file)
index 0000000..e3681c3
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js b/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js
deleted file mode 100644 (file)
index 69aea61..0000000
+++ /dev/null
@@ -1,101 +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 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 = {
-    loading: true,
-    page: 1,
-    query: ''
-  };
-
-  componentWillMount() {
-    this.loadMore = this.loadMore.bind(this);
-    this.search = this.search.bind(this);
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadProjects();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadProjects(page = this.state.page, query = this.state.query) {
-    this.setState({ loading: true });
-    const data = { 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;
-      this.setState({
-        projects,
-        query,
-        loading: false,
-        page: r.paging.pageIndex,
-        total: r.paging.total
-      });
-    });
-  }
-
-  loadMore() {
-    return this.loadProjects(this.state.page + 1);
-  }
-
-  search(query) {
-    return this.loadProjects(1, query);
-  }
-
-  render() {
-    const helmet = <Helmet title={translate('my_account.projects')} />;
-
-    if (this.state.projects == null) {
-      return (
-        <div className="text-center">
-          {helmet}
-          <i className="spinner spinner-margin" />
-        </div>
-      );
-    }
-
-    return (
-      <div className="account-body account-container">
-        {helmet}
-        <Projects
-          projects={this.state.projects}
-          total={this.state.total}
-          loading={this.state.loading}
-          loadMore={this.loadMore}
-          search={this.search}
-        />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx
new file mode 100644 (file)
index 0000000..b822768
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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 Helmet from 'react-helmet';
+import Projects from './Projects';
+import { getMyProjects } from '../../../api/components';
+import { translate } from '../../../helpers/l10n';
+
+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: ''
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadProjects();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadProjects(page = this.state.page, query = this.state.query) {
+    this.setState({ loading: true });
+    const data: { [p: string]: any } = { ps: 100 };
+    if (page > 1) {
+      data.p = page;
+    }
+    if (query) {
+      data.q = query;
+    }
+    return getMyProjects(data).then((r: any) => {
+      const projects = page > 1 ? [...(this.state.projects || []), ...r.projects] : r.projects;
+      this.setState({
+        projects,
+        query,
+        loading: false,
+        page: r.paging.pageIndex,
+        total: r.paging.total
+      });
+    });
+  }
+
+  loadMore = () => this.loadProjects(this.state.page + 1);
+
+  search = (query: string) => this.loadProjects(1, query);
+
+  render() {
+    const helmet = <Helmet title={translate('my_account.projects')} />;
+
+    if (this.state.projects == null) {
+      return (
+        <div className="text-center">
+          {helmet}
+          <i className="spinner spinner-margin" />
+        </div>
+      );
+    }
+
+    return (
+      <div className="account-body account-container">
+        {helmet}
+        <Projects
+          projects={this.state.projects}
+          total={this.state.total}
+          loading={this.state.loading}
+          loadMore={this.loadMore}
+          search={this.search}
+        />
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/account/projects/propTypes.js b/server/sonar-web/src/main/js/apps/account/projects/propTypes.js
deleted file mode 100644 (file)
index cae87b4..0000000
+++ /dev/null
@@ -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);
diff --git a/server/sonar-web/src/main/js/apps/account/projects/types.ts b/server/sonar-web/src/main/js/apps/account/projects/types.ts
new file mode 100644 (file)
index 0000000..76feb04
--- /dev/null
@@ -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;
+}
diff --git a/server/sonar-web/src/main/js/apps/account/routes.js b/server/sonar-web/src/main/js/apps/account/routes.js
deleted file mode 100644 (file)
index 2b92bf0..0000000
+++ /dev/null
@@ -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.
- */
-const routes = [
-  {
-    getComponent(_, callback) {
-      import('./components/Account').then(i => callback(null, i.default));
-    },
-    childRoutes: [
-      {
-        getIndexRoute(_, callback) {
-          import('./profile/Profile').then(i => callback(null, { component: i.default }));
-        }
-      },
-      {
-        path: 'security',
-        getComponent(_, callback) {
-          import('./components/Security').then(i => callback(null, i.default));
-        }
-      },
-      {
-        path: 'projects',
-        getComponent(_, callback) {
-          import('./projects/ProjectsContainer').then(i => callback(null, i.default));
-        }
-      },
-      {
-        path: 'notifications',
-        getComponent(_, callback) {
-          import('./notifications/Notifications').then(i => callback(null, i.default));
-        }
-      },
-      {
-        path: 'organizations',
-        getComponent(_, callback) {
-          import('./organizations/UserOrganizations').then(i => callback(null, i.default));
-        },
-        childRoutes: [
-          {
-            path: 'create',
-            getComponent(_, callback) {
-              import('./organizations/CreateOrganizationForm').then(i => callback(null, i.default));
-            }
-          }
-        ]
-      }
-    ]
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/account/routes.ts b/server/sonar-web/src/main/js/apps/account/routes.ts
new file mode 100644 (file)
index 0000000..90e6614
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 { RouterState, IndexRouteProps, RouteComponent } from 'react-router';
+
+const routes = [
+  {
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/Account').then(i => callback(null, i.default));
+    },
+    childRoutes: [
+      {
+        getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+          import('./profile/Profile').then(i => callback(null, { component: i.default }));
+        }
+      },
+      {
+        path: 'security',
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./components/Security').then(i => callback(null, i.default));
+        }
+      },
+      {
+        path: 'projects',
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./projects/ProjectsContainer').then(i => callback(null, i.default));
+        }
+      },
+      {
+        path: 'notifications',
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./notifications/Notifications').then(i => callback(null, i.default));
+        }
+      },
+      {
+        path: 'organizations',
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./organizations/UserOrganizations').then(i => callback(null, i.default));
+        },
+        childRoutes: [
+          {
+            path: 'create',
+            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+              import('./organizations/CreateOrganizationForm').then(i => callback(null, i.default));
+            }
+          }
+        ]
+      }
+    ]
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/routes.js b/server/sonar-web/src/main/js/apps/background-tasks/routes.js
deleted file mode 100644 (file)
index 3f46e62..0000000
+++ /dev/null
@@ -1,28 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/BackgroundTasksApp').then(i => callback(null, { component: i.default }));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/routes.ts b/server/sonar-web/src/main/js/apps/background-tasks/routes.ts
new file mode 100644 (file)
index 0000000..88e0a5e
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/BackgroundTasksApp').then(i => callback(null, { component: i.default }));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/code/routes.js b/server/sonar-web/src/main/js/apps/code/routes.js
deleted file mode 100644 (file)
index 4de4165..0000000
+++ /dev/null
@@ -1,28 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/App').then(i => callback(null, { component: i.default }));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/code/routes.ts b/server/sonar-web/src/main/js/apps/code/routes.ts
new file mode 100644 (file)
index 0000000..fcffeff
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/App').then(i => callback(null, { component: i.default }));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/routes.js b/server/sonar-web/src/main/js/apps/coding-rules/routes.js
deleted file mode 100644 (file)
index af91a4e..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-const routes = [
-  {
-    indexRoute: {
-      getComponent(_, callback) {
-        import('./components/CodingRulesAppContainer').then(i => callback(null, i.default));
-      }
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/routes.ts b/server/sonar-web/src/main/js/apps/coding-rules/routes.ts
new file mode 100644 (file)
index 0000000..798ee29
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { RouterState, RouteComponent } from 'react-router';
+
+const routes = [
+  {
+    indexRoute: {
+      getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+        import('./components/CodingRulesAppContainer').then(i => callback(null, i.default));
+      }
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.js b/server/sonar-web/src/main/js/apps/component-measures/routes.js
deleted file mode 100644 (file)
index c0abc9f..0000000
+++ /dev/null
@@ -1,64 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
-    }
-  },
-  {
-    path: 'domain/:domainName',
-    onEnter(nextState, replace) {
-      replace({
-        pathname: '/component_measures',
-        query: {
-          ...nextState.location.query,
-          metric: nextState.params.domainName
-        }
-      });
-    }
-  },
-  {
-    path: 'metric/:metricKey(/:view)',
-    onEnter(nextState, replace) {
-      if (nextState.params.view === 'history') {
-        replace({
-          pathname: '/project/activity',
-          query: {
-            id: nextState.location.query.id,
-            graph: 'custom',
-            custom_metrics: nextState.params.metricKey
-          }
-        });
-      } else {
-        replace({
-          pathname: '/component_measures',
-          query: {
-            ...nextState.location.query,
-            metric: nextState.params.metricKey,
-            view: nextState.params.view
-          }
-        });
-      }
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.ts b/server/sonar-web/src/main/js/apps/component-measures/routes.ts
new file mode 100644 (file)
index 0000000..9f3813c
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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 { RouterState, IndexRouteProps, RedirectFunction } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
+    }
+  },
+  {
+    path: 'domain/:domainName',
+    onEnter(nextState: RouterState, replace: RedirectFunction) {
+      replace({
+        pathname: '/component_measures',
+        query: {
+          ...nextState.location.query,
+          metric: nextState.params.domainName
+        }
+      });
+    }
+  },
+  {
+    path: 'metric/:metricKey(/:view)',
+    onEnter(nextState: RouterState, replace: RedirectFunction) {
+      if (nextState.params.view === 'history') {
+        replace({
+          pathname: '/project/activity',
+          query: {
+            id: nextState.location.query.id,
+            graph: 'custom',
+            custom_metrics: nextState.params.metricKey
+          }
+        });
+      } else {
+        replace({
+          pathname: '/component_measures',
+          query: {
+            ...nextState.location.query,
+            metric: nextState.params.metricKey,
+            view: nextState.params.view
+          }
+        });
+      }
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/component/components/App.js b/server/sonar-web/src/main/js/apps/component/components/App.js
deleted file mode 100644 (file)
index e8c1c9f..0000000
+++ /dev/null
@@ -1,63 +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 SourceViewer from '../../../components/SourceViewer/SourceViewer';
-
-export default class App extends React.PureComponent {
-  /*:: props: {
-    location: {
-      query: {
-        id: string,
-        line?: string
-      }
-    }
-  };
-*/
-  scrollToLine = () => {
-    const { line } = this.props.location.query;
-    if (line) {
-      const row = document.querySelector(`.source-line[data-line-number="${line}"]`);
-      if (row) {
-        const rect = row.getBoundingClientRect();
-        const topOffset = window.innerHeight / 2 - 60;
-        const goal = rect.top - topOffset;
-        window.scrollTo(0, goal);
-      }
-    }
-  };
-
-  render() {
-    const { id, line } = this.props.location.query;
-
-    const finalLine = line != null ? Number(line) : null;
-
-    return (
-      <div className="page page-limited">
-        <SourceViewer
-          aroundLine={finalLine}
-          component={id}
-          highlightedLine={finalLine}
-          onLoaded={this.scrollToLine}
-        />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/component/components/App.tsx b/server/sonar-web/src/main/js/apps/component/components/App.tsx
new file mode 100644 (file)
index 0000000..a1fb59f
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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 SourceViewer from '../../../components/SourceViewer/SourceViewer';
+
+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) {
+      const row = document.querySelector(`.source-line[data-line-number="${line}"]`);
+      if (row) {
+        const rect = row.getBoundingClientRect();
+        const topOffset = window.innerHeight / 2 - 60;
+        const goal = rect.top - topOffset;
+        window.scrollTo(0, goal);
+      }
+    }
+  };
+
+  render() {
+    const { id, line } = this.props.location.query;
+
+    const finalLine = line != null ? Number(line) : null;
+
+    return (
+      <div className="page page-limited">
+        <SourceViewer
+          aroundLine={finalLine}
+          component={id}
+          highlightedLine={finalLine}
+          onLoaded={this.scrollToLine}
+        />
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/component/routes.js b/server/sonar-web/src/main/js/apps/component/routes.js
deleted file mode 100644 (file)
index 9943b1d..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-const routes = [
-  {
-    indexRoute: {
-      getComponent(_, callback) {
-        import('./components/App').then(i => callback(null, i.default));
-      }
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/component/routes.ts b/server/sonar-web/src/main/js/apps/component/routes.ts
new file mode 100644 (file)
index 0000000..c5105ee
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { RouterState, RouteComponent } from 'react-router';
+
+const routes = [
+  {
+    indexRoute: {
+      getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+        import('./components/App').then(i => callback(null, i.default));
+      }
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/routes.js b/server/sonar-web/src/main/js/apps/custom-measures/routes.js
deleted file mode 100644 (file)
index e3aee20..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/CustomMeasuresAppContainer').then(i =>
-        callback(null, { component: i.default })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/custom-measures/routes.ts b/server/sonar-web/src/main/js/apps/custom-measures/routes.ts
new file mode 100644 (file)
index 0000000..ad092ba
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/CustomMeasuresAppContainer').then(i =>
+        callback(null, { component: i.default })
+      );
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/groups/routes.js b/server/sonar-web/src/main/js/apps/groups/routes.js
deleted file mode 100644 (file)
index 8829f80..0000000
+++ /dev/null
@@ -1,33 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      Promise.all([
-        import('./components/GroupsAppContainer').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([GroupsAppContainer, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(GroupsAppContainer) })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/groups/routes.ts b/server/sonar-web/src/main/js/apps/groups/routes.ts
new file mode 100644 (file)
index 0000000..80ee598
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      Promise.all([
+        import('./components/GroupsAppContainer').then(i => i.default),
+        import('../organizations/forSingleOrganization').then(i => i.default)
+      ]).then(([GroupsAppContainer, forSingleOrganization]) =>
+        callback(null, { component: forSingleOrganization(GroupsAppContainer) })
+      );
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/issues/routes.js b/server/sonar-web/src/main/js/apps/issues/routes.js
deleted file mode 100644 (file)
index ffcdcb7..0000000
+++ /dev/null
@@ -1,32 +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 { onEnter } from './redirects';
-
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/AppContainer').then(i =>
-        callback(null, { component: i.default, onEnter })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/issues/routes.ts b/server/sonar-web/src/main/js/apps/issues/routes.ts
new file mode 100644 (file)
index 0000000..e915d90
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+import { onEnter } from './redirects';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/AppContainer').then(i =>
+        callback(null, { component: i.default, onEnter })
+      );
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.js b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.js
deleted file mode 100644 (file)
index bf151b8..0000000
+++ /dev/null
@@ -1,31 +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 init from '../init';
-
-export default class MaintenanceAppContainer extends React.PureComponent {
-  componentDidMount() {
-    init(this.refs.container, false);
-  }
-
-  render() {
-    return <div ref="container" />;
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/MaintenanceAppContainer.tsx
new file mode 100644 (file)
index 0000000..923d805
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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 init from '../init';
+
+export default class MaintenanceAppContainer extends React.PureComponent {
+  componentDidMount() {
+    init(this.refs.container, false);
+  }
+
+  render() {
+    return <div ref="container" />;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.js b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.js
deleted file mode 100644 (file)
index c284927..0000000
+++ /dev/null
@@ -1,31 +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 init from '../init';
-
-export default class SetupAppContainer extends React.PureComponent {
-  componentDidMount() {
-    init(this.refs.container, true);
-  }
-
-  render() {
-    return <div ref="container" />;
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/SetupAppContainer.tsx
new file mode 100644 (file)
index 0000000..398ebb6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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 init from '../init';
+
+export default class SetupAppContainer extends React.PureComponent {
+  componentDidMount() {
+    init(this.refs.container, true);
+  }
+
+  render() {
+    return <div ref="container" />;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/maintenance/routes.js b/server/sonar-web/src/main/js/apps/maintenance/routes.js
deleted file mode 100644 (file)
index e6b19e9..0000000
+++ /dev/null
@@ -1,27 +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 { IndexRoute } from 'react-router';
-import MaintenanceAppContainer from './components/MaintenanceAppContainer';
-import SetupAppContainer from './components/SetupAppContainer';
-
-export const maintenanceRoutes = <IndexRoute component={MaintenanceAppContainer} />;
-
-export const setupRoutes = <IndexRoute component={SetupAppContainer} />;
diff --git a/server/sonar-web/src/main/js/apps/maintenance/routes.tsx b/server/sonar-web/src/main/js/apps/maintenance/routes.tsx
new file mode 100644 (file)
index 0000000..df6cd94
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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 { IndexRoute } from 'react-router';
+import MaintenanceAppContainer from './components/MaintenanceAppContainer';
+import SetupAppContainer from './components/SetupAppContainer';
+
+export const maintenanceRoutes = <IndexRoute component={MaintenanceAppContainer} />;
+
+export const setupRoutes = <IndexRoute component={SetupAppContainer} />;
diff --git a/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js b/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js
deleted file mode 100644 (file)
index f5f031e..0000000
+++ /dev/null
@@ -1,38 +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 Helmet from 'react-helmet';
-import init from '../init';
-import { translate } from '../../../helpers/l10n';
-
-export default class MetricsAppContainer extends React.PureComponent {
-  componentDidMount() {
-    init(this.refs.container);
-  }
-
-  render() {
-    return (
-      <div>
-        <Helmet title={translate('custom_metrics.page')} />
-        <div ref="container" />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx b/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.tsx
new file mode 100644 (file)
index 0000000..0bf49c7
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 Helmet from 'react-helmet';
+import init from '../init';
+import { translate } from '../../../helpers/l10n';
+
+export default class MetricsAppContainer extends React.PureComponent {
+  componentDidMount() {
+    init(this.refs.container);
+  }
+
+  render() {
+    return (
+      <div>
+        <Helmet title={translate('custom_metrics.page')} />
+        <div ref="container" />
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/metrics/routes.js b/server/sonar-web/src/main/js/apps/metrics/routes.js
deleted file mode 100644 (file)
index 637dba7..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/MetricsAppContainer').then(i =>
-        callback(null, { component: i.default })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/metrics/routes.ts b/server/sonar-web/src/main/js/apps/metrics/routes.ts
new file mode 100644 (file)
index 0000000..c52951e
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/MetricsAppContainer').then(i =>
+        callback(null, { component: i.default })
+      );
+    }
+  }
+];
+
+export default routes;
index 247c125198618c142f1604a7741bab12d5763d02..326b9ecefb11ea8f6696db24911915d9361cd53c 100644 (file)
@@ -27,8 +27,6 @@ exports[`renders 2`] = `
     overview.quality_gate
     <Level
       level="ERROR"
-      muted={false}
-      small={false}
     />
   </h2>
   <div
diff --git a/server/sonar-web/src/main/js/apps/overview/routes.js b/server/sonar-web/src/main/js/apps/overview/routes.js
deleted file mode 100644 (file)
index 02a7323..0000000
+++ /dev/null
@@ -1,28 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/overview/routes.ts b/server/sonar-web/src/main/js/apps/overview/routes.ts
new file mode 100644 (file)
index 0000000..21e41c6
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.js b/server/sonar-web/src/main/js/apps/permission-templates/routes.js
deleted file mode 100644 (file)
index ce1b267..0000000
+++ /dev/null
@@ -1,33 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      Promise.all([
-        import('./components/AppContainer').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([AppContainer, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(AppContainer) })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/permission-templates/routes.ts b/server/sonar-web/src/main/js/apps/permission-templates/routes.ts
new file mode 100644 (file)
index 0000000..f00dc43
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      Promise.all([
+        import('./components/AppContainer').then(i => i.default),
+        import('../organizations/forSingleOrganization').then(i => i.default)
+      ]).then(([AppContainer, forSingleOrganization]) =>
+        callback(null, { component: forSingleOrganization(AppContainer) })
+      );
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/permissions/routes.js b/server/sonar-web/src/main/js/apps/permissions/routes.js
deleted file mode 100644 (file)
index 631a269..0000000
+++ /dev/null
@@ -1,41 +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.
- */
-export const globalPermissionsRoutes = [
-  {
-    getIndexRoute(_, callback) {
-      Promise.all([
-        import('./global/components/App').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([App, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(App) })
-      );
-    }
-  }
-];
-
-export const projectPermissionsRoutes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./project/components/AppContainer').then(i =>
-        callback(null, { component: i.default })
-      );
-    }
-  }
-];
diff --git a/server/sonar-web/src/main/js/apps/permissions/routes.ts b/server/sonar-web/src/main/js/apps/permissions/routes.ts
new file mode 100644 (file)
index 0000000..85a62be
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+export const globalPermissionsRoutes = [
+  {
+    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)
+      ]).then(([App, forSingleOrganization]) =>
+        callback(null, { component: forSingleOrganization(App) })
+      );
+    }
+  }
+];
+
+export const projectPermissionsRoutes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./project/components/AppContainer').then(i =>
+        callback(null, { component: i.default })
+      );
+    }
+  }
+];
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.js b/server/sonar-web/src/main/js/apps/projectActivity/routes.js
deleted file mode 100644 (file)
index 11233a7..0000000
+++ /dev/null
@@ -1,30 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/ProjectActivityAppContainer').then(i =>
-        callback(null, { component: i.default })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.ts b/server/sonar-web/src/main/js/apps/projectActivity/routes.ts
new file mode 100644 (file)
index 0000000..47d7bda
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/ProjectActivityAppContainer').then(i =>
+        callback(null, { component: i.default })
+      );
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/routes.js b/server/sonar-web/src/main/js/apps/projects-admin/routes.js
deleted file mode 100644 (file)
index d179583..0000000
+++ /dev/null
@@ -1,33 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      Promise.all([
-        import('./AppContainer').then(i => i.default),
-        import('../organizations/forSingleOrganization').then(i => i.default)
-      ]).then(([AppContainer, forSingleOrganization]) =>
-        callback(null, { component: forSingleOrganization(AppContainer) })
-      );
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projects-admin/routes.ts b/server/sonar-web/src/main/js/apps/projects-admin/routes.ts
new file mode 100644 (file)
index 0000000..447c6ae
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      Promise.all([
+        import('./AppContainer').then(i => i.default),
+        import('../organizations/forSingleOrganization').then(i => i.default)
+      ]).then(([AppContainer, forSingleOrganization]) =>
+        callback(null, { component: forSingleOrganization(AppContainer) })
+      );
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projects/components/App.js b/server/sonar-web/src/main/js/apps/projects/components/App.js
deleted file mode 100644 (file)
index 83b097e..0000000
+++ /dev/null
@@ -1,45 +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';
-
-export default class App extends React.PureComponent {
-  componentDidMount() {
-    const elem = document.querySelector('html');
-    if (elem) {
-      elem.classList.add('dashboard-page');
-    }
-  }
-
-  componentWillUnmount() {
-    const elem = document.querySelector('html');
-    if (elem) {
-      elem.classList.remove('dashboard-page');
-    }
-  }
-
-  render() {
-    return (
-      <div id="projects-page">
-        {this.props.children}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/App.tsx b/server/sonar-web/src/main/js/apps/projects/components/App.tsx
new file mode 100644 (file)
index 0000000..471331f
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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';
+
+export default class App extends React.PureComponent {
+  componentDidMount() {
+    const elem = document.querySelector('html');
+    if (elem) {
+      elem.classList.add('dashboard-page');
+    }
+  }
+
+  componentWillUnmount() {
+    const elem = document.querySelector('html');
+    if (elem) {
+      elem.classList.remove('dashboard-page');
+    }
+  }
+
+  render() {
+    return (
+      <div id="projects-page">
+        {this.props.children}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/routes.js b/server/sonar-web/src/main/js/apps/projects/routes.js
deleted file mode 100644 (file)
index 1c3c76c..0000000
+++ /dev/null
@@ -1,52 +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 { saveAll } from '../../helpers/storage';
-
-const routes = [
-  {
-    getComponent(_, callback) {
-      import('./components/App').then(i => callback(null, i.default));
-    },
-    childRoutes: [
-      {
-        getIndexRoute(_, callback) {
-          import('./components/DefaultPageSelector').then(i =>
-            callback(null, { component: i.default })
-          );
-        }
-      },
-      {
-        path: 'all',
-        onEnter(_, replace) {
-          saveAll();
-          replace('/projects');
-        }
-      },
-      {
-        path: 'favorite',
-        getComponent(_, callback) {
-          import('./components/FavoriteProjectsContainer').then(i => callback(null, i.default));
-        }
-      }
-    ]
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/projects/routes.ts b/server/sonar-web/src/main/js/apps/projects/routes.ts
new file mode 100644 (file)
index 0000000..c6f3df3
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 { RouterState, IndexRouteProps, RouteComponent, RedirectFunction } from 'react-router';
+import { saveAll } from '../../helpers/storage';
+
+const routes = [
+  {
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/App').then(i => callback(null, i.default));
+    },
+    childRoutes: [
+      {
+        getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+          import('./components/DefaultPageSelector').then(i =>
+            callback(null, { component: i.default })
+          );
+        }
+      },
+      {
+        path: 'all',
+        onEnter(_: RouterState, replace: RedirectFunction) {
+          saveAll();
+          replace('/projects');
+        }
+      },
+      {
+        path: 'favorite',
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./components/FavoriteProjectsContainer').then(i => callback(null, i.default));
+        }
+      }
+    ]
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/routes.js b/server/sonar-web/src/main/js/apps/quality-gates/routes.js
deleted file mode 100644 (file)
index 8664f1f..0000000
+++ /dev/null
@@ -1,41 +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.
- */
-const routes = [
-  {
-    getComponent(_, callback) {
-      import('./containers/QualityGatesAppContainer').then(i => callback(null, i.default));
-    },
-    childRoutes: [
-      {
-        getIndexRoute(_, callback) {
-          import('./components/Intro').then(i => callback(null, { component: i.default }));
-        }
-      },
-      {
-        path: 'show/:id',
-        getComponent(_, callback) {
-          import('./containers/DetailsContainer').then(i => callback(null, i.default));
-        }
-      }
-    ]
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/routes.ts b/server/sonar-web/src/main/js/apps/quality-gates/routes.ts
new file mode 100644 (file)
index 0000000..f1d203b
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { RouterState, IndexRouteProps, RouteComponent } from 'react-router';
+
+const routes = [
+  {
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./containers/QualityGatesAppContainer').then(i => callback(null, i.default));
+    },
+    childRoutes: [
+      {
+        getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+          import('./components/Intro').then(i => callback(null, { component: i.default }));
+        }
+      },
+      {
+        path: 'show/:id',
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./containers/DetailsContainer').then(i => callback(null, i.default));
+        }
+      }
+    ]
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.js.snap
deleted file mode 100644 (file)
index 63a0a9e..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`#sortProfiles should sort by name 1`] = `
-Array [
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile1",
-    "name": "profile1",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile2",
-    "name": "profile2",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile3",
-    "name": "profile3",
-    "parentKey": undefined,
-  },
-]
-`;
-
-exports[`#sortProfiles should sort single branch 1`] = `
-Array [
-  Object {
-    "childrenCount": 1,
-    "depth": 1,
-    "key": "profile1",
-    "name": "profile1",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 1,
-    "depth": 2,
-    "key": "profile3",
-    "name": "profile3",
-    "parentKey": "profile1",
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 3,
-    "key": "profile2",
-    "name": "profile2",
-    "parentKey": "profile3",
-  },
-]
-`;
-
-exports[`#sortProfiles should sort when no parents 1`] = `
-Array [
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile1",
-    "name": "profile1",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile2",
-    "name": "profile2",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile3",
-    "name": "profile3",
-    "parentKey": undefined,
-  },
-]
-`;
-
-exports[`#sortProfiles should sort with children 1`] = `
-Array [
-  Object {
-    "childrenCount": 2,
-    "depth": 1,
-    "key": "parent",
-    "name": "parent",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 2,
-    "key": "child1",
-    "name": "child1",
-    "parentKey": "parent",
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 2,
-    "key": "child2",
-    "name": "child2",
-    "parentKey": "parent",
-  },
-]
-`;
-
-exports[`#sortProfiles sorts partial set of inherited profiles 1`] = `
-Array [
-  Object {
-    "childrenCount": 0,
-    "depth": 1,
-    "key": "profile1",
-    "name": "profile1",
-    "parentKey": "x",
-  },
-  Object {
-    "childrenCount": 1,
-    "depth": 1,
-    "key": "profile2",
-    "name": "profile2",
-    "parentKey": undefined,
-  },
-  Object {
-    "childrenCount": 0,
-    "depth": 2,
-    "key": "profile3",
-    "name": "profile3",
-    "parentKey": "profile2",
-  },
-]
-`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.ts.snap b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/__snapshots__/utils-test.ts.snap
new file mode 100644 (file)
index 0000000..a833c80
--- /dev/null
@@ -0,0 +1,143 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`#sortProfiles should sort by name 1`] = `
+Array [
+  Object {
+    "childrenCount": 0,
+    "depth": 1,
+    "key": "profile1",
+    "name": "profile1",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 1,
+    "key": "profile2",
+    "name": "profile2",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 1,
+    "key": "profile3",
+    "name": "profile3",
+    "parentKey": undefined,
+  },
+]
+`;
+
+exports[`#sortProfiles should sort single branch 1`] = `
+Array [
+  Object {
+    "childrenCount": 1,
+    "depth": 1,
+    "key": "profile1",
+    "name": "profile1",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 1,
+    "depth": 2,
+    "key": "profile3",
+    "name": "profile3",
+    "parentKey": "profile1",
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 3,
+    "key": "profile2",
+    "name": "profile2",
+    "parentKey": "profile3",
+  },
+]
+`;
+
+exports[`#sortProfiles should sort when no parents 1`] = `
+Array [
+  Object {
+    "childrenCount": 0,
+    "depth": 1,
+    "key": "profile1",
+    "name": "profile1",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 1,
+    "key": "profile2",
+    "name": "profile2",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 1,
+    "key": "profile3",
+    "name": "profile3",
+    "parentKey": undefined,
+  },
+]
+`;
+
+exports[`#sortProfiles should sort with children 1`] = `
+Array [
+  Object {
+    "childrenCount": 2,
+    "depth": 1,
+    "key": "parent",
+    "name": "parent",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 2,
+    "key": "child1",
+    "name": "child1",
+    "parentKey": "parent",
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 2,
+    "key": "child2",
+    "name": "child2",
+    "parentKey": "parent",
+  },
+]
+`;
+
+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,
+    "depth": 1,
+    "key": "profile1",
+    "name": "profile1",
+    "parentKey": "x",
+  },
+  Object {
+    "childrenCount": 1,
+    "depth": 1,
+    "key": "profile2",
+    "name": "profile2",
+    "parentKey": undefined,
+  },
+  Object {
+    "childrenCount": 0,
+    "depth": 2,
+    "key": "profile3",
+    "name": "profile3",
+    "parentKey": "profile2",
+  },
+]
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.js
deleted file mode 100644 (file)
index 57157f7..0000000
+++ /dev/null
@@ -1,64 +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 { sortProfiles } from '../utils';
-
-function createProfile(key, parentKey) {
-  return { name: key, key, parentKey };
-}
-
-describe('#sortProfiles', () => {
-  it('should sort when no parents', () => {
-    const profile1 = createProfile('profile1');
-    const profile2 = createProfile('profile2');
-    const profile3 = createProfile('profile3');
-    expect(sortProfiles([profile1, profile2, profile3])).toMatchSnapshot();
-  });
-
-  it('should sort by name', () => {
-    const profile1 = createProfile('profile1');
-    const profile2 = createProfile('profile2');
-    const profile3 = createProfile('profile3');
-    expect(sortProfiles([profile3, profile1, profile2])).toMatchSnapshot();
-  });
-
-  it('should sort with children', () => {
-    const child1 = createProfile('child1', 'parent');
-    const child2 = createProfile('child2', 'parent');
-    const parent = createProfile('parent');
-    expect(sortProfiles([child1, child2, parent])).toMatchSnapshot();
-  });
-
-  it('should sort single branch', () => {
-    const profile1 = createProfile('profile1');
-    const profile2 = createProfile('profile2', 'profile3');
-    const profile3 = createProfile('profile3', 'profile1');
-    expect(sortProfiles([profile3, profile2, profile1])).toMatchSnapshot();
-  });
-
-  it('sorts partial set of inherited profiles', () => {
-    const foo = createProfile('foo', 'bar');
-    expect(sortProfiles([foo]), ['foo']);
-
-    const profile1 = createProfile('profile1', 'x');
-    const profile2 = createProfile('profile2');
-    const profile3 = createProfile('profile3', 'profile2');
-    expect(sortProfiles([profile1, profile2, profile3])).toMatchSnapshot();
-  });
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/quality-profiles/__tests__/utils-test.ts
new file mode 100644 (file)
index 0000000..433702e
--- /dev/null
@@ -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 { sortProfiles } from '../utils';
+import { IProfile } from '../types';
+
+function createProfile(key: string, parentKey?: string) {
+  return { name: key, key, parentKey } as IProfile;
+}
+
+describe('#sortProfiles', () => {
+  it('should sort when no parents', () => {
+    const profile1 = createProfile('profile1');
+    const profile2 = createProfile('profile2');
+    const profile3 = createProfile('profile3');
+    expect(sortProfiles([profile1, profile2, profile3])).toMatchSnapshot();
+  });
+
+  it('should sort by name', () => {
+    const profile1 = createProfile('profile1');
+    const profile2 = createProfile('profile2');
+    const profile3 = createProfile('profile3');
+    expect(sortProfiles([profile3, profile1, profile2])).toMatchSnapshot();
+  });
+
+  it('should sort with children', () => {
+    const child1 = createProfile('child1', 'parent');
+    const child2 = createProfile('child2', 'parent');
+    const parent = createProfile('parent');
+    expect(sortProfiles([child1, child2, parent])).toMatchSnapshot();
+  });
+
+  it('should sort single branch', () => {
+    const profile1 = createProfile('profile1');
+    const profile2 = createProfile('profile2', 'profile3');
+    const profile3 = createProfile('profile3', 'profile1');
+    expect(sortProfiles([profile3, profile2, profile1])).toMatchSnapshot();
+  });
+
+  it('sorts partial set of inherited profiles', () => {
+    const foo = createProfile('foo', 'bar');
+    expect(sortProfiles([foo])).toMatchSnapshot();
+
+    const profile1 = createProfile('profile1', 'x');
+    const profile2 = createProfile('profile2');
+    const profile3 = createProfile('profile3', 'profile2');
+    expect(sortProfiles([profile1, profile2, profile3])).toMatchSnapshot();
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js
deleted file mode 100644 (file)
index 7eeb76e..0000000
+++ /dev/null
@@ -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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx
new file mode 100644 (file)
index 0000000..6c15292
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js
deleted file mode 100644 (file)
index 6bca8f5..0000000
+++ /dev/null
@@ -1,204 +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 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'; */
-
-/*::
-type 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; */
-
-  static contextTypes = {
-    router: PropTypes.object
-  };
-
-  state /*: State */ = {
-    loading: true
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadChangelog();
-  }
-
-  componentDidUpdate(prevProps /*: Props */) {
-    if (prevProps.location !== this.props.location) {
-      this.loadChangelog();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadChangelog() {
-    this.setState({ loading: true });
-    const { query } = this.props.location;
-    const data /*: Object */ = { profileKey: this.props.profile.key };
-    if (query.since) {
-      data.since = query.since;
-    }
-    if (query.to) {
-      data.to = query.to;
-    }
-
-    getProfileChangelog(data).then(r => {
-      if (this.mounted) {
-        this.setState({
-          events: r.events,
-          total: r.total,
-          page: r.p,
-          loading: false
-        });
-      }
-    });
-  }
-
-  loadMore(e /*: SyntheticInputEvent */) {
-    e.preventDefault();
-    e.target.blur();
-
-    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 => {
-      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 */) => {
-    const path = getProfileChangelogPath(
-      this.props.profile.name,
-      this.props.profile.language,
-      this.props.organization,
-      {
-        since: fromDate,
-        to: this.props.location.query.to
-      }
-    );
-    this.context.router.push(path);
-  };
-
-  handleToDateChange = (toDate /*: string | void */) => {
-    const path = getProfileChangelogPath(
-      this.props.profile.name,
-      this.props.profile.language,
-      this.props.organization,
-      {
-        since: this.props.location.query.since,
-        to: toDate
-      }
-    );
-    this.context.router.push(path);
-  };
-
-  handleReset = () => {
-    const path = getProfileChangelogPath(
-      this.props.profile.name,
-      this.props.profile.language,
-      this.props.organization
-    );
-    this.context.router.push(path);
-  };
-
-  render() {
-    const { query } = this.props.location;
-
-    const shouldDisplayFooter =
-      this.state.events != null &&
-      this.state.total != null &&
-      this.state.events.length < this.state.total;
-
-    return (
-      <div className="quality-profile-box js-profile-changelog">
-        <header className="spacer-bottom">
-          <ChangelogSearch
-            fromDate={query.since}
-            toDate={query.to}
-            onFromDateChange={this.handleFromDateChange}
-            onToDateChange={this.handleToDateChange}
-            onReset={this.handleReset}
-          />
-
-          {this.state.loading && <i className="spinner spacer-left" />}
-        </header>
-
-        {this.state.events != null && this.state.events.length === 0 && <ChangelogEmpty />}
-
-        {this.state.events != null &&
-          this.state.events.length > 0 &&
-          <Changelog events={this.state.events} organization={this.props.organization} />}
-
-        {shouldDisplayFooter &&
-          <footer className="text-center spacer-top small">
-            <a href="#" onClick={this.loadMore.bind(this)}>
-              {translate('show_more')}
-            </a>
-          </footer>}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.tsx
new file mode 100644 (file)
index 0000000..aa54dd0
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * 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 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 { IProfile, IProfileChangelogEvent } from '../types';
+
+interface Props {
+  location: {
+    query: {
+      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 = {
+    loading: true
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadChangelog();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.location !== this.props.location) {
+      this.loadChangelog();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadChangelog() {
+    this.setState({ loading: true });
+    const { query } = this.props.location;
+    const data: any = { profileKey: this.props.profile.key };
+    if (query.since) {
+      data.since = query.since;
+    }
+    if (query.to) {
+      data.to = query.to;
+    }
+
+    getProfileChangelog(data).then((r: any) => {
+      if (this.mounted) {
+        this.setState({
+          events: r.events,
+          total: r.total,
+          page: r.p,
+          loading: false
+        });
+      }
+    });
+  }
+
+  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;
+      }
+
+      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
+          });
+        }
+      });
+    }
+  }
+
+  handleFromDateChange = (fromDate?: string) => {
+    const path = getProfileChangelogPath(
+      this.props.profile.name,
+      this.props.profile.language,
+      this.props.organization,
+      {
+        since: fromDate,
+        to: this.props.location.query.to
+      }
+    );
+    this.context.router.push(path);
+  };
+
+  handleToDateChange = (toDate?: string) => {
+    const path = getProfileChangelogPath(
+      this.props.profile.name,
+      this.props.profile.language,
+      this.props.organization,
+      {
+        since: this.props.location.query.since,
+        to: toDate
+      }
+    );
+    this.context.router.push(path);
+  };
+
+  handleReset = () => {
+    const path = getProfileChangelogPath(
+      this.props.profile.name,
+      this.props.profile.language,
+      this.props.organization
+    );
+    this.context.router.push(path);
+  };
+
+  render() {
+    const { query } = this.props.location;
+
+    const shouldDisplayFooter =
+      this.state.events != null &&
+      this.state.total != null &&
+      this.state.events.length < this.state.total;
+
+    return (
+      <div className="quality-profile-box js-profile-changelog">
+        <header className="spacer-bottom">
+          <ChangelogSearch
+            fromDate={query.since}
+            toDate={query.to}
+            onFromDateChange={this.handleFromDateChange}
+            onToDateChange={this.handleToDateChange}
+            onReset={this.handleReset}
+          />
+
+          {this.state.loading && <i className="spinner spacer-left" />}
+        </header>
+
+        {this.state.events != null && this.state.events.length === 0 && <ChangelogEmpty />}
+
+        {this.state.events != null &&
+          this.state.events.length > 0 &&
+          <Changelog events={this.state.events} organization={this.props.organization} />}
+
+        {shouldDisplayFooter &&
+          <footer className="text-center spacer-top small">
+            <a href="#" onClick={this.loadMore.bind(this)}>
+              {translate('show_more')}
+            </a>
+          </footer>}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js
deleted file mode 100644 (file)
index 96db0d8..0000000
+++ /dev/null
@@ -1,32 +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 { translate } from '../../../helpers/l10n';
-
-export default class ChangelogEmpty extends React.PureComponent {
-  render() {
-    return (
-      <div className="big-spacer-top">
-        {translate('no_results')}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.tsx
new file mode 100644 (file)
index 0000000..b8a9fba
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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 { translate } from '../../../helpers/l10n';
+
+export default function ChangelogEmpty() {
+  return (
+    <div className="big-spacer-top">
+      {translate('no_results')}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js
deleted file mode 100644 (file)
index c0abdb0..0000000
+++ /dev/null
@@ -1,66 +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 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; */
-
-  handleResetClick(e /*: SyntheticInputEvent */) {
-    e.preventDefault();
-    e.target.blur();
-    this.props.onReset();
-  }
-
-  render() {
-    return (
-      <div className="display-inline-block" id="quality-profile-changelog-form">
-        <DateInput
-          name="since"
-          value={this.props.fromDate}
-          placeholder="From"
-          onChange={this.props.onFromDateChange}
-        />
-        {' â€” '}
-        <DateInput
-          name="to"
-          value={this.props.toDate}
-          placeholder="To"
-          onChange={this.props.onToDateChange}
-        />
-        <button className="spacer-left" onClick={this.handleResetClick.bind(this)}>
-          {translate('reset_verb')}
-        </button>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.tsx
new file mode 100644 (file)
index 0000000..e59479f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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 DateInput from '../../../components/controls/DateInput';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  fromDate?: string;
+  toDate?: string;
+  onFromDateChange: () => void;
+  onReset: () => void;
+  onToDateChange: () => void;
+}
+
+export default class ChangelogSearch extends React.PureComponent<Props> {
+  handleResetClick(event: React.SyntheticEvent<HTMLElement>) {
+    event.preventDefault();
+    event.currentTarget.blur();
+    this.props.onReset();
+  }
+
+  render() {
+    return (
+      <div className="display-inline-block" id="quality-profile-changelog-form">
+        <DateInput
+          name="since"
+          value={this.props.fromDate}
+          placeholder="From"
+          onChange={this.props.onFromDateChange}
+        />
+        {' â€” '}
+        <DateInput
+          name="to"
+          value={this.props.toDate}
+          placeholder="To"
+          onChange={this.props.onToDateChange}
+        />
+        <button className="spacer-left" onClick={this.handleResetClick.bind(this)}>
+          {translate('reset_verb')}
+        </button>
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js
deleted file mode 100644 (file)
index c910ebb..0000000
+++ /dev/null
@@ -1,49 +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 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;
-
-    return (
-      <ul>
-        {Object.keys(changes).map(key =>
-          <li key={key}>
-            {key === 'severity'
-              ? <SeverityChange severity={changes[key]} />
-              : <ParameterChange name={key} value={changes[key]} />}
-          </li>
-        )}
-      </ul>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.tsx
new file mode 100644 (file)
index 0000000..c800e44
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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 SeverityChange from './SeverityChange';
+import ParameterChange from './ParameterChange';
+
+interface Props {
+  changes: { [change: string]: string | null };
+}
+
+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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js
deleted file mode 100644 (file)
index 9ee9d2b..0000000
+++ /dev/null
@@ -1,54 +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 { 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>
-      );
-    }
-
-    return (
-      <div style={{ whiteSpace: 'normal' }}>
-        {translateWithParameters('quality_profiles.parameter_set_to', name, value)}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.tsx
new file mode 100644 (file)
index 0000000..d62d11d
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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 { translateWithParameters } from '../../../helpers/l10n';
+
+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.changelog.parameter_reset_to_default_value',
+          name
+        )}
+      </div>
+    );
+  }
+
+  return (
+    <div style={{ whiteSpace: 'normal' }}>
+      {translateWithParameters('quality_profiles.parameter_set_to', name, value)}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js
deleted file mode 100644 (file)
index 546c466..0000000
+++ /dev/null
@@ -1,42 +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 SeverityHelper from '../../../components/shared/SeverityHelper';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  severity: ?string
-};
-*/
-
-export default class SeverityChange extends React.PureComponent {
-  /*:: props: Props; */
-
-  render() {
-    return (
-      <div>
-        {translate('quality_profiles.severity_set_to')}{' '}
-        <SeverityHelper severity={this.props.severity} />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.tsx
new file mode 100644 (file)
index 0000000..3c744e7
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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 SeverityHelper from '../../../components/shared/SeverityHelper';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  severity: string | null;
+}
+
+export default function SeverityChange({ severity }: Props) {
+  return (
+    <div>
+      {translate('quality_profiles.severity_set_to')} <SeverityHelper severity={severity} />
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.js
deleted file mode 100644 (file)
index 3277455..0000000
+++ /dev/null
@@ -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.
- */
-import { shallow } from 'enzyme';
-import React from 'react';
-import Changelog from '../Changelog';
-import ChangesList from '../ChangesList';
-
-function createEvent(overrides) {
-  return {
-    date: '2016-01-01',
-    authorName: 'John',
-    action: 'ACTIVATED',
-    ruleKey: 'squid1234',
-    ruleName: 'Do not do this',
-    params: {},
-    ...overrides
-  };
-}
-
-it('should render events', () => {
-  const events = [createEvent(), createEvent()];
-  const changelog = shallow(<Changelog events={events} />);
-  expect(changelog.find('tbody').find('tr').length).toBe(2);
-});
-
-it('should render event date', () => {
-  const events = [createEvent()];
-  const changelog = shallow(<Changelog events={events} />);
-  expect(changelog.text()).toContain('2016');
-});
-
-it('should render author', () => {
-  const events = [createEvent()];
-  const changelog = shallow(<Changelog events={events} />);
-  expect(changelog.text()).toContain('John');
-});
-
-it('should render system author', () => {
-  const events = [createEvent({ authorName: undefined })];
-  const changelog = shallow(<Changelog events={events} />);
-  expect(changelog.text()).toContain('System');
-});
-
-it('should render action', () => {
-  const events = [createEvent()];
-  const changelog = shallow(<Changelog events={events} />);
-  expect(changelog.text()).toContain('ACTIVATED');
-});
-
-it('should render rule', () => {
-  const events = [createEvent()];
-  const changelog = shallow(<Changelog events={events} />);
-  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 changesList = changelog.find(ChangesList);
-  expect(changesList.length).toBe(1);
-  expect(changesList.prop('changes')).toBe(params);
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/Changelog-test.tsx
new file mode 100644 (file)
index 0000000..ed8fb6b
--- /dev/null
@@ -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 { shallow } from 'enzyme';
+import * as React from 'react';
+import Changelog from '../Changelog';
+import ChangesList from '../ChangesList';
+import { IProfileChangelogEvent } from '../../types';
+
+function createEvent(overrides?: { [p: string]: any }): IProfileChangelogEvent {
+  return {
+    date: '2016-01-01',
+    authorName: 'John',
+    action: 'ACTIVATED',
+    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} 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} organization={null} />);
+  expect(changelog.text()).toContain('2016');
+});
+
+it('should render author', () => {
+  const events = [createEvent()];
+  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} organization={null} />);
+  expect(changelog.text()).toContain('System');
+});
+
+it('should render action', () => {
+  const events = [createEvent()];
+  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} 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} organization={null} />);
+  const changesList = changelog.find(ChangesList);
+  expect(changesList.length).toBe(1);
+  expect(changesList.prop('changes')).toBe(params);
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.js
deleted file mode 100644 (file)
index be461db..0000000
+++ /dev/null
@@ -1,60 +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 { shallow } from 'enzyme';
-import React from 'react';
-import ChangelogSearch from '../ChangelogSearch';
-import DateInput from '../../../../components/controls/DateInput';
-import { click } from '../../../../helpers/testUtils';
-
-it('should render DateInput', () => {
-  const onFromDateChange = jest.fn();
-  const onToDateChange = jest.fn();
-  const output = shallow(
-    <ChangelogSearch
-      fromDate="2016-01-01"
-      toDate="2016-05-05"
-      onFromDateChange={onFromDateChange}
-      onToDateChange={onToDateChange}
-      onReset={jest.fn()}
-    />
-  );
-  const dateInputs = output.find(DateInput);
-  expect(dateInputs.length).toBe(2);
-  expect(dateInputs.at(0).prop('value')).toBe('2016-01-01');
-  expect(dateInputs.at(0).prop('onChange')).toBe(onFromDateChange);
-  expect(dateInputs.at(1).prop('value')).toBe('2016-05-05');
-  expect(dateInputs.at(1).prop('onChange')).toBe(onToDateChange);
-});
-
-it('should reset', () => {
-  const onReset = jest.fn();
-  const output = shallow(
-    <ChangelogSearch
-      fromDate="2016-01-01"
-      toDate="2016-05-05"
-      onFromDateChange={jest.fn()}
-      onToDateChange={jest.fn()}
-      onReset={onReset}
-    />
-  );
-  expect(onReset).not.toBeCalled();
-  click(output.find('button'));
-  expect(onReset).toBeCalled();
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogSearch-test.tsx
new file mode 100644 (file)
index 0000000..78dd87a
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import ChangelogSearch from '../ChangelogSearch';
+import DateInput from '../../../../components/controls/DateInput';
+import { click } from '../../../../helpers/testUtils';
+
+it('should render DateInput', () => {
+  const onFromDateChange = jest.fn();
+  const onToDateChange = jest.fn();
+  const output = shallow(
+    <ChangelogSearch
+      fromDate="2016-01-01"
+      toDate="2016-05-05"
+      onFromDateChange={onFromDateChange}
+      onToDateChange={onToDateChange}
+      onReset={jest.fn()}
+    />
+  );
+  const dateInputs = output.find(DateInput);
+  expect(dateInputs.length).toBe(2);
+  expect(dateInputs.at(0).prop('value')).toBe('2016-01-01');
+  expect(dateInputs.at(0).prop('onChange')).toBe(onFromDateChange);
+  expect(dateInputs.at(1).prop('value')).toBe('2016-05-05');
+  expect(dateInputs.at(1).prop('onChange')).toBe(onToDateChange);
+});
+
+it('should reset', () => {
+  const onReset = jest.fn();
+  const output = shallow(
+    <ChangelogSearch
+      fromDate="2016-01-01"
+      toDate="2016-05-05"
+      onFromDateChange={jest.fn()}
+      onToDateChange={jest.fn()}
+      onReset={onReset}
+    />
+  );
+  expect(onReset).not.toBeCalled();
+  click(output.find('button'));
+  expect(onReset).toBeCalled();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.js
deleted file mode 100644 (file)
index 7ccb56e..0000000
+++ /dev/null
@@ -1,45 +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 { shallow } from 'enzyme';
-import React from 'react';
-import ChangesList from '../ChangesList';
-import SeverityChange from '../SeverityChange';
-import ParameterChange from '../ParameterChange';
-
-it('should render changes', () => {
-  const changes = { severity: 'BLOCKER', foo: 'bar' };
-  const output = shallow(<ChangesList changes={changes} />);
-  expect(output.find('li').length).toBe(2);
-});
-
-it('should render severity change', () => {
-  const changes = { severity: 'BLOCKER' };
-  const output = shallow(<ChangesList changes={changes} />).find(SeverityChange);
-  expect(output.length).toBe(1);
-  expect(output.prop('severity')).toBe('BLOCKER');
-});
-
-it('should render parameter change', () => {
-  const changes = { foo: 'bar' };
-  const output = shallow(<ChangesList changes={changes} />).find(ParameterChange);
-  expect(output.length).toBe(1);
-  expect(output.prop('name')).toBe('foo');
-  expect(output.prop('value')).toBe('bar');
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangesList-test.tsx
new file mode 100644 (file)
index 0000000..82ddc9a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import ChangesList from '../ChangesList';
+import SeverityChange from '../SeverityChange';
+import ParameterChange from '../ParameterChange';
+
+it('should render changes', () => {
+  const changes = { severity: 'BLOCKER', foo: 'bar' };
+  const output = shallow(<ChangesList changes={changes} />);
+  expect(output.find('li').length).toBe(2);
+});
+
+it('should render severity change', () => {
+  const changes = { severity: 'BLOCKER' };
+  const output = shallow(<ChangesList changes={changes} />).find(SeverityChange);
+  expect(output.length).toBe(1);
+  expect(output.prop('severity')).toBe('BLOCKER');
+});
+
+it('should render parameter change', () => {
+  const changes = { foo: 'bar' };
+  const output = shallow(<ChangesList changes={changes} />).find(ParameterChange);
+  expect(output.length).toBe(1);
+  expect(output.prop('name')).toBe('foo');
+  expect(output.prop('value')).toBe('bar');
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.js
deleted file mode 100644 (file)
index 25032b3..0000000
+++ /dev/null
@@ -1,28 +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 { shallow } from 'enzyme';
-import React from 'react';
-import ParameterChange from '../ParameterChange';
-
-it('should render different messages', () => {
-  const first = shallow(<ParameterChange name="foo" />);
-  const second = shallow(<ParameterChange name="foo" value="bar" />);
-  expect(first.text()).not.toBe(second.text());
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ParameterChange-test.tsx
new file mode 100644 (file)
index 0000000..de80150
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import ParameterChange from '../ParameterChange';
+
+it('should render different messages', () => {
+  const first = shallow(<ParameterChange name="foo" value={null} />);
+  const second = shallow(<ParameterChange name="foo" value="bar" />);
+  expect(first.text()).not.toBe(second.text());
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.js
deleted file mode 100644 (file)
index 8597dd0..0000000
+++ /dev/null
@@ -1,29 +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 { shallow } from 'enzyme';
-import React from 'react';
-import SeverityChange from '../SeverityChange';
-import SeverityHelper from '../../../../components/shared/SeverityHelper';
-
-it('should render SeverityHelper', () => {
-  const output = shallow(<SeverityChange severity="BLOCKER" />).find(SeverityHelper);
-  expect(output.length).toBe(1);
-  expect(output.prop('severity')).toBe('BLOCKER');
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/SeverityChange-test.tsx
new file mode 100644 (file)
index 0000000..bc5f655
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import SeverityChange from '../SeverityChange';
+import SeverityHelper from '../../../../components/shared/SeverityHelper';
+
+it('should render SeverityHelper', () => {
+  const output = shallow(<SeverityChange severity="BLOCKER" />).find(SeverityHelper);
+  expect(output.length).toBe(1);
+  expect(output.prop('severity')).toBe('BLOCKER');
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js
deleted file mode 100644 (file)
index 718d80d..0000000
+++ /dev/null
@@ -1,140 +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 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; */
-
-  static contextTypes = {
-    router: PropTypes.object
-  };
-
-  constructor(props /*: Props */) {
-    super(props);
-    this.state = { loading: false };
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadResults();
-  }
-
-  componentDidUpdate(prevProps /*: Props */) {
-    if (prevProps.profile !== this.props.profile || prevProps.location !== this.props.location) {
-      this.loadResults();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadResults() {
-    const { withKey } = this.props.location.query;
-    if (!withKey) {
-      this.setState({ left: undefined, loading: false });
-      return;
-    }
-
-    this.setState({ loading: true });
-    compareProfiles(this.props.profile.key, withKey).then(r => {
-      if (this.mounted) {
-        this.setState({
-          left: r.left,
-          right: r.right,
-          inLeft: r.inLeft,
-          inRight: r.inRight,
-          modified: r.modified,
-          loading: false
-        });
-      }
-    });
-  }
-
-  handleCompare = (withKey /*: string */) => {
-    const path = getProfileComparePath(
-      this.props.profile.name,
-      this.props.profile.language,
-      this.props.organization,
-      withKey
-    );
-    this.context.router.push(path);
-  };
-
-  render() {
-    const { profile, profiles, location } = this.props;
-    const { withKey } = location.query;
-    const { left, right, inLeft, inRight, modified } = this.state;
-
-    return (
-      <div className="quality-profile-box js-profile-comparison">
-        <header className="spacer-bottom">
-          <ComparisonForm
-            withKey={withKey}
-            profile={profile}
-            profiles={profiles}
-            onCompare={this.handleCompare}
-          />
-
-          {this.state.loading && <i className="spinner spacer-left" />}
-        </header>
-
-        {left != null &&
-          <ComparisonResults
-            left={left}
-            right={right}
-            inLeft={inLeft}
-            inRight={inRight}
-            modified={modified}
-            organization={this.props.organization}
-          />}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.tsx
new file mode 100644 (file)
index 0000000..003fd55
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import ComparisonForm from './ComparisonForm';
+import ComparisonResults from './ComparisonResults';
+import { compareProfiles } from '../../../api/quality-profiles';
+import { getProfileComparePath } from '../utils';
+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) {
+    super(props);
+    this.state = { loading: false };
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadResults();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.profile !== this.props.profile || prevProps.location !== this.props.location) {
+      this.loadResults();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadResults() {
+    const { withKey } = this.props.location.query;
+    if (!withKey) {
+      this.setState({ left: undefined, loading: false });
+      return;
+    }
+
+    this.setState({ loading: true });
+    compareProfiles(this.props.profile.key, withKey).then((r: any) => {
+      if (this.mounted) {
+        this.setState({
+          left: r.left,
+          right: r.right,
+          inLeft: r.inLeft,
+          inRight: r.inRight,
+          modified: r.modified,
+          loading: false
+        });
+      }
+    });
+  }
+
+  handleCompare = (withKey: string) => {
+    const path = getProfileComparePath(
+      this.props.profile.name,
+      this.props.profile.language,
+      this.props.organization,
+      withKey
+    );
+    this.context.router.push(path);
+  };
+
+  render() {
+    const { profile, profiles, location } = this.props;
+    const { withKey } = location.query;
+    const { left, right, inLeft, inRight, modified } = this.state;
+
+    return (
+      <div className="quality-profile-box js-profile-comparison">
+        <header className="spacer-bottom">
+          <ComparisonForm
+            withKey={withKey}
+            profile={profile}
+            profiles={profiles}
+            onCompare={this.handleCompare}
+          />
+
+          {this.state.loading && <i className="spinner spacer-left" />}
+        </header>
+
+        {left != null &&
+          inLeft != null &&
+          right != null &&
+          inRight != null &&
+          modified != null &&
+          <ComparisonResults
+            left={left}
+            right={right}
+            inLeft={inLeft}
+            inRight={inRight}
+            modified={modified}
+            organization={this.props.organization}
+          />}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js
deleted file mode 100644 (file)
index b3a19a3..0000000
+++ /dev/null
@@ -1,32 +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 { translate } from '../../../helpers/l10n';
-
-export default class ComparisonEmpty extends React.PureComponent {
-  render() {
-    return (
-      <div className="big-spacer-top">
-        {translate('quality_profile.empty_comparison')}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.tsx
new file mode 100644 (file)
index 0000000..a6a2c39
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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 { translate } from '../../../helpers/l10n';
+
+export default function ComparisonEmpty() {
+  return (
+    <div className="big-spacer-top">
+      {translate('quality_profile.empty_comparison')}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js
deleted file mode 100644 (file)
index c0a0086..0000000
+++ /dev/null
@@ -1,63 +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 Select from 'react-select';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-type Props = {
-  profile: Profile,
-  profiles: Array<Profile>,
-  onCompare: string => void,
-  withKey: string
-};
-*/
-
-export default class ComparisonForm extends React.PureComponent {
-  /*:: props: Props; */
-
-  handleChange(option /*: { value: string } */) {
-    this.props.onCompare(option.value);
-  }
-
-  render() {
-    const { profile, profiles, withKey } = this.props;
-    const options = profiles
-      .filter(p => p.language === profile.language && p !== profile)
-      .map(p => ({ value: p.key, label: p.name }));
-
-    return (
-      <div className="display-inline-block">
-        <label className="spacer-right">
-          {translate('quality_profiles.compare_with')}
-        </label>
-        <Select
-          value={withKey}
-          options={options}
-          clearable={false}
-          className="input-large"
-          onChange={this.handleChange.bind(this)}
-        />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.tsx
new file mode 100644 (file)
index 0000000..11c4fb2
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 Select from 'react-select';
+import { translate } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  profile: IProfile;
+  profiles: IProfile[];
+  onCompare: (rule: string) => void;
+  withKey?: string;
+}
+
+export default class ComparisonForm extends React.PureComponent<Props> {
+  handleChange(option: { value: string }) {
+    this.props.onCompare(option.value);
+  }
+
+  render() {
+    const { profile, profiles, withKey } = this.props;
+    const options = profiles
+      .filter(p => p.language === profile.language && p !== profile)
+      .map(p => ({ value: p.key, label: p.name }));
+
+    return (
+      <div className="display-inline-block">
+        <label className="spacer-right">
+          {translate('quality_profiles.compare_with')}
+        </label>
+        <Select
+          value={withKey}
+          options={options}
+          clearable={false}
+          className="input-large"
+          onChange={this.handleChange.bind(this)}
+        />
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js
deleted file mode 100644 (file)
index b13c88e..0000000
+++ /dev/null
@@ -1,192 +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 ComparisonEmpty from './ComparisonEmpty';
-import SeverityIcon from '../../../components/shared/SeverityIcon';
-import { translateWithParameters } from '../../../helpers/l10n';
-import { getRulesUrl } from '../../../helpers/urls';
-
-/*::
-type Params = { [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; */
-
-  renderRule(rule /*: { key: string, name: string } */, severity /*: string */) {
-    return (
-      <div>
-        <SeverityIcon severity={severity} />{' '}
-        <Link to={getRulesUrl({ rule_key: rule.key }, this.props.organization)}>{rule.name}</Link>
-      </div>
-    );
-  }
-
-  renderParameters(params /*: Params */) {
-    if (!params) {
-      return null;
-    }
-    return (
-      <ul>
-        {Object.keys(params).map(key =>
-          <li key={key} className="spacer-top">
-            <code>
-              {key}
-              {': '}
-              {params[key]}
-            </code>
-          </li>
-        )}
-      </ul>
-    );
-  }
-
-  renderLeft() {
-    if (this.props.inLeft.length === 0) {
-      return null;
-    }
-    const header = (
-      <tr key="left-header">
-        <td>
-          <h6>
-            {translateWithParameters(
-              'quality_profiles.x_rules_only_in',
-              this.props.inLeft.length
-            )}{' '}
-            {this.props.left.name}
-          </h6>
-        </td>
-        <td>&nbsp;</td>
-      </tr>
-    );
-    const rows = this.props.inLeft.map(rule =>
-      <tr key={`left-${rule.key}`} className="js-comparison-in-left">
-        <td>
-          {this.renderRule(rule, rule.severity)}
-        </td>
-        <td>&nbsp;</td>
-      </tr>
-    );
-    return [header, ...rows];
-  }
-
-  renderRight() {
-    if (this.props.inRight.length === 0) {
-      return null;
-    }
-    const header = (
-      <tr key="right-header">
-        <td>&nbsp;</td>
-        <td>
-          <h6>
-            {translateWithParameters(
-              'quality_profiles.x_rules_only_in',
-              this.props.inRight.length
-            )}{' '}
-            {this.props.right.name}
-          </h6>
-        </td>
-      </tr>
-    );
-    const rows = this.props.inRight.map(rule =>
-      <tr key={`right-${rule.key}`} className="js-comparison-in-right">
-        <td>&nbsp;</td>
-        <td>
-          {this.renderRule(rule, rule.severity)}
-        </td>
-      </tr>
-    );
-    return [header, ...rows];
-  }
-
-  renderModified() {
-    if (this.props.modified.length === 0) {
-      return null;
-    }
-    const header = (
-      <tr key="modified-header">
-        <td colSpan="2" className="text-center">
-          <h6>
-            {translateWithParameters(
-              'quality_profiles.x_rules_have_different_configuration',
-              this.props.modified.length
-            )}
-          </h6>
-        </td>
-      </tr>
-    );
-    const secondHeader = (
-      <tr key="modified-second-header">
-        <td>
-          <h6>
-            {this.props.left.name}
-          </h6>
-        </td>
-        <td>
-          <h6>
-            {this.props.right.name}
-          </h6>
-        </td>
-      </tr>
-    );
-    const rows = this.props.modified.map(rule =>
-      <tr key={`modified-${rule.key}`} className="js-comparison-modified">
-        <td>
-          {this.renderRule(rule, rule.left.severity)}
-          {this.renderParameters(rule.left.params)}
-        </td>
-        <td>
-          {this.renderRule(rule, rule.right.severity)}
-          {this.renderParameters(rule.right.params)}
-        </td>
-      </tr>
-    );
-    return [header, secondHeader, ...rows];
-  }
-
-  render() {
-    if (!this.props.inLeft.length && !this.props.inRight.length && !this.props.modified.length) {
-      return <ComparisonEmpty />;
-    }
-
-    return (
-      <table className="data zebra quality-profile-comparison-table">
-        <tbody>
-          {this.renderLeft()}
-          {this.renderRight()}
-          {this.renderModified()}
-        </tbody>
-      </table>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx
new file mode 100644 (file)
index 0000000..f29c2ec
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * 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 ComparisonEmpty from './ComparisonEmpty';
+import SeverityIcon from '../../../components/shared/SeverityIcon';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+
+type Params = { [p: string]: string };
+
+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;
+}
+
+export default class ComparisonResults extends React.PureComponent<Props> {
+  renderRule(rule: { key: string; name: string }, severity: string) {
+    return (
+      <div>
+        <SeverityIcon severity={severity} />{' '}
+        <Link to={getRulesUrl({ rule_key: rule.key }, this.props.organization)}>{rule.name}</Link>
+      </div>
+    );
+  }
+
+  renderParameters(params: Params) {
+    if (!params) {
+      return null;
+    }
+    return (
+      <ul>
+        {Object.keys(params).map(key =>
+          <li key={key} className="spacer-top">
+            <code>
+              {key}
+              {': '}
+              {params[key]}
+            </code>
+          </li>
+        )}
+      </ul>
+    );
+  }
+
+  renderLeft() {
+    if (this.props.inLeft.length === 0) {
+      return null;
+    }
+    const header = (
+      <tr key="left-header">
+        <td>
+          <h6>
+            {translateWithParameters(
+              'quality_profiles.x_rules_only_in',
+              this.props.inLeft.length
+            )}{' '}
+            {this.props.left.name}
+          </h6>
+        </td>
+        <td>&nbsp;</td>
+      </tr>
+    );
+    const rows = this.props.inLeft.map(rule =>
+      <tr key={`left-${rule.key}`} className="js-comparison-in-left">
+        <td>
+          {this.renderRule(rule, rule.severity)}
+        </td>
+        <td>&nbsp;</td>
+      </tr>
+    );
+    return [header, ...rows];
+  }
+
+  renderRight() {
+    if (this.props.inRight.length === 0) {
+      return null;
+    }
+    const header = (
+      <tr key="right-header">
+        <td>&nbsp;</td>
+        <td>
+          <h6>
+            {translateWithParameters(
+              'quality_profiles.x_rules_only_in',
+              this.props.inRight.length
+            )}{' '}
+            {this.props.right.name}
+          </h6>
+        </td>
+      </tr>
+    );
+    const rows = this.props.inRight.map(rule =>
+      <tr key={`right-${rule.key}`} className="js-comparison-in-right">
+        <td>&nbsp;</td>
+        <td>
+          {this.renderRule(rule, rule.severity)}
+        </td>
+      </tr>
+    );
+    return [header, ...rows];
+  }
+
+  renderModified() {
+    if (this.props.modified.length === 0) {
+      return null;
+    }
+    const header = (
+      <tr key="modified-header">
+        <td colSpan={2} className="text-center">
+          <h6>
+            {translateWithParameters(
+              'quality_profiles.x_rules_have_different_configuration',
+              this.props.modified.length
+            )}
+          </h6>
+        </td>
+      </tr>
+    );
+    const secondHeader = (
+      <tr key="modified-second-header">
+        <td>
+          <h6>
+            {this.props.left.name}
+          </h6>
+        </td>
+        <td>
+          <h6>
+            {this.props.right.name}
+          </h6>
+        </td>
+      </tr>
+    );
+    const rows = this.props.modified.map(rule =>
+      <tr key={`modified-${rule.key}`} className="js-comparison-modified">
+        <td>
+          {this.renderRule(rule, rule.left.severity)}
+          {this.renderParameters(rule.left.params)}
+        </td>
+        <td>
+          {this.renderRule(rule, rule.right.severity)}
+          {this.renderParameters(rule.right.params)}
+        </td>
+      </tr>
+    );
+    return [header, secondHeader, ...rows];
+  }
+
+  render() {
+    if (!this.props.inLeft.length && !this.props.inRight.length && !this.props.modified.length) {
+      return <ComparisonEmpty />;
+    }
+
+    return (
+      <table className="data zebra quality-profile-comparison-table">
+        <tbody>
+          {this.renderLeft()}
+          {this.renderRight()}
+          {this.renderModified()}
+        </tbody>
+      </table>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.js
deleted file mode 100644 (file)
index b972451..0000000
+++ /dev/null
@@ -1,45 +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 { shallow } from 'enzyme';
-import React from 'react';
-import Select from 'react-select';
-import ComparisonForm from '../ComparisonForm';
-import { createFakeProfile } from '../../utils';
-
-it('should render Select with right options', () => {
-  const profile = createFakeProfile();
-  const profiles = [
-    profile,
-    createFakeProfile({ key: 'another', name: 'another name' }),
-    createFakeProfile({ key: 'java', name: 'java', language: 'java' })
-  ];
-
-  const output = shallow(
-    <ComparisonForm
-      withKey="another"
-      profile={profile}
-      profiles={profiles}
-      onCompare={() => true}
-    />
-  ).find(Select);
-  expect(output.length).toBe(1);
-  expect(output.prop('value')).toBe('another');
-  expect(output.prop('options')).toEqual([{ value: 'another', label: 'another name' }]);
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonForm-test.tsx
new file mode 100644 (file)
index 0000000..55edbd9
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import ComparisonForm from '../ComparisonForm';
+import { createFakeProfile } from '../../utils';
+
+it('should render Select with right options', () => {
+  const profile = createFakeProfile();
+  const profiles = [
+    profile,
+    createFakeProfile({ key: 'another', name: 'another name' }),
+    createFakeProfile({ key: 'java', name: 'java', language: 'java' })
+  ];
+
+  const output = shallow(
+    <ComparisonForm
+      withKey="another"
+      profile={profile}
+      profiles={profiles}
+      onCompare={() => true}
+    />
+  ).find('Select');
+  expect(output.length).toBe(1);
+  expect(output.prop('value')).toBe('another');
+  expect(output.prop('options')).toEqual([{ value: 'another', label: 'another name' }]);
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.js
deleted file mode 100644 (file)
index d346aa2..0000000
+++ /dev/null
@@ -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 { shallow } from 'enzyme';
-import React from 'react';
-import { Link } from 'react-router';
-import ComparisonResults from '../ComparisonResults';
-import ComparisonEmpty from '../ComparisonEmpty';
-import SeverityIcon from '../../../../components/shared/SeverityIcon';
-
-it('should render ComparisonEmpty', () => {
-  const output = shallow(
-    <ComparisonResults
-      left={{ name: 'left' }}
-      right={{ name: 'right' }}
-      inLeft={[]}
-      inRight={[]}
-      modified={[]}
-    />
-  );
-  expect(output.is(ComparisonEmpty)).toBe(true);
-});
-
-it('should compare', () => {
-  const inLeft = [{ key: 'rule1', name: 'rule1', severity: 'BLOCKER' }];
-  const inRight = [
-    { key: 'rule2', name: 'rule2', severity: 'CRITICAL' },
-    { key: 'rule3', name: 'rule3', severity: 'MAJOR' }
-  ];
-  const modified = [
-    {
-      key: 'rule4',
-      name: 'rule4',
-      left: {
-        severity: 'BLOCKER',
-        params: { foo: 'bar' }
-      },
-      right: {
-        severity: 'INFO',
-        params: { foo: 'qwe' }
-      }
-    }
-  ];
-
-  const output = shallow(
-    <ComparisonResults
-      left={{ name: 'left' }}
-      right={{ name: 'right' }}
-      inLeft={inLeft}
-      inRight={inRight}
-      modified={modified}
-    />
-  );
-
-  const leftDiffs = output.find('.js-comparison-in-left');
-  expect(leftDiffs.length).toBe(1);
-  expect(leftDiffs.find(Link).length).toBe(1);
-  expect(leftDiffs.find(Link).prop('to')).toContain('rule_key=rule1');
-  expect(leftDiffs.find(Link).prop('children')).toContain('rule1');
-  expect(leftDiffs.find(SeverityIcon).length).toBe(1);
-  expect(leftDiffs.find(SeverityIcon).prop('severity')).toBe('BLOCKER');
-
-  const rightDiffs = output.find('.js-comparison-in-right');
-  expect(rightDiffs.length).toBe(2);
-  expect(rightDiffs.at(0).find(Link).length).toBe(1);
-  expect(rightDiffs.at(0).find(Link).prop('to')).toContain('rule_key=rule2');
-  expect(rightDiffs.at(0).find(Link).prop('children')).toContain('rule2');
-  expect(rightDiffs.at(0).find(SeverityIcon).length).toBe(1);
-  expect(rightDiffs.at(0).find(SeverityIcon).prop('severity')).toBe('CRITICAL');
-
-  const modifiedDiffs = output.find('.js-comparison-modified');
-  expect(modifiedDiffs.length).toBe(1);
-  expect(modifiedDiffs.find(Link).at(0).prop('to')).toContain('rule_key=rule4');
-  expect(modifiedDiffs.find(Link).at(0).prop('children')).toContain('rule4');
-  expect(modifiedDiffs.find(SeverityIcon).length).toBe(2);
-  expect(modifiedDiffs.text()).toContain('bar');
-  expect(modifiedDiffs.text()).toContain('qwe');
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/compare/__tests__/ComparisonResults-test.tsx
new file mode 100644 (file)
index 0000000..c3200b0
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { Link } from 'react-router';
+import ComparisonResults from '../ComparisonResults';
+import ComparisonEmpty from '../ComparisonEmpty';
+import SeverityIcon from '../../../../components/shared/SeverityIcon';
+
+it('should render ComparisonEmpty', () => {
+  const output = shallow(
+    <ComparisonResults
+      left={{ name: 'left' }}
+      right={{ name: 'right' }}
+      inLeft={[]}
+      inRight={[]}
+      modified={[]}
+      organization={null}
+    />
+  );
+  expect(output.is(ComparisonEmpty)).toBe(true);
+});
+
+it('should compare', () => {
+  const inLeft = [{ key: 'rule1', name: 'rule1', severity: 'BLOCKER' }];
+  const inRight = [
+    { key: 'rule2', name: 'rule2', severity: 'CRITICAL' },
+    { key: 'rule3', name: 'rule3', severity: 'MAJOR' }
+  ];
+  const modified = [
+    {
+      key: 'rule4',
+      name: 'rule4',
+      left: {
+        severity: 'BLOCKER',
+        params: { foo: 'bar' }
+      },
+      right: {
+        severity: 'INFO',
+        params: { foo: 'qwe' }
+      }
+    }
+  ];
+
+  const output = shallow(
+    <ComparisonResults
+      left={{ name: 'left' }}
+      right={{ name: 'right' }}
+      inLeft={inLeft}
+      inRight={inRight}
+      modified={modified}
+      organization={null}
+    />
+  );
+
+  const leftDiffs = output.find('.js-comparison-in-left');
+  expect(leftDiffs.length).toBe(1);
+  expect(leftDiffs.find(Link).length).toBe(1);
+  expect(leftDiffs.find(Link).prop('to')).toContain('rule_key=rule1');
+  expect(leftDiffs.find(Link).prop('children')).toContain('rule1');
+  expect(leftDiffs.find(SeverityIcon).length).toBe(1);
+  expect(leftDiffs.find(SeverityIcon).prop('severity')).toBe('BLOCKER');
+
+  const rightDiffs = output.find('.js-comparison-in-right');
+  expect(rightDiffs.length).toBe(2);
+  expect(rightDiffs.at(0).find(Link).length).toBe(1);
+  expect(rightDiffs.at(0).find(Link).prop('to')).toContain('rule_key=rule2');
+  expect(rightDiffs.at(0).find(Link).prop('children')).toContain('rule2');
+  expect(rightDiffs.at(0).find(SeverityIcon).length).toBe(1);
+  expect(rightDiffs.at(0).find(SeverityIcon).prop('severity')).toBe('CRITICAL');
+
+  const modifiedDiffs = output.find('.js-comparison-modified');
+  expect(modifiedDiffs.length).toBe(1);
+  expect(modifiedDiffs.find(Link).at(0).prop('to')).toContain('rule_key=rule4');
+  expect(modifiedDiffs.find(Link).at(0).prop('children')).toContain('rule4');
+  expect(modifiedDiffs.find(SeverityIcon).length).toBe(2);
+  expect(modifiedDiffs.text()).toContain('bar');
+  expect(modifiedDiffs.text()).toContain('qwe');
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
deleted file mode 100644 (file)
index b0f80ee..0000000
+++ /dev/null
@@ -1,134 +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 { 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';
-
-/*::
-type Props = {
-  children: React.Element<*>,
-  currentUser: { permissions: { global: Array<string> } },
-  languages: Array<*>,
-  onRequestFail: Object => void,
-  organization: { name: string, canAdmin?: boolean, key: string } | null
-};
-*/
-
-/*::
-type State = {
-  loading: boolean,
-  exporters?: Array<Exporter>,
-  profiles?: Array<*>
-};
-*/
-
-export default class App extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = { loading: true };
-
-  componentWillMount() {
-    const html = document.querySelector('html');
-    if (html) {
-      html.classList.add('dashboard-page');
-    }
-  }
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadData();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-    const html = document.querySelector('html');
-    if (html) {
-      html.classList.remove('dashboard-page');
-    }
-  }
-
-  fetchProfiles() {
-    const { organization } = this.props;
-    const data = organization ? { organization: organization.key } : {};
-    return searchQualityProfiles(data);
-  }
-
-  loadData() {
-    this.setState({ loading: true });
-    Promise.all([getExporters(), this.fetchProfiles()]).then(responses => {
-      if (this.mounted) {
-        const [exporters, profiles] = responses;
-        this.setState({
-          exporters,
-          profiles: sortProfiles(profiles),
-          loading: false
-        });
-      }
-    });
-  }
-
-  updateProfiles = () => {
-    return this.fetchProfiles().then(profiles => {
-      if (this.mounted) {
-        this.setState({ profiles: sortProfiles(profiles) });
-      }
-    });
-  };
-
-  renderChild() {
-    if (this.state.loading) {
-      return <i className="spinner" />;
-    }
-    const { organization } = this.props;
-    const finalLanguages = Object.values(this.props.languages);
-
-    const canAdmin = organization
-      ? organization.canAdmin
-      : this.props.currentUser.permissions.global.includes('profileadmin');
-
-    return React.cloneElement(this.props.children, {
-      profiles: this.state.profiles,
-      languages: finalLanguages,
-      exporters: this.state.exporters,
-      updateProfiles: this.updateProfiles,
-      onRequestFail: this.props.onRequestFail,
-      organization: organization ? organization.key : null,
-      canAdmin
-    });
-  }
-
-  render() {
-    return (
-      <div className="page page-limited">
-        <OrganizationHelmet
-          title={translate('quality_profiles.page')}
-          organization={this.props.organization}
-        />
-
-        {this.renderChild()}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/App.tsx
new file mode 100644 (file)
index 0000000..44013b7
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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 { searchQualityProfiles, getExporters } from '../../../api/quality-profiles';
+import { sortProfiles } from '../utils';
+import { translate } from '../../../helpers/l10n';
+import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
+import '../styles.css';
+import { IExporter, IProfile } from '../types';
+
+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;
+}
+
+interface State {
+  loading: boolean;
+  exporters?: IExporter[];
+  profiles?: IProfile[];
+}
+
+export default class App extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: true };
+
+  componentWillMount() {
+    const html = document.querySelector('html');
+    if (html) {
+      html.classList.add('dashboard-page');
+    }
+  }
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadData();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+    const html = document.querySelector('html');
+    if (html) {
+      html.classList.remove('dashboard-page');
+    }
+  }
+
+  fetchProfiles() {
+    const { organization } = this.props;
+    const data = organization ? { organization: organization.key } : {};
+    return searchQualityProfiles(data);
+  }
+
+  loadData() {
+    this.setState({ loading: true });
+    Promise.all([getExporters(), this.fetchProfiles()]).then(responses => {
+      if (this.mounted) {
+        const [exporters, profiles] = responses;
+        this.setState({
+          exporters,
+          profiles: sortProfiles(profiles),
+          loading: false
+        });
+      }
+    });
+  }
+
+  updateProfiles = () => {
+    return this.fetchProfiles().then((profiles: any) => {
+      if (this.mounted) {
+        this.setState({ profiles: sortProfiles(profiles) });
+      }
+    });
+  };
+
+  renderChild() {
+    if (this.state.loading) {
+      return <i className="spinner" />;
+    }
+    const { organization } = this.props;
+    const finalLanguages = Object.values(this.props.languages);
+
+    const canAdmin = organization
+      ? organization.canAdmin
+      : this.props.currentUser.permissions.global.includes('profileadmin');
+
+    return React.cloneElement(this.props.children, {
+      profiles: this.state.profiles,
+      languages: finalLanguages,
+      exporters: this.state.exporters,
+      updateProfiles: this.updateProfiles,
+      onRequestFail: this.props.onRequestFail,
+      organization: organization ? organization.key : null,
+      canAdmin
+    });
+  }
+
+  render() {
+    return (
+      <div className="page page-limited">
+        <OrganizationHelmet
+          title={translate('quality_profiles.page')}
+          organization={this.props.organization}
+        />
+
+        {this.renderChild()}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
deleted file mode 100644 (file)
index a9809ff..0000000
+++ /dev/null
@@ -1,37 +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 { connect } from 'react-redux';
-import App from './App';
-import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';
-import { onFail } from '../../../store/rootActions';
-
-const mapStateToProps = (state, ownProps) => ({
-  currentUser: getCurrentUser(state),
-  languages: getLanguages(state),
-  organization: ownProps.params.organizationKey
-    ? getOrganizationByKey(state, ownProps.params.organizationKey)
-    : null
-});
-
-const mapDispatchToProps = dispatch => ({
-  onRequestFail: error => onFail(dispatch)(error)
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(App);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.tsx
new file mode 100644 (file)
index 0000000..9eb3af6
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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 { connect } from 'react-redux';
+import App from './App';
+import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';
+import { onFail } from '../../../store/rootActions';
+
+const mapStateToProps = (state: any, ownProps: any) => ({
+  currentUser: getCurrentUser(state),
+  languages: getLanguages(state),
+  organization: ownProps.params.organizationKey
+    ? getOrganizationByKey(state, ownProps.params.organizationKey)
+    : null
+});
+
+const mapDispatchToProps = (dispatch: any) => ({
+  onRequestFail: (error: any) => onFail(dispatch)(error)
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(App as any);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.js
deleted file mode 100644 (file)
index fa2e163..0000000
+++ /dev/null
@@ -1,56 +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 classNames from 'classnames';
-import Tooltip from '../../../components/controls/Tooltip';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {|
-  className?: string,
-  tooltip?: boolean
-|};
-*/
-
-export default function BuiltInBadge(props /*: Props */) {
-  const badge = (
-    <div className={classNames('outline-badge', props.className)}>
-      {translate('quality_profiles.built_in')}
-    </div>
-  );
-
-  const overlay = (
-    <span>
-      {translate('quality_profiles.built_in.description.1')}{' '}
-      {translate('quality_profiles.built_in.description.2')}
-    </span>
-  );
-
-  return props.tooltip
-    ? <Tooltip overlay={overlay}>
-        {badge}
-      </Tooltip>
-    : badge;
-}
-
-BuiltInBadge.defaultProps = {
-  tooltip: true
-};
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/BuiltInBadge.tsx
new file mode 100644 (file)
index 0000000..7a70614
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 classNames from 'classnames';
+import Tooltip from '../../../components/controls/Tooltip';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  className?: string;
+  tooltip?: boolean;
+}
+
+export default function BuiltInBadge({ className, tooltip = true }: Props) {
+  const badge = (
+    <div className={classNames('outline-badge', className)}>
+      {translate('quality_profiles.built_in')}
+    </div>
+  );
+
+  const overlay = (
+    <span>
+      {translate('quality_profiles.built_in.description.1')}{' '}
+      {translate('quality_profiles.built_in.description.2')}
+    </span>
+  );
+
+  return tooltip
+    ? <Tooltip overlay={overlay}>
+        {badge}
+      </Tooltip>
+    : badge;
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.js
deleted file mode 100644 (file)
index 4ad268e..0000000
+++ /dev/null
@@ -1,139 +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 Modal from 'react-modal';
-/*:: import type { Profile } from '../propTypes'; */
-import { copyProfile } from '../../../api/quality-profiles';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  onClose: () => void,
-  onCopy: string => void,
-  onRequestFail: Object => void,
-  profile: Profile
-};
-*/
-
-/*::
-type State = {
-  loading: boolean,
-  name: ?string
-};
-*/
-
-export default class CopyProfileForm extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = { loading: false, name: null };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => {
-    this.setState({ name: event.currentTarget.value });
-  };
-
-  handleFormSubmit = (event /*: Event */) => {
-    event.preventDefault();
-
-    const { name } = this.state;
-
-    if (name != null) {
-      this.setState({ loading: true });
-      copyProfile(this.props.profile.key, name).then(
-        profile => this.props.onCopy(profile.name),
-        error => {
-          if (this.mounted) {
-            this.setState({ loading: false });
-          }
-          this.props.onRequestFail(error);
-        }
-      );
-    }
-  };
-
-  render() {
-    const { profile } = this.props;
-    const header = translateWithParameters(
-      'quality_profiles.copy_x_title',
-      profile.name,
-      profile.languageName
-    );
-    const submitDisabled =
-      this.state.loading || !this.state.name || this.state.name === profile.name;
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <form id="copy-profile-form" onSubmit={this.handleFormSubmit}>
-          <div className="modal-head">
-            <h2>
-              {header}
-            </h2>
-          </div>
-          <div className="modal-body">
-            <div className="modal-field">
-              <label htmlFor="copy-profile-name">
-                {translate('quality_profiles.copy_new_name')}
-                <em className="mandatory">*</em>
-              </label>
-              <input
-                autoFocus={true}
-                id="copy-profile-name"
-                maxLength="100"
-                name="name"
-                onChange={this.handleNameChange}
-                required={true}
-                size="50"
-                type="text"
-                value={this.state.name != null ? this.state.name : profile.name}
-              />
-            </div>
-          </div>
-          <div className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            <button disabled={submitDisabled} id="copy-profile-submit">
-              {translate('copy')}
-            </button>
-            <a href="#" id="copy-profile-cancel" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </div>
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/CopyProfileForm.tsx
new file mode 100644 (file)
index 0000000..4d24898
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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 Modal from 'react-modal';
+import { copyProfile } from '../../../api/quality-profiles';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  onClose: () => void;
+  onCopy: (name: string) => void;
+  onRequestFail: (reasong: any) => void;
+  profile: IProfile;
+}
+
+interface State {
+  loading: boolean;
+  name: string | null;
+}
+
+export default class CopyProfileForm extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: false, name: null };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+    this.setState({ name: event.currentTarget.value });
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+
+    const { name } = this.state;
+
+    if (name != null) {
+      this.setState({ loading: true });
+      copyProfile(this.props.profile.key, name).then(
+        (profile: any) => this.props.onCopy(profile.name),
+        (error: any) => {
+          if (this.mounted) {
+            this.setState({ loading: false });
+          }
+          this.props.onRequestFail(error);
+        }
+      );
+    }
+  };
+
+  render() {
+    const { profile } = this.props;
+    const header = translateWithParameters(
+      'quality_profiles.copy_x_title',
+      profile.name,
+      profile.languageName
+    );
+    const submitDisabled =
+      this.state.loading || !this.state.name || this.state.name === profile.name;
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="copy-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>
+              {header}
+            </h2>
+          </div>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="copy-profile-name">
+                {translate('quality_profiles.copy_new_name')}
+                <em className="mandatory">*</em>
+              </label>
+              <input
+                autoFocus={true}
+                id="copy-profile-name"
+                maxLength={100}
+                name="name"
+                onChange={this.handleNameChange}
+                required={true}
+                size={50}
+                type="text"
+                value={this.state.name != null ? this.state.name : profile.name}
+              />
+            </div>
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} id="copy-profile-submit">
+              {translate('copy')}
+            </button>
+            <a href="#" id="copy-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.js
deleted file mode 100644 (file)
index 975ca0a..0000000
+++ /dev/null
@@ -1,124 +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 Modal from 'react-modal';
-/*:: import type { Profile } from '../propTypes'; */
-import { deleteProfile } from '../../../api/quality-profiles';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  onClose: () => void,
-  onDelete: () => void,
-  onRequestFail: Object => void,
-  profile: Profile
-};
-*/
-
-/*::
-type State = {
-  loading: boolean
-};
-*/
-
-export default class DeleteProfileForm extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = { loading: false, name: null };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleFormSubmit = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ loading: true });
-    deleteProfile(this.props.profile.key).then(this.props.onDelete, error => {
-      if (this.mounted) {
-        this.setState({ loading: false });
-      }
-      this.props.onRequestFail(error);
-    });
-  };
-
-  render() {
-    const { profile } = this.props;
-    const header = translate('quality_profiles.delete_confirm_title');
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <form id="delete-profile-form" onSubmit={this.handleFormSubmit}>
-          <div className="modal-head">
-            <h2>
-              {header}
-            </h2>
-          </div>
-          <div className="modal-body">
-            <div className="js-modal-messages" />
-            {profile.childrenCount > 0
-              ? <div>
-                  <div className="alert alert-warning">
-                    {translate('quality_profiles.this_profile_has_descendants')}
-                  </div>
-                  <p>
-                    {translateWithParameters(
-                      'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants',
-                      profile.name,
-                      profile.languageName
-                    )}
-                  </p>
-                </div>
-              : <p>
-                  {translateWithParameters(
-                    'quality_profiles.are_you_sure_want_delete_profile_x',
-                    profile.name,
-                    profile.languageName
-                  )}
-                </p>}
-          </div>
-          <div className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            <button className="button-red" disabled={this.state.loading} id="delete-profile-submit">
-              {translate('delete')}
-            </button>
-            <a href="#" id="delete-profile-cancel" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </div>
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/DeleteProfileForm.tsx
new file mode 100644 (file)
index 0000000..5a7b227
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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 Modal from 'react-modal';
+import { deleteProfile } from '../../../api/quality-profiles';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  onClose: () => void;
+  onDelete: () => void;
+  onRequestFail: (reason: any) => void;
+  profile: IProfile;
+}
+
+interface State {
+  loading: boolean;
+  name: string | null;
+}
+
+export default class DeleteProfileForm extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: false, name: null };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+    this.setState({ loading: true });
+    deleteProfile(this.props.profile.key).then(this.props.onDelete, (error: any) => {
+      if (this.mounted) {
+        this.setState({ loading: false });
+      }
+      this.props.onRequestFail(error);
+    });
+  };
+
+  render() {
+    const { profile } = this.props;
+    const header = translate('quality_profiles.delete_confirm_title');
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="delete-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>
+              {header}
+            </h2>
+          </div>
+          <div className="modal-body">
+            <div className="js-modal-messages" />
+            {profile.childrenCount > 0
+              ? <div>
+                  <div className="alert alert-warning">
+                    {translate('quality_profiles.this_profile_has_descendants')}
+                  </div>
+                  <p>
+                    {translateWithParameters(
+                      'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants',
+                      profile.name,
+                      profile.languageName
+                    )}
+                  </p>
+                </div>
+              : <p>
+                  {translateWithParameters(
+                    'quality_profiles.are_you_sure_want_delete_profile_x',
+                    profile.name,
+                    profile.languageName
+                  )}
+                </p>}
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button className="button-red" disabled={this.state.loading} id="delete-profile-submit">
+              {translate('delete')}
+            </button>
+            <a href="#" id="delete-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js
deleted file mode 100644 (file)
index 8a2b2f8..0000000
+++ /dev/null
@@ -1,221 +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 PropTypes from 'prop-types';
-import { Link } from 'react-router';
-import RenameProfileForm from './RenameProfileForm';
-import CopyProfileForm from './CopyProfileForm';
-import DeleteProfileForm from './DeleteProfileForm';
-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; */
-
-  static defaultProps = {
-    fromList: false
-  };
-
-  static contextTypes = {
-    router: PropTypes.object
-  };
-
-  constructor(props /*: Props */) {
-    super(props);
-    this.state = {
-      copyFormOpen: false,
-      deleteFormOpen: false,
-      renameFormOpen: false
-    };
-  }
-
-  handleRenameClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ renameFormOpen: true });
-  };
-
-  handleProfileRename = (name /*: string */) => {
-    this.closeRenameForm();
-    this.props.updateProfiles().then(() => {
-      if (!this.props.fromList) {
-        this.context.router.replace(
-          getProfilePath(name, this.props.profile.language, this.props.organization)
-        );
-      }
-    });
-  };
-
-  closeRenameForm = () => {
-    this.setState({ renameFormOpen: false });
-  };
-
-  handleCopyClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ copyFormOpen: true });
-  };
-
-  handleProfileCopy = (name /*: string */) => {
-    this.props.updateProfiles().then(() => {
-      this.context.router.push(
-        getProfilePath(name, this.props.profile.language, this.props.organization)
-      );
-    });
-  };
-
-  closeCopyForm = () => {
-    this.setState({ copyFormOpen: false });
-  };
-
-  handleSetDefaultClick = (e /*: SyntheticInputEvent */) => {
-    e.preventDefault();
-    setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles);
-  };
-
-  handleDeleteClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ deleteFormOpen: true });
-  };
-
-  handleProfileDelete = () => {
-    this.context.router.replace(getProfilesPath(this.props.organization));
-    this.props.updateProfiles();
-  };
-
-  closeDeleteForm = () => {
-    this.setState({ deleteFormOpen: false });
-  };
-
-  render() {
-    const { profile, canAdmin } = this.props;
-
-    // FIXME use org, name and lang
-    const backupUrl =
-      window.baseUrl + '/api/qualityprofiles/backup?profileKey=' + encodeURIComponent(profile.key);
-
-    const activateMoreUrl = getRulesUrl(
-      {
-        qprofile: profile.key,
-        activation: 'false'
-      },
-      this.props.organization
-    );
-
-    return (
-      <ul className="dropdown-menu dropdown-menu-right">
-        {canAdmin &&
-          !profile.isBuiltIn &&
-          <li>
-            <Link to={activateMoreUrl}>
-              {translate('quality_profiles.activate_more_rules')}
-            </Link>
-          </li>}
-        {!profile.isBuiltIn &&
-          <li>
-            <a id="quality-profile-backup" href={backupUrl}>
-              {translate('backup_verb')}
-            </a>
-          </li>}
-        <li>
-          <Link
-            to={getProfileComparePath(profile.name, profile.language, this.props.organization)}
-            id="quality-profile-compare">
-            {translate('compare')}
-          </Link>
-        </li>
-        {canAdmin &&
-          <li>
-            <a id="quality-profile-copy" href="#" onClick={this.handleCopyClick}>
-              {translate('copy')}
-            </a>
-          </li>}
-        {canAdmin &&
-          !profile.isBuiltIn &&
-          <li>
-            <a id="quality-profile-rename" href="#" onClick={this.handleRenameClick}>
-              {translate('rename')}
-            </a>
-          </li>}
-        {canAdmin &&
-          !profile.isDefault &&
-          <li>
-            <a id="quality-profile-set-as-default" href="#" onClick={this.handleSetDefaultClick}>
-              {translate('set_as_default')}
-            </a>
-          </li>}
-        {canAdmin &&
-          !profile.isDefault &&
-          !profile.isBuiltIn &&
-          <li>
-            <a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick}>
-              {translate('delete')}
-            </a>
-          </li>}
-
-        {this.state.copyFormOpen &&
-          <CopyProfileForm
-            onClose={this.closeCopyForm}
-            onCopy={this.handleProfileCopy}
-            onRequestFail={this.props.onRequestFail}
-            profile={profile}
-          />}
-
-        {this.state.deleteFormOpen &&
-          <DeleteProfileForm
-            onClose={this.closeDeleteForm}
-            onDelete={this.handleProfileDelete}
-            onRequestFail={this.props.onRequestFail}
-            profile={profile}
-          />}
-
-        {this.state.renameFormOpen &&
-          <RenameProfileForm
-            onClose={this.closeRenameForm}
-            onRename={this.handleProfileRename}
-            onRequestFail={this.props.onRequestFail}
-            profile={profile}
-          />}
-      </ul>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.tsx
new file mode 100644 (file)
index 0000000..6d1ad5c
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import { Link } from 'react-router';
+import RenameProfileForm from './RenameProfileForm';
+import CopyProfileForm from './CopyProfileForm';
+import DeleteProfileForm from './DeleteProfileForm';
+import { translate } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+import { setDefaultProfile } from '../../../api/quality-profiles';
+import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils';
+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
+  };
+
+  static contextTypes = {
+    router: PropTypes.object
+  };
+
+  constructor(props: Props) {
+    super(props);
+    this.state = {
+      copyFormOpen: false,
+      deleteFormOpen: false,
+      renameFormOpen: false
+    };
+  }
+
+  handleRenameClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ renameFormOpen: true });
+  };
+
+  handleProfileRename = (name: string) => {
+    this.closeRenameForm();
+    this.props.updateProfiles().then(() => {
+      if (!this.props.fromList) {
+        this.context.router.replace(
+          getProfilePath(name, this.props.profile.language, this.props.organization)
+        );
+      }
+    });
+  };
+
+  closeRenameForm = () => {
+    this.setState({ renameFormOpen: false });
+  };
+
+  handleCopyClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ copyFormOpen: true });
+  };
+
+  handleProfileCopy = (name: string) => {
+    this.props.updateProfiles().then(() => {
+      this.context.router.push(
+        getProfilePath(name, this.props.profile.language, this.props.organization)
+      );
+    });
+  };
+
+  closeCopyForm = () => {
+    this.setState({ copyFormOpen: false });
+  };
+
+  handleSetDefaultClick = (e: React.SyntheticEvent<HTMLElement>) => {
+    e.preventDefault();
+    setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles);
+  };
+
+  handleDeleteClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ deleteFormOpen: true });
+  };
+
+  handleProfileDelete = () => {
+    this.context.router.replace(getProfilesPath(this.props.organization));
+    this.props.updateProfiles();
+  };
+
+  closeDeleteForm = () => {
+    this.setState({ deleteFormOpen: false });
+  };
+
+  render() {
+    const { profile, canAdmin } = this.props;
+
+    // FIXME use org, name and lang
+    const backupUrl =
+      (window as any).baseUrl +
+      '/api/qualityprofiles/backup?profileKey=' +
+      encodeURIComponent(profile.key);
+
+    const activateMoreUrl = getRulesUrl(
+      {
+        qprofile: profile.key,
+        activation: 'false'
+      },
+      this.props.organization
+    );
+
+    return (
+      <ul className="dropdown-menu dropdown-menu-right">
+        {canAdmin &&
+          !profile.isBuiltIn &&
+          <li>
+            <Link to={activateMoreUrl}>
+              {translate('quality_profiles.activate_more_rules')}
+            </Link>
+          </li>}
+        {!profile.isBuiltIn &&
+          <li>
+            <a id="quality-profile-backup" href={backupUrl}>
+              {translate('backup_verb')}
+            </a>
+          </li>}
+        <li>
+          <Link
+            to={getProfileComparePath(profile.name, profile.language, this.props.organization)}
+            id="quality-profile-compare">
+            {translate('compare')}
+          </Link>
+        </li>
+        {canAdmin &&
+          <li>
+            <a id="quality-profile-copy" href="#" onClick={this.handleCopyClick}>
+              {translate('copy')}
+            </a>
+          </li>}
+        {canAdmin &&
+          !profile.isBuiltIn &&
+          <li>
+            <a id="quality-profile-rename" href="#" onClick={this.handleRenameClick}>
+              {translate('rename')}
+            </a>
+          </li>}
+        {canAdmin &&
+          !profile.isDefault &&
+          <li>
+            <a id="quality-profile-set-as-default" href="#" onClick={this.handleSetDefaultClick}>
+              {translate('set_as_default')}
+            </a>
+          </li>}
+        {canAdmin &&
+          !profile.isDefault &&
+          !profile.isBuiltIn &&
+          <li>
+            <a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick}>
+              {translate('delete')}
+            </a>
+          </li>}
+
+        {this.state.copyFormOpen &&
+          <CopyProfileForm
+            onClose={this.closeCopyForm}
+            onCopy={this.handleProfileCopy}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+          />}
+
+        {this.state.deleteFormOpen &&
+          <DeleteProfileForm
+            onClose={this.closeDeleteForm}
+            onDelete={this.handleProfileDelete}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+          />}
+
+        {this.state.renameFormOpen &&
+          <RenameProfileForm
+            onClose={this.closeRenameForm}
+            onRename={this.handleProfileRename}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+          />}
+      </ul>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js
deleted file mode 100644 (file)
index f44f53c..0000000
+++ /dev/null
@@ -1,105 +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 Helmet from 'react-helmet';
-import ProfileNotFound from './ProfileNotFound';
-import ProfileHeader from '../details/ProfileHeader';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-type Props = {
-  canAdmin: boolean,
-  children: React.Element<*>,
-  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; */
-
-  componentDidMount() {
-    const { location, profiles, router } = this.props;
-    if (location.query.key) {
-      // try to find a quality profile with the given key
-      // if managed to find one, redirect to a new version
-      // otherwise do nothing, `render` will show not found page
-      const profile = profiles.find(profile => profile.key === location.query.key);
-      if (profile) {
-        router.replace({
-          pathname: location.pathname,
-          query: { language: profile.language, name: profile.name }
-        });
-      }
-    }
-  }
-
-  render() {
-    const { organization, profiles, location, ...other } = this.props;
-    const { key, language, name } = location.query;
-
-    if (key) {
-      // if there is a `key` parameter,
-      // then if we managed to find a quality profile with this key
-      // then we will be redirected in `componentDidMount`
-      // otherwise show `ProfileNotFound`
-      const profile = profiles.find(profile => profile.key === location.query.key);
-      return profile ? null : <ProfileNotFound organization={organization} />;
-    }
-
-    const profile = profiles.find(
-      profile => profile.language === language && profile.name === name
-    );
-
-    if (!profile) {
-      return <ProfileNotFound organization={organization} />;
-    }
-
-    const child = React.cloneElement(this.props.children, {
-      onRequestFail: this.props.onRequestFail,
-      organization,
-      profile,
-      profiles,
-      ...other
-    });
-
-    return (
-      <div id="quality-profile">
-        <Helmet title={profile.name} />
-        <ProfileHeader
-          canAdmin={this.props.canAdmin}
-          onRequestFail={this.props.onRequestFail}
-          organization={organization}
-          profile={profile}
-          updateProfiles={this.props.updateProfiles}
-        />
-        {child}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx
new file mode 100644 (file)
index 0000000..ee6d302
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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 Helmet from 'react-helmet';
+import ProfileNotFound from './ProfileNotFound';
+import ProfileHeader from '../details/ProfileHeader';
+import { IProfile } from '../types';
+
+interface Props {
+  canAdmin: boolean;
+  children: React.ReactElement<any>;
+  location: {
+    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) {
+      // try to find a quality profile with the given key
+      // if managed to find one, redirect to a new version
+      // otherwise do nothing, `render` will show not found page
+      const profile = profiles.find(profile => profile.key === location.query.key);
+      if (profile) {
+        router.replace({
+          pathname: location.pathname,
+          query: { language: profile.language, name: profile.name }
+        });
+      }
+    }
+  }
+
+  render() {
+    const { organization, profiles, location, ...other } = this.props;
+    const { key, language, name } = location.query;
+
+    if (key) {
+      // if there is a `key` parameter,
+      // then if we managed to find a quality profile with this key
+      // then we will be redirected in `componentDidMount`
+      // otherwise show `ProfileNotFound`
+      const profile = profiles.find(profile => profile.key === location.query.key);
+      return profile ? null : <ProfileNotFound organization={organization} />;
+    }
+
+    const profile = profiles.find(
+      profile => profile.language === language && profile.name === name
+    );
+
+    if (!profile) {
+      return <ProfileNotFound organization={organization} />;
+    }
+
+    const child = React.cloneElement(this.props.children, {
+      onRequestFail: this.props.onRequestFail,
+      organization,
+      profile,
+      profiles,
+      ...other
+    });
+
+    return (
+      <div id="quality-profile">
+        <Helmet title={profile.name} />
+        <ProfileHeader
+          canAdmin={this.props.canAdmin}
+          onRequestFail={this.props.onRequestFail}
+          organization={organization}
+          profile={profile}
+          updateProfiles={this.props.updateProfiles}
+        />
+        {child}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js
deleted file mode 100644 (file)
index ae878a4..0000000
+++ /dev/null
@@ -1,51 +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 { 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>
-      );
-    }
-
-    return (
-      <span title={moment(date).format('LLL')} data-toggle="tooltip">
-        {moment(date).fromNow()}
-      </span>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.tsx
new file mode 100644 (file)
index 0000000..947b15e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 { translate } from '../../../helpers/l10n';
+
+interface Props {
+  date?: string;
+}
+
+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>;
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js
deleted file mode 100644 (file)
index 2ff0534..0000000
+++ /dev/null
@@ -1,48 +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 { getProfilePath } from '../utils';
-
-/*::
-type Props = {
-  children?: React.Element<*>,
-  language: string,
-  name: string,
-  organization: ?string
-};
-*/
-
-export default class ProfileLink extends React.PureComponent {
-  /*:: props: Props; */
-
-  render() {
-    const { name, language, organization, children, ...other } = this.props;
-    return (
-      <Link
-        to={getProfilePath(name, language, organization)}
-        activeClassName="link-no-underline"
-        {...other}>
-        {children}
-      </Link>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.tsx
new file mode 100644 (file)
index 0000000..8b69532
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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 { getProfilePath } from '../utils';
+
+interface Props {
+  className?: string;
+  children?: React.ReactElement<any> | string;
+  language: string;
+  name: string;
+  organization: string | null;
+}
+
+export default function ProfileLink({ name, language, organization, children, ...other }: Props) {
+  return (
+    <Link
+      to={getProfilePath(name, language, organization)}
+      activeClassName="link-no-underline"
+      {...other}>
+      {children}
+    </Link>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js
deleted file mode 100644 (file)
index 7dce1cc..0000000
+++ /dev/null
@@ -1,50 +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 { 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; */
-
-  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>
-
-        <div>
-          {translate('quality_profiles.not_found')}
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.tsx
new file mode 100644 (file)
index 0000000..a6fd496
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { IndexLink } from 'react-router';
+import { translate } from '../../../helpers/l10n';
+import { getProfilesPath } from '../utils';
+
+interface Props {
+  organization: string | null;
+}
+
+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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.js
deleted file mode 100644 (file)
index 37d6339..0000000
+++ /dev/null
@@ -1,139 +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 Modal from 'react-modal';
-/*:: import type { Profile } from '../propTypes'; */
-import { renameProfile } from '../../../api/quality-profiles';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  onClose: () => void,
-  onRename: string => void,
-  onRequestFail: Object => void,
-  profile: Profile
-};
-*/
-
-/*::
-type State = {
-  loading: boolean,
-  name: ?string
-};
-*/
-
-export default class RenameProfileForm extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = { loading: false, name: null };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => {
-    this.setState({ name: event.currentTarget.value });
-  };
-
-  handleFormSubmit = (event /*: Event */) => {
-    event.preventDefault();
-
-    const { name } = this.state;
-
-    if (name != null) {
-      this.setState({ loading: true });
-      renameProfile(this.props.profile.key, name).then(
-        () => this.props.onRename(name),
-        error => {
-          if (this.mounted) {
-            this.setState({ loading: false });
-          }
-          this.props.onRequestFail(error);
-        }
-      );
-    }
-  };
-
-  render() {
-    const { profile } = this.props;
-    const header = translateWithParameters(
-      'quality_profiles.rename_x_title',
-      profile.name,
-      profile.languageName
-    );
-    const submitDisabled =
-      this.state.loading || !this.state.name || this.state.name === profile.name;
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <form id="rename-profile-form" onSubmit={this.handleFormSubmit}>
-          <div className="modal-head">
-            <h2>
-              {header}
-            </h2>
-          </div>
-          <div className="modal-body">
-            <div className="modal-field">
-              <label htmlFor="rename-profile-name">
-                {translate('quality_profiles.new_name')}
-                <em className="mandatory">*</em>
-              </label>
-              <input
-                autoFocus={true}
-                id="rename-profile-name"
-                maxLength="100"
-                name="name"
-                onChange={this.handleNameChange}
-                required={true}
-                size="50"
-                type="text"
-                value={this.state.name != null ? this.state.name : profile.name}
-              />
-            </div>
-          </div>
-          <div className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            <button disabled={submitDisabled} id="rename-profile-submit">
-              {translate('rename')}
-            </button>
-            <a href="#" id="rename-profile-cancel" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </div>
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/RenameProfileForm.tsx
new file mode 100644 (file)
index 0000000..1d14919
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * 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 Modal from 'react-modal';
+import { renameProfile } from '../../../api/quality-profiles';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  onClose: () => void;
+  onRename: (name: string) => void;
+  onRequestFail: (reason: any) => void;
+  profile: IProfile;
+}
+
+interface State {
+  loading: boolean;
+  name: string | null;
+}
+
+export default class RenameProfileForm extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: false, name: null };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+    this.setState({ name: event.currentTarget.value });
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+
+    const { name } = this.state;
+
+    if (name != null) {
+      this.setState({ loading: true });
+      renameProfile(this.props.profile.key, name).then(
+        () => this.props.onRename(name),
+        (error: any) => {
+          if (this.mounted) {
+            this.setState({ loading: false });
+          }
+          this.props.onRequestFail(error);
+        }
+      );
+    }
+  };
+
+  render() {
+    const { profile } = this.props;
+    const header = translateWithParameters(
+      'quality_profiles.rename_x_title',
+      profile.name,
+      profile.languageName
+    );
+    const submitDisabled =
+      this.state.loading || !this.state.name || this.state.name === profile.name;
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="rename-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>
+              {header}
+            </h2>
+          </div>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="rename-profile-name">
+                {translate('quality_profiles.new_name')}
+                <em className="mandatory">*</em>
+              </label>
+              <input
+                autoFocus={true}
+                id="rename-profile-name"
+                maxLength={100}
+                name="name"
+                onChange={this.handleNameChange}
+                required={true}
+                size={50}
+                type="text"
+                value={this.state.name != null ? this.state.name : profile.name}
+              />
+            </div>
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} id="rename-profile-submit">
+              {translate('rename')}
+            </button>
+            <a href="#" id="rename-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.js
deleted file mode 100644 (file)
index 08aa98c..0000000
+++ /dev/null
@@ -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.
- */
-import { shallow } from 'enzyme';
-import React from 'react';
-import Helmet from 'react-helmet';
-import ProfileContainer from '../ProfileContainer';
-import ProfileNotFound from '../ProfileNotFound';
-import ProfileHeader from '../../details/ProfileHeader';
-import { createFakeProfile } from '../../utils';
-
-it('should render ProfileHeader', () => {
-  const targetProfile = createFakeProfile({ language: 'js', name: 'fake' });
-  const profiles = [targetProfile, createFakeProfile({ language: 'js', name: 'another' })];
-  const updateProfiles = jest.fn();
-  const output = shallow(
-    <ProfileContainer
-      location={{ query: { language: 'js', name: 'fake' } }}
-      profiles={profiles}
-      canAdmin={false}
-      updateProfiles={updateProfiles}>
-      <div />
-    </ProfileContainer>
-  );
-  const header = output.find(ProfileHeader);
-  expect(header.length).toBe(1);
-  expect(header.prop('profile')).toBe(targetProfile);
-  expect(header.prop('canAdmin')).toBe(false);
-  expect(header.prop('updateProfiles')).toBe(updateProfiles);
-});
-
-it('should render ProfileNotFound', () => {
-  const profiles = [
-    createFakeProfile({ language: 'js', name: 'fake' }),
-    createFakeProfile({ language: 'js', name: 'another' })
-  ];
-  const output = shallow(
-    <ProfileContainer
-      location={{ query: { language: 'js', name: 'random' } }}
-      profiles={profiles}
-      canAdmin={false}
-      updateProfiles={() => true}>
-      <div />
-    </ProfileContainer>
-  );
-  expect(output.is(ProfileNotFound)).toBe(true);
-});
-
-it('should render Helmet', () => {
-  const profiles = [createFakeProfile({ language: 'js', name: 'First Profile' })];
-  const updateProfiles = jest.fn();
-  const output = shallow(
-    <ProfileContainer
-      location={{ query: { language: 'js', name: 'First Profile' } }}
-      profiles={profiles}
-      canAdmin={false}
-      updateProfiles={updateProfiles}>
-      <div />
-    </ProfileContainer>
-  );
-  const helmet = output.find(Helmet);
-  expect(helmet.length).toBe(1);
-  expect(helmet.prop('title')).toContain('First Profile');
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/__tests__/ProfileContainer-test.tsx
new file mode 100644 (file)
index 0000000..1c0efc6
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import Helmet from 'react-helmet';
+import ProfileContainer from '../ProfileContainer';
+import ProfileNotFound from '../ProfileNotFound';
+import ProfileHeader from '../../details/ProfileHeader';
+import { createFakeProfile } from '../../utils';
+
+it('should render ProfileHeader', () => {
+  const targetProfile = createFakeProfile({ language: 'js', name: 'fake' });
+  const profiles = [targetProfile, createFakeProfile({ language: 'js', name: 'another' })];
+  const updateProfiles = jest.fn();
+  const output = shallow(
+    <ProfileContainer
+      canAdmin={false}
+      location={{ pathname: '', query: { language: 'js', name: 'fake' } }}
+      onRequestFail={jest.fn()}
+      organization={null}
+      profiles={profiles}
+      router={{} as any}
+      updateProfiles={updateProfiles}>
+      <div />
+    </ProfileContainer>
+  );
+  const header = output.find(ProfileHeader);
+  expect(header.length).toBe(1);
+  expect(header.prop('profile')).toBe(targetProfile);
+  expect(header.prop('canAdmin')).toBe(false);
+  expect(header.prop('updateProfiles')).toBe(updateProfiles);
+});
+
+it('should render ProfileNotFound', () => {
+  const profiles = [
+    createFakeProfile({ language: 'js', name: 'fake' }),
+    createFakeProfile({ language: 'js', name: 'another' })
+  ];
+  const output = shallow(
+    <ProfileContainer
+      canAdmin={false}
+      location={{ pathname: '', query: { language: 'js', name: 'random' } }}
+      onRequestFail={jest.fn()}
+      organization={null}
+      profiles={profiles}
+      router={{} as any}
+      updateProfiles={jest.fn()}>
+      <div />
+    </ProfileContainer>
+  );
+  expect(output.is(ProfileNotFound)).toBe(true);
+});
+
+it('should render Helmet', () => {
+  const profiles = [createFakeProfile({ language: 'js', name: 'First Profile' })];
+  const updateProfiles = jest.fn();
+  const output = shallow(
+    <ProfileContainer
+      canAdmin={false}
+      location={{ pathname: '', query: { language: 'js', name: 'First Profile' } }}
+      onRequestFail={jest.fn()}
+      organization={null}
+      profiles={profiles}
+      router={{} as any}
+      updateProfiles={updateProfiles}>
+      <div />
+    </ProfileContainer>
+  );
+  const helmet = output.find(Helmet);
+  expect(helmet.length).toBe(1);
+  expect(helmet.prop('title')).toContain('First Profile');
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.js
deleted file mode 100644 (file)
index f7d019e..0000000
+++ /dev/null
@@ -1,150 +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 Modal from 'react-modal';
-import Select from 'react-select';
-import { sortBy } from 'lodash';
-import { changeProfileParent } from '../../../api/quality-profiles';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-type Props = {
-  onChange: () => void,
-  onClose: () => void,
-  onRequestFail: Object => void,
-  profile: Profile,
-  profiles: Array<Profile>
-};
-*/
-
-/*::
-type State = {
-  loading: boolean,
-  selected: ?string
-};
-*/
-
-export default class ChangeParentForm extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = {
-    loading: false,
-    selected: null
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleSelectChange = (option /*: { value: string } */) => {
-    this.setState({ selected: option.value });
-  };
-
-  handleFormSubmit = (event /*: Event */) => {
-    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);
-      });
-    }
-  };
-
-  render() {
-    const { profiles } = this.props;
-
-    const options = [
-      { label: translate('none'), value: '' },
-      ...sortBy(profiles, 'name').map(profile => ({
-        label: profile.isBuiltIn
-          ? `${profile.name} (${translate('quality_profiles.built_in')})`
-          : profile.name,
-        value: profile.key
-      }))
-    ];
-
-    const submitDisabled =
-      this.state.loading ||
-      this.state.selected == null ||
-      this.state.selected === this.props.profile.parentKey;
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={translate('quality_profiles.change_parent')}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <form id="change-profile-parent-form" onSubmit={this.handleFormSubmit}>
-          <div className="modal-head">
-            <h2>
-              {translate('quality_profiles.change_parent')}
-            </h2>
-          </div>
-          <div className="modal-body">
-            <div className="modal-field">
-              <label htmlFor="change-profile-parent">
-                {translate('quality_profiles.parent')}: <em className="mandatory">*</em>
-              </label>
-              <Select
-                clearable={false}
-                id="change-profile-parent"
-                name="parentKey"
-                onChange={this.handleSelectChange}
-                options={options}
-                value={
-                  this.state.selected != null
-                    ? this.state.selected
-                    : this.props.profile.parentKey || ''
-                }
-              />
-            </div>
-          </div>
-          <div className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            <button disabled={submitDisabled} id="change-profile-parent-submit">
-              {translate('change_verb')}
-            </button>
-            <a href="#" id="change-profile-parent-cancel" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </div>
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeParentForm.tsx
new file mode 100644 (file)
index 0000000..fd8292c
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * 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 Modal from 'react-modal';
+import * as Select from 'react-select';
+import { sortBy } from 'lodash';
+import { changeProfileParent } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  onChange: () => void;
+  onClose: () => void;
+  onRequestFail: (reasong: any) => void;
+  profile: IProfile;
+  profiles: IProfile[];
+}
+
+interface State {
+  loading: boolean;
+  selected: string | null;
+}
+
+export default class ChangeParentForm extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = {
+    loading: false,
+    selected: null
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleSelectChange = (option: { value: string }) => {
+    this.setState({ selected: option.value });
+  };
+
+  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: any) => {
+          if (this.mounted) {
+            this.setState({ loading: false });
+          }
+          this.props.onRequestFail(error);
+        });
+    }
+  };
+
+  render() {
+    const { profiles } = this.props;
+
+    const options = [
+      { label: translate('none'), value: '' },
+      ...sortBy(profiles, 'name').map(profile => ({
+        label: profile.isBuiltIn
+          ? `${profile.name} (${translate('quality_profiles.built_in')})`
+          : profile.name,
+        value: profile.key
+      }))
+    ];
+
+    const submitDisabled =
+      this.state.loading ||
+      this.state.selected == null ||
+      this.state.selected === this.props.profile.parentKey;
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={translate('quality_profiles.change_parent')}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="change-profile-parent-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>
+              {translate('quality_profiles.change_parent')}
+            </h2>
+          </div>
+          <div className="modal-body">
+            <div className="modal-field">
+              <label htmlFor="change-profile-parent">
+                {translate('quality_profiles.parent')}: <em className="mandatory">*</em>
+              </label>
+              <Select
+                clearable={false}
+                name="parentKey"
+                onChange={this.handleSelectChange}
+                options={options}
+                value={
+                  this.state.selected != null
+                    ? this.state.selected
+                    : this.props.profile.parentKey || ''
+                }
+              />
+            </div>
+          </div>
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            <button disabled={submitDisabled} id="change-profile-parent-submit">
+              {translate('change_verb')}
+            </button>
+            <a href="#" id="change-profile-parent-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.js
deleted file mode 100644 (file)
index a327763..0000000
+++ /dev/null
@@ -1,105 +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 Modal from 'react-modal';
-import escapeHtml from 'escape-html';
-/*:: import type { Profile } from '../propTypes'; */
-import SelectList from '../../../components/SelectList';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  onClose: () => void,
-  organization: ?string,
-  profile: Profile
-};
-*/
-
-export default class ChangeProjectsForm extends React.PureComponent {
-  /*:: container: HTMLElement; */
-  /*:: props: Props; */
-
-  handleCloseClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  renderSelectList = () => {
-    const { key } = this.props.profile;
-
-    const searchUrl =
-      window.baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key);
-
-    new SelectList({
-      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',
-      extra: { profileKey: key },
-      selectParameter: 'projectUuid',
-      selectParameterValue: 'uuid',
-      labels: {
-        selected: translate('quality_gates.projects.with'),
-        deselected: translate('quality_gates.projects.without'),
-        all: translate('quality_gates.projects.all'),
-        noResults: translate('quality_gates.projects.noResults')
-      },
-      tooltips: {
-        select: translate('quality_profiles.projects.select_hint'),
-        deselect: translate('quality_profiles.projects.deselect_hint')
-      }
-    });
-  };
-
-  render() {
-    const header = translate('projects');
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        onAfterOpen={this.renderSelectList}
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <div className="modal-head">
-          <h2>
-            {header}
-          </h2>
-        </div>
-
-        <div className="modal-body">
-          <div id="profile-projects" ref={node => (this.container = node)} />
-        </div>
-
-        <div className="modal-foot">
-          <a href="#" onClick={this.handleCloseClick}>
-            {translate('close')}
-          </a>
-        </div>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ChangeProjectsForm.tsx
new file mode 100644 (file)
index 0000000..1908588
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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 Modal from 'react-modal';
+import * as escapeHtml from 'escape-html';
+import SelectList from '../../../components/SelectList';
+import { translate } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  onClose: () => void;
+  organization: string | null;
+  profile: IProfile;
+}
+
+export default class ChangeProjectsForm extends React.PureComponent<Props> {
+  container: HTMLElement;
+
+  handleCloseClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  renderSelectList = () => {
+    const { key } = this.props.profile;
+
+    const searchUrl =
+      (window as any).baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key);
+
+    new (SelectList as any)({
+      searchUrl,
+      el: this.container,
+      width: '100%',
+      readOnly: false,
+      focusSearch: false,
+      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',
+      labels: {
+        selected: translate('quality_gates.projects.with'),
+        deselected: translate('quality_gates.projects.without'),
+        all: translate('quality_gates.projects.all'),
+        noResults: translate('quality_gates.projects.noResults')
+      },
+      tooltips: {
+        select: translate('quality_profiles.projects.select_hint'),
+        deselect: translate('quality_profiles.projects.deselect_hint')
+      }
+    });
+  };
+
+  render() {
+    const header = translate('projects');
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        onAfterOpen={this.renderSelectList}
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <div className="modal-head">
+          <h2>
+            {header}
+          </h2>
+        </div>
+
+        <div className="modal-body">
+          <div id="profile-projects" ref={node => (this.container = node as HTMLElement)} />
+        </div>
+
+        <div className="modal-foot">
+          <a href="#" onClick={this.handleCloseClick}>
+            {translate('close')}
+          </a>
+        </div>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
deleted file mode 100644 (file)
index b157db4..0000000
+++ /dev/null
@@ -1,59 +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 ProfileRules from './ProfileRules';
-import ProfileProjects from './ProfileProjects';
-import ProfileInheritance from './ProfileInheritance';
-import ProfileExporters from './ProfileExporters';
-/*:: import type { Profile, Exporter } from '../propTypes'; */
-
-/*::
-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; */
-
-  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>
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.tsx
new file mode 100644 (file)
index 0000000..a01130a
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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 ProfileRules from './ProfileRules';
+import ProfileProjects from './ProfileProjects';
+import ProfileInheritance from './ProfileInheritance';
+import ProfileExporters from './ProfileExporters';
+import { IExporter, IProfile } from '../types';
+
+interface Props {
+  canAdmin: boolean;
+  exporters: IExporter[];
+  onRequestFail: (reasong: any) => void;
+  organization: string | null;
+  profile: IProfile;
+  profiles: IProfile[];
+  updateProfiles: () => Promise<void>;
+}
+
+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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js
deleted file mode 100644 (file)
index 04f3c58..0000000
+++ /dev/null
@@ -1,79 +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 { stringify } from 'querystring';
-import React from 'react';
-import { translate } from '../../../helpers/l10n';
-/*:: import type { Profile, Exporter } from '../propTypes'; */
-
-/*::
-type Props = {
-  exporters: Array<Exporter>,
-  organization: ?string,
-  profile: Profile
-};
-*/
-
-export default class ProfileExporters extends React.PureComponent {
-  /*:: props: Props; */
-
-  getExportUrl(exporter /*: Exporter */) {
-    const { organization, profile } = this.props;
-
-    const path = '/api/qualityprofiles/export';
-    const parameters /*: { [string]: string } */ = {
-      exporterKey: exporter.key,
-      language: profile.language,
-      name: profile.name
-    };
-    if (organization) {
-      Object.assign(parameters, { organization });
-    }
-    return window.baseUrl + path + '?' + stringify(parameters);
-  }
-
-  render() {
-    const { exporters, profile } = this.props;
-    const exportersForLanguage = exporters.filter(e => e.languages.includes(profile.language));
-
-    if (exportersForLanguage.length === 0) {
-      return null;
-    }
-
-    return (
-      <div className="quality-profile-box quality-profile-exporters">
-        <header className="big-spacer-bottom">
-          <h2>
-            {translate('quality_profiles.exporters')}
-          </h2>
-        </header>
-        <ul>
-          {exportersForLanguage.map(exporter =>
-            <li key={exporter.key} data-key={exporter.key} className="spacer-top">
-              <a href={this.getExportUrl(exporter)} target="_blank">
-                {exporter.name}
-              </a>
-            </li>
-          )}
-        </ul>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.tsx
new file mode 100644 (file)
index 0000000..71913fa
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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 { stringify } from 'querystring';
+import * as React from 'react';
+import { translate } from '../../../helpers/l10n';
+import { IProfile, IExporter } from '../types';
+
+interface Props {
+  exporters: IExporter[];
+  organization: string | null;
+  profile: IProfile;
+}
+
+export default class ProfileExporters extends React.PureComponent<Props> {
+  getExportUrl(exporter: IExporter) {
+    const { organization, profile } = this.props;
+
+    const path = '/api/qualityprofiles/export';
+    const parameters = {
+      exporterKey: exporter.key,
+      language: profile.language,
+      name: profile.name
+    };
+    if (organization) {
+      Object.assign(parameters, { organization });
+    }
+    return (window as any).baseUrl + path + '?' + stringify(parameters);
+  }
+
+  render() {
+    const { exporters, profile } = this.props;
+    const exportersForLanguage = exporters.filter(e => e.languages.includes(profile.language));
+
+    if (exportersForLanguage.length === 0) {
+      return null;
+    }
+
+    return (
+      <div className="quality-profile-box quality-profile-exporters">
+        <header className="big-spacer-bottom">
+          <h2>
+            {translate('quality_profiles.exporters')}
+          </h2>
+        </header>
+        <ul>
+          {exportersForLanguage.map(exporter =>
+            <li key={exporter.key} data-key={exporter.key} className="spacer-top">
+              <a href={this.getExportUrl(exporter)} target="_blank">
+                {exporter.name}
+              </a>
+            </li>
+          )}
+        </ul>
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js
deleted file mode 100644 (file)
index f5691c5..0000000
+++ /dev/null
@@ -1,159 +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, IndexLink } from 'react-router';
-import ProfileLink from '../components/ProfileLink';
-import ProfileActions from '../components/ProfileActions';
-import ProfileDate from '../components/ProfileDate';
-import BuiltInBadge from '../components/BuiltInBadge';
-import { translate } from '../../../helpers/l10n';
-import {
-  isStagnant,
-  getProfilesPath,
-  getProfilesForLanguagePath,
-  getProfileChangelogPath
-} from '../utils';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-type Props = {
-  canAdmin: boolean,
-  onRequestFail: Object => void,
-  organization: ?string,
-  profile: Profile,
-  updateProfiles: () => Promise<*>
-};
-*/
-
-export default class ProfileHeader extends React.PureComponent {
-  /*:: props: Props; */
-
-  renderUpdateDate() {
-    const { profile } = this.props;
-    let inner = (
-      <span>
-        {translate('quality_profiles.updated_')} <ProfileDate date={profile.userUpdatedAt} />
-      </span>
-    );
-    if (isStagnant(profile)) {
-      inner = (
-        <span className="badge badge-normal-size badge-focus">
-          {inner}
-        </span>
-      );
-    }
-    return (
-      <li className="small spacer-right">
-        {inner}
-      </li>
-    );
-  }
-
-  renderUsageDate() {
-    const { profile } = this.props;
-    let inner = (
-      <span>
-        {translate('quality_profiles.used_')} <ProfileDate date={profile.lastUsed} />
-      </span>
-    );
-    if (!profile.lastUsed) {
-      inner = (
-        <span className="badge badge-normal-size badge-focus">
-          {inner}
-        </span>
-      );
-    }
-
-    return (
-      <li className="small big-spacer-right">
-        {inner}
-      </li>
-    );
-  }
-
-  render() {
-    const { organization, profile } = this.props;
-
-    return (
-      <header className="page-header quality-profile-header">
-        <div className="note spacer-bottom">
-          <IndexLink to={getProfilesPath(organization)} className="text-muted">
-            {translate('quality_profiles.page')}
-          </IndexLink>
-          {' / '}
-          <Link
-            to={getProfilesForLanguagePath(profile.language, organization)}
-            className="text-muted">
-            {profile.languageName}
-          </Link>
-        </div>
-
-        <h1 className="page-title">
-          <ProfileLink
-            className="link-base-color"
-            language={profile.language}
-            name={profile.name}
-            organization={organization}>
-            <span>
-              {profile.name}
-            </span>
-          </ProfileLink>
-          {profile.isBuiltIn && <BuiltInBadge className="spacer-left" tooltip={false} />}
-        </h1>
-
-        <div className="pull-right">
-          <ul className="list-inline" style={{ lineHeight: '24px' }}>
-            {this.renderUpdateDate()}
-            {this.renderUsageDate()}
-            <li>
-              <Link
-                to={getProfileChangelogPath(profile.name, profile.language, organization)}
-                className="button">
-                {translate('changelog')}
-              </Link>
-            </li>
-            <li>
-              <div className="pull-left dropdown">
-                <button className="dropdown-toggle" data-toggle="dropdown">
-                  {translate('actions')} <i className="icon-dropdown" />
-                </button>
-                <ProfileActions
-                  canAdmin={this.props.canAdmin}
-                  onRequestFail={this.props.onRequestFail}
-                  organization={organization}
-                  profile={profile}
-                  updateProfiles={this.props.updateProfiles}
-                />
-              </div>
-            </li>
-          </ul>
-        </div>
-
-        {profile.isBuiltIn &&
-          <div className="page-description">
-            {translate('quality_profiles.built_in.description.1')}
-            <br />
-            {translate('quality_profiles.built_in.description.2')}
-          </div>}
-      </header>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.tsx
new file mode 100644 (file)
index 0000000..ba05982
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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, IndexLink } from 'react-router';
+import ProfileLink from '../components/ProfileLink';
+import ProfileActions from '../components/ProfileActions';
+import ProfileDate from '../components/ProfileDate';
+import BuiltInBadge from '../components/BuiltInBadge';
+import { translate } from '../../../helpers/l10n';
+import {
+  isStagnant,
+  getProfilesPath,
+  getProfilesForLanguagePath,
+  getProfileChangelogPath
+} from '../utils';
+import { IProfile } from '../types';
+
+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 = (
+      <span>
+        {translate('quality_profiles.updated_')} <ProfileDate date={profile.userUpdatedAt} />
+      </span>
+    );
+    if (isStagnant(profile)) {
+      inner = (
+        <span className="badge badge-normal-size badge-focus">
+          {inner}
+        </span>
+      );
+    }
+    return (
+      <li className="small spacer-right">
+        {inner}
+      </li>
+    );
+  }
+
+  renderUsageDate() {
+    const { profile } = this.props;
+    let inner = (
+      <span>
+        {translate('quality_profiles.used_')} <ProfileDate date={profile.lastUsed} />
+      </span>
+    );
+    if (!profile.lastUsed) {
+      inner = (
+        <span className="badge badge-normal-size badge-focus">
+          {inner}
+        </span>
+      );
+    }
+
+    return (
+      <li className="small big-spacer-right">
+        {inner}
+      </li>
+    );
+  }
+
+  render() {
+    const { organization, profile } = this.props;
+
+    return (
+      <header className="page-header quality-profile-header">
+        <div className="note spacer-bottom">
+          <IndexLink to={getProfilesPath(organization)} className="text-muted">
+            {translate('quality_profiles.page')}
+          </IndexLink>
+          {' / '}
+          <Link
+            to={getProfilesForLanguagePath(profile.language, organization)}
+            className="text-muted">
+            {profile.languageName}
+          </Link>
+        </div>
+
+        <h1 className="page-title">
+          <ProfileLink
+            className="link-base-color"
+            language={profile.language}
+            name={profile.name}
+            organization={organization}>
+            <span>
+              {profile.name}
+            </span>
+          </ProfileLink>
+          {profile.isBuiltIn && <BuiltInBadge className="spacer-left" tooltip={false} />}
+        </h1>
+
+        <div className="pull-right">
+          <ul className="list-inline" style={{ lineHeight: '24px' }}>
+            {this.renderUpdateDate()}
+            {this.renderUsageDate()}
+            <li>
+              <Link
+                to={getProfileChangelogPath(profile.name, profile.language, organization)}
+                className="button">
+                {translate('changelog')}
+              </Link>
+            </li>
+            <li>
+              <div className="pull-left dropdown">
+                <button className="dropdown-toggle" data-toggle="dropdown">
+                  {translate('actions')} <i className="icon-dropdown" />
+                </button>
+                <ProfileActions
+                  canAdmin={this.props.canAdmin}
+                  onRequestFail={this.props.onRequestFail}
+                  organization={organization}
+                  profile={profile}
+                  updateProfiles={this.props.updateProfiles}
+                />
+              </div>
+            </li>
+          </ul>
+        </div>
+
+        {profile.isBuiltIn &&
+          <div className="page-description">
+            {translate('quality_profiles.built_in.description.1')}
+            <br />
+            {translate('quality_profiles.built_in.description.2')}
+          </div>}
+      </header>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js
deleted file mode 100644 (file)
index 9669bd0..0000000
+++ /dev/null
@@ -1,191 +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 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 */ = {
-    formOpen: false,
-    loading: true
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadData();
-  }
-
-  componentDidUpdate(prevProps /*: Props */) {
-    if (prevProps.profile !== this.props.profile) {
-      this.loadData();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadData() {
-    getProfileInheritance(this.props.profile.key).then(r => {
-      if (this.mounted) {
-        const { ancestors, children } = r;
-        this.setState({
-          children,
-          ancestors: ancestors.reverse(),
-          profile: r.profile,
-          loading: false
-        });
-      }
-    });
-  }
-
-  handleChangeParentClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ formOpen: true });
-  };
-
-  closeForm = () => {
-    this.setState({ formOpen: false });
-  };
-
-  handleParentChange = () => {
-    this.props.updateProfiles();
-    this.closeForm();
-  };
-
-  render() {
-    const { profile, profiles } = this.props;
-    const { ancestors } = this.state;
-
-    const highlightCurrent =
-      !this.state.loading &&
-      ancestors != null &&
-      this.state.children != null &&
-      (ancestors.length > 0 || this.state.children.length > 0);
-
-    const currentClassName = classNames('js-inheritance-current', {
-      selected: highlightCurrent
-    });
-
-    const extendsBuiltIn = ancestors != null && ancestors.some(profile => profile.isBuiltIn);
-
-    return (
-      <div className="quality-profile-inheritance">
-        <header className="big-spacer-bottom clearfix">
-          <h2 className="pull-left">
-            {translate('quality_profiles.profile_inheritance')}
-          </h2>
-          {this.props.canAdmin &&
-            !this.props.profile.isBuiltIn &&
-            <button className="pull-right js-change-parent" onClick={this.handleChangeParentClick}>
-              {translate('quality_profiles.change_parent')}
-            </button>}
-        </header>
-
-        {!this.state.loading &&
-          <table className="data zebra">
-            <tbody>
-              {ancestors != null &&
-                ancestors.map((ancestor, index) =>
-                  <ProfileInheritanceBox
-                    className="js-inheritance-ancestor"
-                    depth={index}
-                    key={ancestor.key}
-                    language={profile.language}
-                    organization={this.props.organization}
-                    profile={ancestor}
-                  />
-                )}
-
-              <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 =>
-                  <ProfileInheritanceBox
-                    className="js-inheritance-child"
-                    depth={ancestors ? ancestors.length + 1 : 0}
-                    key={child.key}
-                    language={profile.language}
-                    organization={this.props.organization}
-                    profile={child}
-                  />
-                )}
-            </tbody>
-          </table>}
-
-        {this.state.formOpen &&
-          <ChangeParentForm
-            onChange={this.handleParentChange}
-            onClose={this.closeForm}
-            onRequestFail={this.props.onRequestFail}
-            profile={profile}
-            profiles={profiles.filter(p => p !== profile && p.language === profile.language)}
-          />}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.tsx
new file mode 100644 (file)
index 0000000..a7c6173
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * 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 classNames from 'classnames';
+import ProfileInheritanceBox from './ProfileInheritanceBox';
+import ChangeParentForm from './ChangeParentForm';
+import { translate } from '../../../helpers/l10n';
+import { getProfileInheritance } from '../../../api/quality-profiles';
+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
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadData();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.profile !== this.props.profile) {
+      this.loadData();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadData() {
+    getProfileInheritance(this.props.profile.key).then((r: any) => {
+      if (this.mounted) {
+        const { ancestors, children } = r;
+        this.setState({
+          children,
+          ancestors: ancestors.reverse(),
+          profile: r.profile,
+          loading: false
+        });
+      }
+    });
+  }
+
+  handleChangeParentClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ formOpen: true });
+  };
+
+  closeForm = () => {
+    this.setState({ formOpen: false });
+  };
+
+  handleParentChange = () => {
+    this.props.updateProfiles();
+    this.closeForm();
+  };
+
+  render() {
+    const { profile, profiles } = this.props;
+    const { ancestors } = this.state;
+
+    const highlightCurrent =
+      !this.state.loading &&
+      ancestors != null &&
+      this.state.children != null &&
+      (ancestors.length > 0 || this.state.children.length > 0);
+
+    const currentClassName = classNames('js-inheritance-current', {
+      selected: highlightCurrent
+    });
+
+    const extendsBuiltIn = ancestors != null && ancestors.some(profile => profile.isBuiltIn);
+
+    return (
+      <div className="quality-profile-inheritance">
+        <header className="big-spacer-bottom clearfix">
+          <h2 className="pull-left">
+            {translate('quality_profiles.profile_inheritance')}
+          </h2>
+          {this.props.canAdmin &&
+            !this.props.profile.isBuiltIn &&
+            <button className="pull-right js-change-parent" onClick={this.handleChangeParentClick}>
+              {translate('quality_profiles.change_parent')}
+            </button>}
+        </header>
+
+        {!this.state.loading &&
+          <table className="data zebra">
+            <tbody>
+              {ancestors != null &&
+                ancestors.map((ancestor, index) =>
+                  <ProfileInheritanceBox
+                    className="js-inheritance-ancestor"
+                    depth={index}
+                    key={ancestor.key}
+                    language={profile.language}
+                    organization={this.props.organization}
+                    profile={ancestor}
+                  />
+                )}
+
+              {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 =>
+                  <ProfileInheritanceBox
+                    className="js-inheritance-child"
+                    depth={ancestors ? ancestors.length + 1 : 0}
+                    key={child.key}
+                    language={profile.language}
+                    organization={this.props.organization}
+                    profile={child}
+                  />
+                )}
+            </tbody>
+          </table>}
+
+        {this.state.formOpen &&
+          <ChangeParentForm
+            onChange={this.handleParentChange}
+            onClose={this.closeForm}
+            onRequestFail={this.props.onRequestFail}
+            profile={profile}
+            profiles={profiles.filter(p => p !== profile && p.language === profile.language)}
+          />}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js
deleted file mode 100644 (file)
index 1adf7c7..0000000
+++ /dev/null
@@ -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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.tsx
new file mode 100644 (file)
index 0000000..2455e39
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js
deleted file mode 100644 (file)
index 984b799..0000000
+++ /dev/null
@@ -1,167 +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 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 */ = {
-    formOpen: false,
-    loading: true,
-    projects: null
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadProjects();
-  }
-
-  componentDidUpdate(prevProps /*: Props */) {
-    if (prevProps.profile !== this.props.profile) {
-      this.loadProjects();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadProjects() {
-    if (this.props.profile.isDefault) {
-      return;
-    }
-
-    const data = { key: this.props.profile.key };
-    getProfileProjects(data).then(r => {
-      if (this.mounted) {
-        this.setState({
-          projects: r.results,
-          more: r.more,
-          loading: false
-        });
-      }
-    });
-  }
-
-  handleChangeClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ formOpen: true });
-  };
-
-  closeForm = () => {
-    this.setState({ formOpen: false });
-    this.props.updateProfiles();
-  };
-
-  renderDefault() {
-    return (
-      <div>
-        <span className="badge spacer-right">
-          {translate('default')}
-        </span>
-        {translate('quality_profiles.projects_for_default')}
-      </div>
-    );
-  }
-
-  renderProjects() {
-    const { projects } = this.state;
-
-    if (projects == null) {
-      return null;
-    }
-
-    if (projects.length === 0) {
-      return (
-        <div>
-          {translate('quality_profiles.no_projects_associated_to_profile')}
-        </div>
-      );
-    }
-
-    return (
-      <ul>
-        {projects.map(project =>
-          <li key={project.uuid} className="spacer-top js-profile-project" data-key={project.key}>
-            <Link
-              to={{ pathname: '/dashboard', query: { id: project.key } }}
-              className="link-with-icon">
-              <QualifierIcon qualifier="TRK" /> <span>{project.name}</span>
-            </Link>
-          </li>
-        )}
-      </ul>
-    );
-  }
-
-  render() {
-    return (
-      <div className="quality-profile-projects">
-        <header className="page-header">
-          <h2 className="page-title">
-            {translate('projects')}
-          </h2>
-
-          {this.props.canAdmin &&
-            !this.props.profile.isDefault &&
-            <div className="pull-right">
-              <button className="js-change-projects" onClick={this.handleChangeClick}>
-                {translate('quality_profiles.change_projects')}
-              </button>
-            </div>}
-        </header>
-
-        {this.props.profile.isDefault ? this.renderDefault() : this.renderProjects()}
-
-        {this.state.formOpen &&
-          <ChangeProjectsForm
-            onClose={this.closeForm}
-            organization={this.props.organization}
-            profile={this.props.profile}
-          />}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx
new file mode 100644 (file)
index 0000000..92d30c2
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * 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 ChangeProjectsForm from './ChangeProjectsForm';
+import QualifierIcon from '../../../components/shared/QualifierIcon';
+import { getProfileProjects } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
+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
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadProjects();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.profile !== this.props.profile) {
+      this.loadProjects();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadProjects() {
+    if (this.props.profile.isDefault) {
+      return;
+    }
+
+    const data = { key: this.props.profile.key };
+    getProfileProjects(data).then((r: any) => {
+      if (this.mounted) {
+        this.setState({
+          projects: r.results,
+          more: r.more,
+          loading: false
+        });
+      }
+    });
+  }
+
+  handleChangeClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ formOpen: true });
+  };
+
+  closeForm = () => {
+    this.setState({ formOpen: false });
+    this.props.updateProfiles();
+  };
+
+  renderDefault() {
+    return (
+      <div>
+        <span className="badge spacer-right">
+          {translate('default')}
+        </span>
+        {translate('quality_profiles.projects_for_default')}
+      </div>
+    );
+  }
+
+  renderProjects() {
+    const { projects } = this.state;
+
+    if (projects == null) {
+      return null;
+    }
+
+    if (projects.length === 0) {
+      return (
+        <div>
+          {translate('quality_profiles.no_projects_associated_to_profile')}
+        </div>
+      );
+    }
+
+    return (
+      <ul>
+        {projects.map(project =>
+          <li key={project.uuid} className="spacer-top js-profile-project" data-key={project.key}>
+            <Link
+              to={{ pathname: '/dashboard', query: { id: project.key } }}
+              className="link-with-icon">
+              <QualifierIcon qualifier="TRK" /> <span>{project.name}</span>
+            </Link>
+          </li>
+        )}
+      </ul>
+    );
+  }
+
+  render() {
+    return (
+      <div className="quality-profile-projects">
+        <header className="page-header">
+          <h2 className="page-title">
+            {translate('projects')}
+          </h2>
+
+          {this.props.canAdmin &&
+            !this.props.profile.isDefault &&
+            <div className="pull-right">
+              <button className="js-change-projects" onClick={this.handleChangeClick}>
+                {translate('quality_profiles.change_projects')}
+              </button>
+            </div>}
+        </header>
+
+        {this.props.profile.isDefault ? this.renderDefault() : this.renderProjects()}
+
+        {this.state.formOpen &&
+          <ChangeProjectsForm
+            onClose={this.closeForm}
+            organization={this.props.organization}
+            profile={this.props.profile}
+          />}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js
deleted file mode 100644 (file)
index ceb3fbe..0000000
+++ /dev/null
@@ -1,216 +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 { keyBy } from 'lodash';
-import ProfileRulesRowOfType from './ProfileRulesRowOfType';
-import ProfileRulesRowTotal from './ProfileRulesRowTotal';
-import ProfileRulesDeprecatedWarning from './ProfileRulesDeprecatedWarning';
-import ProfileRulesSonarWayComparison from './ProfileRulesSonarWayComparison';
-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'; */
-
-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 */ = {
-    activatedTotal: null,
-    activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
-    allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
-    compareToSonarWay: null,
-    loading: true,
-    total: null
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadRules();
-  }
-
-  componentDidUpdate(prevProps /*: Props */) {
-    if (prevProps.profile !== this.props.profile) {
-      this.loadRules();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadProfile() {
-    if (this.props.profile.isBuiltIn) {
-      return Promise.resolve(null);
-    }
-    return getQualityProfiles({
-      compareToSonarWay: true,
-      profile: this.props.profile.key
-    });
-  }
-
-  loadAllRules() {
-    return searchRules({
-      languages: this.props.profile.language,
-      ps: 1,
-      facets: 'types'
-    });
-  }
-
-  loadActivatedRules() {
-    return searchRules({
-      qprofile: this.props.profile.key,
-      activation: 'true',
-      ps: 1,
-      facets: 'types'
-    });
-  }
-
-  loadRules() {
-    this.setState({ loading: true });
-    Promise.all([
-      this.loadAllRules(),
-      this.loadActivatedRules(),
-      this.loadProfile()
-    ]).then(responses => {
-      if (this.mounted) {
-        const [allRules, activatedRules, showProfile] = responses;
-        this.setState({
-          activatedTotal: activatedRules.total,
-          allByType: keyBy(takeFacet(allRules, 'types'), 'val'),
-          activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val'),
-          compareToSonarWay: showProfile && showProfile.compareToSonarWay,
-          loading: false,
-          total: allRules.total
-        });
-      }
-    });
-  }
-
-  getRulesCountForType(type /*: string */) /*: ?number */ {
-    return this.state.activatedByType && this.state.activatedByType[type]
-      ? this.state.activatedByType[type].count
-      : null;
-  }
-
-  getRulesTotalForType(type /*: string */) /*: ?number */ {
-    return this.state.allByType && this.state.allByType[type]
-      ? this.state.allByType[type].count
-      : null;
-  }
-
-  render() {
-    const { organization, profile } = this.props;
-    const { compareToSonarWay } = this.state;
-    const activateMoreUrl = getRulesUrl(
-      { qprofile: profile.key, activation: 'false' },
-      organization
-    );
-
-    return (
-      <div className="quality-profile-rules">
-        <div className="quality-profile-rules-distribution">
-          <table className="data condensed">
-            <thead>
-              <tr>
-                <th>
-                  <h2>
-                    {translate('rules')}
-                  </h2>
-                </th>
-                <th>
-                  {translate('active')}
-                </th>
-                <th>
-                  {translate('inactive')}
-                </th>
-              </tr>
-            </thead>
-            <tbody>
-              <ProfileRulesRowTotal
-                key="all"
-                count={this.state.activatedTotal}
-                organization={organization}
-                qprofile={profile.key}
-                total={this.state.total}
-              />
-              {TYPES.map(type =>
-                <ProfileRulesRowOfType
-                  key={type}
-                  count={this.getRulesCountForType(type)}
-                  organization={organization}
-                  qprofile={profile.key}
-                  total={this.getRulesTotalForType(type)}
-                  type={type}
-                />
-              )}
-            </tbody>
-          </table>
-
-          {this.props.canAdmin &&
-            !profile.isBuiltIn &&
-            <div className="text-right big-spacer-top">
-              <Link to={activateMoreUrl} className="button js-activate-rules">
-                {translate('quality_profiles.activate_more')}
-              </Link>
-            </div>}
-        </div>
-        {profile.activeDeprecatedRuleCount > 0 &&
-          <ProfileRulesDeprecatedWarning
-            activeDeprecatedRules={profile.activeDeprecatedRuleCount}
-            organization={organization}
-            profile={profile.key}
-          />}
-        {compareToSonarWay != null &&
-          compareToSonarWay.missingRuleCount > 0 &&
-          <ProfileRulesSonarWayComparison
-            language={profile.language}
-            organization={organization}
-            profile={profile.key}
-            sonarway={compareToSonarWay.profile}
-            sonarWayMissingRules={compareToSonarWay.missingRuleCount}
-          />}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx
new file mode 100644 (file)
index 0000000..2fc1879
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * 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 { keyBy } from 'lodash';
+import ProfileRulesRowOfType from './ProfileRulesRowOfType';
+import ProfileRulesRowTotal from './ProfileRulesRowTotal';
+import ProfileRulesDeprecatedWarning from './ProfileRulesDeprecatedWarning';
+import ProfileRulesSonarWayComparison from './ProfileRulesSonarWayComparison';
+import { searchRules, takeFacet } from '../../../api/rules';
+import { getQualityProfiles } from '../../../api/quality-profiles';
+import { getRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+
+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'),
+    compareToSonarWay: null,
+    loading: true,
+    total: null
+  };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadRules();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.profile !== this.props.profile) {
+      this.loadRules();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadProfile() {
+    if (this.props.profile.isBuiltIn) {
+      return Promise.resolve(null);
+    }
+    return getQualityProfiles({
+      compareToSonarWay: true,
+      profile: this.props.profile.key
+    });
+  }
+
+  loadAllRules() {
+    return searchRules({
+      languages: this.props.profile.language,
+      ps: 1,
+      facets: 'types'
+    });
+  }
+
+  loadActivatedRules() {
+    return searchRules({
+      qprofile: this.props.profile.key,
+      activation: 'true',
+      ps: 1,
+      facets: 'types'
+    });
+  }
+
+  loadRules() {
+    this.setState({ loading: true });
+    Promise.all([
+      this.loadAllRules(),
+      this.loadActivatedRules(),
+      this.loadProfile()
+    ]).then(responses => {
+      if (this.mounted) {
+        const [allRules, activatedRules, showProfile] = responses;
+        this.setState({
+          activatedTotal: activatedRules.total,
+          allByType: keyBy<ByType>(takeFacet(allRules, 'types'), 'val'),
+          activatedByType: keyBy<ByType>(takeFacet(activatedRules, 'types'), 'val'),
+          compareToSonarWay: showProfile && showProfile.compareToSonarWay,
+          loading: false,
+          total: allRules.total
+        });
+      }
+    });
+  }
+
+  getRulesCountForType(type: string) {
+    return this.state.activatedByType && this.state.activatedByType[type]
+      ? this.state.activatedByType[type].count
+      : null;
+  }
+
+  getRulesTotalForType(type: string) {
+    return this.state.allByType && this.state.allByType[type]
+      ? this.state.allByType[type].count
+      : null;
+  }
+
+  render() {
+    const { organization, profile } = this.props;
+    const { compareToSonarWay } = this.state;
+    const activateMoreUrl = getRulesUrl(
+      { qprofile: profile.key, activation: 'false' },
+      organization
+    );
+
+    return (
+      <div className="quality-profile-rules">
+        <div className="quality-profile-rules-distribution">
+          <table className="data condensed">
+            <thead>
+              <tr>
+                <th>
+                  <h2>
+                    {translate('rules')}
+                  </h2>
+                </th>
+                <th>
+                  {translate('active')}
+                </th>
+                <th>
+                  {translate('inactive')}
+                </th>
+              </tr>
+            </thead>
+            <tbody>
+              <ProfileRulesRowTotal
+                key="all"
+                count={this.state.activatedTotal}
+                organization={organization}
+                qprofile={profile.key}
+                total={this.state.total}
+              />
+              {TYPES.map(type =>
+                <ProfileRulesRowOfType
+                  key={type}
+                  count={this.getRulesCountForType(type)}
+                  organization={organization}
+                  qprofile={profile.key}
+                  total={this.getRulesTotalForType(type)}
+                  type={type}
+                />
+              )}
+            </tbody>
+          </table>
+
+          {this.props.canAdmin &&
+            !profile.isBuiltIn &&
+            <div className="text-right big-spacer-top">
+              <Link to={activateMoreUrl} className="button js-activate-rules">
+                {translate('quality_profiles.activate_more')}
+              </Link>
+            </div>}
+        </div>
+        {profile.activeDeprecatedRuleCount > 0 &&
+          <ProfileRulesDeprecatedWarning
+            activeDeprecatedRules={profile.activeDeprecatedRuleCount}
+            organization={organization}
+            profile={profile.key}
+          />}
+        {compareToSonarWay != null &&
+          compareToSonarWay.missingRuleCount > 0 &&
+          <ProfileRulesSonarWayComparison
+            language={profile.language}
+            organization={organization}
+            profile={profile.key}
+            sonarway={compareToSonarWay.profile}
+            sonarWayMissingRules={compareToSonarWay.missingRuleCount}
+          />}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.js
deleted file mode 100644 (file)
index a74e082..0000000
+++ /dev/null
@@ -1,46 +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 Tooltip from '../../../components/controls/Tooltip';
-import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = { activeDeprecatedRules: number, organization: ?string, profile: string };
-*/
-
-export default function ProfileRulesDeprecatedWarning(props /*: Props */) {
-  const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization);
-  return (
-    <div className="quality-profile-rules-deprecated clearfix">
-      <span className="pull-left">
-        {translate('quality_profiles.deprecated_rules')}
-        <Tooltip overlay={translate('quality_profiles.deprecated_rules_description')}>
-          <i className="icon-help spacer-left" />
-        </Tooltip>
-      </span>
-      <Link className="pull-right" to={url}>
-        {props.activeDeprecatedRules}
-      </Link>
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesDeprecatedWarning.tsx
new file mode 100644 (file)
index 0000000..3e69b0c
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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 Tooltip from '../../../components/controls/Tooltip';
+import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  activeDeprecatedRules: number;
+  organization: string | null;
+  profile: string;
+}
+
+export default function ProfileRulesDeprecatedWarning(props: Props) {
+  const url = getDeprecatedActiveRulesUrl({ qprofile: props.profile }, props.organization);
+  return (
+    <div className="quality-profile-rules-deprecated clearfix">
+      <span className="pull-left">
+        {translate('quality_profiles.deprecated_rules')}
+        <Tooltip overlay={translate('quality_profiles.deprecated_rules_description')}>
+          <i className="icon-help spacer-left" />
+        </Tooltip>
+      </span>
+      <Link className="pull-right" to={url}>
+        {props.activeDeprecatedRules}
+      </Link>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.js
deleted file mode 100644 (file)
index a899b49..0000000
+++ /dev/null
@@ -1,76 +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 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
-};
-*/
-
-export default function ProfileRulesRowOfType(props /*: Props */) {
-  const activeRulesUrl = getRulesUrl(
-    { qprofile: props.qprofile, activation: 'true', types: props.type },
-    props.organization
-  );
-  const inactiveRulesUrl = getRulesUrl(
-    { qprofile: props.qprofile, activation: 'false', types: props.type },
-    props.organization
-  );
-  let inactiveCount = null;
-  if (props.count != null && props.total != null) {
-    inactiveCount = props.total - props.count;
-  }
-
-  return (
-    <tr>
-      <td>
-        <span>
-          <IssueTypeIcon query={props.type} className="little-spacer-right" />
-          {translate('issue.type', props.type, 'plural')}
-        </span>
-      </td>
-      <td className="thin nowrap text-right">
-        {props.count != null &&
-          <Link to={activeRulesUrl}>
-            {formatMeasure(props.count, 'SHORT_INT')}
-          </Link>}
-      </td>
-      <td className="thin nowrap text-right">
-        {inactiveCount != null &&
-          (inactiveCount > 0
-            ? <Link to={inactiveRulesUrl} className="small text-muted">
-                {formatMeasure(inactiveCount, 'SHORT_INT')}
-              </Link>
-            : <span className="note text-muted">0</span>)}
-      </td>
-    </tr>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowOfType.tsx
new file mode 100644 (file)
index 0000000..b13a5ed
--- /dev/null
@@ -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 { 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';
+
+interface Props {
+  count: number | null;
+  organization: string | null;
+  qprofile: string;
+  total: number | null;
+  type: string;
+}
+
+export default function ProfileRulesRowOfType(props: Props) {
+  const activeRulesUrl = getRulesUrl(
+    { qprofile: props.qprofile, activation: 'true', types: props.type },
+    props.organization
+  );
+  const inactiveRulesUrl = getRulesUrl(
+    { qprofile: props.qprofile, activation: 'false', types: props.type },
+    props.organization
+  );
+  let inactiveCount = null;
+  if (props.count != null && props.total != null) {
+    inactiveCount = props.total - props.count;
+  }
+
+  return (
+    <tr>
+      <td>
+        <span>
+          <IssueTypeIcon query={props.type} className="little-spacer-right" />
+          {translate('issue.type', props.type, 'plural')}
+        </span>
+      </td>
+      <td className="thin nowrap text-right">
+        {props.count != null &&
+          <Link to={activeRulesUrl}>
+            {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', null)}
+              </Link>
+            : <span className="note text-muted">0</span>)}
+      </td>
+    </tr>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.js
deleted file mode 100644 (file)
index 1cb16d7..0000000
+++ /dev/null
@@ -1,77 +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 { formatMeasure } from '../../../helpers/measures';
-import { getRulesUrl } from '../../../helpers/urls';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  count: ?number,
-  organization: ?string,
-  qprofile: string,
-  total: ?number
-};
-*/
-
-export default function ProfileRulesRowTotal(props /*: Props */) {
-  const activeRulesUrl = getRulesUrl(
-    { qprofile: props.qprofile, activation: 'true' },
-    props.organization
-  );
-  const inactiveRulesUrl = getRulesUrl(
-    { qprofile: props.qprofile, activation: 'false' },
-    props.organization
-  );
-  let inactiveCount = null;
-  if (props.count != null && props.total != null) {
-    inactiveCount = props.total - props.count;
-  }
-
-  return (
-    <tr>
-      <td>
-        <strong>
-          {translate('total')}
-        </strong>
-      </td>
-      <td className="thin nowrap text-right">
-        {props.count != null &&
-          <Link to={activeRulesUrl}>
-            <strong>
-              {formatMeasure(props.count, 'SHORT_INT')}
-            </strong>
-          </Link>}
-      </td>
-      <td className="thin nowrap text-right">
-        {inactiveCount != null &&
-          (inactiveCount > 0
-            ? <Link to={inactiveRulesUrl} className="small text-muted">
-                <strong>
-                  {formatMeasure(inactiveCount, 'SHORT_INT')}
-                </strong>
-              </Link>
-            : <span className="note text-muted">0</span>)}
-      </td>
-    </tr>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRowTotal.tsx
new file mode 100644 (file)
index 0000000..4f97d20
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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 { formatMeasure } from '../../../helpers/measures';
+import { getRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  count: number | null;
+  organization: string | null;
+  qprofile: string;
+  total: number | null;
+}
+
+export default function ProfileRulesRowTotal(props: Props) {
+  const activeRulesUrl = getRulesUrl(
+    { qprofile: props.qprofile, activation: 'true' },
+    props.organization
+  );
+  const inactiveRulesUrl = getRulesUrl(
+    { qprofile: props.qprofile, activation: 'false' },
+    props.organization
+  );
+  let inactiveCount = null;
+  if (props.count != null && props.total != null) {
+    inactiveCount = props.total - props.count;
+  }
+
+  return (
+    <tr>
+      <td>
+        <strong>
+          {translate('total')}
+        </strong>
+      </td>
+      <td className="thin nowrap text-right">
+        {props.count != null &&
+          <Link to={activeRulesUrl}>
+            <strong>
+              {formatMeasure(props.count, 'SHORT_INT', null)}
+            </strong>
+          </Link>}
+      </td>
+      <td className="thin nowrap text-right">
+        {inactiveCount != null &&
+          (inactiveCount > 0
+            ? <Link to={inactiveRulesUrl} className="small text-muted">
+                <strong>
+                  {formatMeasure(inactiveCount, 'SHORT_INT', null)}
+                </strong>
+              </Link>
+            : <span className="note text-muted">0</span>)}
+      </td>
+    </tr>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.js
deleted file mode 100644 (file)
index 1e4a4c2..0000000
+++ /dev/null
@@ -1,61 +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 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
-};
-*/
-
-export default function ProfileRulesSonarWayComparison(props /*: Props */) {
-  const url = getRulesUrl(
-    {
-      qprofile: props.profile,
-      activation: false,
-      compareToProfile: props.sonarway,
-      languages: props.language
-    },
-    props.organization
-  );
-
-  return (
-    <div className="quality-profile-rules-sonarway-missing clearfix">
-      <span className="pull-left">
-        {translate('quality_profiles.sonarway_missing_rules')}
-        <Tooltip overlay={translate('quality_profiles.sonarway_missing_rules_description')}>
-          <i className="icon-help spacer-left" />
-        </Tooltip>
-      </span>
-      <Link className="pull-right" to={url}>
-        {props.sonarWayMissingRules}
-      </Link>
-    </div>
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesSonarWayComparison.tsx
new file mode 100644 (file)
index 0000000..cc7470d
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * 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 Tooltip from '../../../components/controls/Tooltip';
+import { getRulesUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  language: string;
+  organization: string | null;
+  profile: string;
+  sonarway: string;
+  sonarWayMissingRules: number;
+}
+
+export default function ProfileRulesSonarWayComparison(props: Props) {
+  const url = getRulesUrl(
+    {
+      qprofile: props.profile,
+      activation: false,
+      compareToProfile: props.sonarway,
+      languages: props.language
+    },
+    props.organization
+  );
+
+  return (
+    <div className="quality-profile-rules-sonarway-missing clearfix">
+      <span className="pull-left">
+        {translate('quality_profiles.sonarway_missing_rules')}
+        <Tooltip overlay={translate('quality_profiles.sonarway_missing_rules_description')}>
+          <i className="icon-help spacer-left" />
+        </Tooltip>
+      </span>
+      <Link className="pull-right" to={url}>
+        {props.sonarWayMissingRules}
+      </Link>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.js
deleted file mode 100644 (file)
index cd62b6a..0000000
+++ /dev/null
@@ -1,159 +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 { shallow } from 'enzyme';
-import ProfileRules from '../ProfileRules';
-import { doAsync } from '../../../../helpers/testUtils';
-import * as apiRules from '../../../../api/rules';
-import * as apiQP from '../../../../api/quality-profiles';
-
-const PROFILE = {
-  key: 'foo',
-  name: 'Foo',
-  isBuiltIn: false,
-  isDefault: false,
-  isInherited: false,
-  language: 'java',
-  languageName: 'Java',
-  activeRuleCount: 68,
-  activeDeprecatedRuleCount: 0,
-  rulesUpdatedAt: '2017-06-28T12:58:44+0000',
-  depth: 0,
-  childrenCount: 0
-};
-
-const apiResponseAll = {
-  total: 243,
-  facets: [
-    {
-      property: 'types',
-      values: [
-        { val: 'CODE_SMELL', count: 168 },
-        { val: 'BUG', count: 68 },
-        { val: 'VULNERABILITY', count: 7 }
-      ]
-    }
-  ]
-};
-
-const apiResponseActive = {
-  total: 68,
-  facets: [
-    {
-      property: 'types',
-      values: [
-        { val: 'BUG', count: 68 },
-        { val: 'CODE_SMELL', count: 0 },
-        { val: 'VULNERABILITY', count: 0 }
-      ]
-    }
-  ]
-};
-
-// Mock api some api functions
-// eslint-disable-next-line
-apiRules.searchRules = data =>
-  Promise.resolve(data.activation === 'true' ? apiResponseActive : apiResponseAll);
-// eslint-disable-next-line
-apiQP.getQualityProfiles = () =>
-  Promise.resolve({
-    compareToSonarWay: {
-      profile: 'sonarway',
-      profileName: 'Sonar way',
-      missingRuleCount: 4
-    }
-  });
-
-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();
-  return doAsync(() => {
-    wrapper.update();
-    expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1);
-    expect(wrapper).toMatchSnapshot();
-  });
-});
-
-it('should show a button to activate more rules for admins', () => {
-  const wrapper = shallow(<ProfileRules canAdmin={true} organization="foo" profile={PROFILE} />);
-  expect(wrapper.find('.js-activate-rules')).toMatchSnapshot();
-});
-
-it('should show a deprecated rules warning message', () => {
-  const wrapper = shallow(
-    <ProfileRules
-      canAdmin={true}
-      organization="foo"
-      profile={{ ...PROFILE, activeDeprecatedRuleCount: 8 }}
-    />
-  );
-  expect(wrapper.find('ProfileRulesDeprecatedWarning')).toMatchSnapshot();
-});
-
-it('should not show a button to activate more rules on built in profiles', () => {
-  const wrapper = shallow(
-    <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
-  );
-  expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
-});
-
-it('should not show a button to activate more rules on built in profiles', () => {
-  const wrapper = shallow(
-    <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
-  );
-  expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
-});
-
-it('should not show sonarway comparison for built in profiles', () => {
-  // eslint-disable-next-line
-  apiQP.getQualityProfiles = jest.fn(() => Promise.resolve());
-  const wrapper = shallow(
-    <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
-  );
-  wrapper.instance().mounted = true;
-  wrapper.instance().loadRules();
-  return doAsync(() => {
-    wrapper.update();
-    expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(0);
-    expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
-  });
-});
-
-it('should not show sonarway comparison if there is no missing rules', () => {
-  // eslint-disable-next-line
-  apiQP.getQualityProfiles = jest.fn(() =>
-    Promise.resolve({
-      compareToSonarWay: {
-        profile: 'sonarway',
-        profileName: 'Sonar way',
-        missingRuleCount: 0
-      }
-    })
-  );
-  const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />);
-  wrapper.instance().mounted = true;
-  wrapper.instance().loadRules();
-  return doAsync(() => {
-    wrapper.update();
-    expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(1);
-    expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
-  });
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRules-test.tsx
new file mode 100644 (file)
index 0000000..91e5ed9
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import ProfileRules from '../ProfileRules';
+import { doAsync } from '../../../../helpers/testUtils';
+import * as apiRules from '../../../../api/rules';
+import * as apiQP from '../../../../api/quality-profiles';
+
+const PROFILE = {
+  key: 'foo',
+  name: 'Foo',
+  isBuiltIn: false,
+  isDefault: false,
+  isInherited: false,
+  language: 'java',
+  languageName: 'Java',
+  activeRuleCount: 68,
+  activeDeprecatedRuleCount: 0,
+  rulesUpdatedAt: '2017-06-28T12:58:44+0000',
+  depth: 0,
+  childrenCount: 0
+};
+
+const apiResponseAll = {
+  total: 243,
+  facets: [
+    {
+      property: 'types',
+      values: [
+        { val: 'CODE_SMELL', count: 168 },
+        { val: 'BUG', count: 68 },
+        { val: 'VULNERABILITY', count: 7 }
+      ]
+    }
+  ]
+};
+
+const apiResponseActive = {
+  total: 68,
+  facets: [
+    {
+      property: 'types',
+      values: [
+        { val: 'BUG', count: 68 },
+        { val: 'CODE_SMELL', count: 0 },
+        { val: 'VULNERABILITY', count: 0 }
+      ]
+    }
+  ]
+};
+
+// Mock api some api functions
+(apiRules as any).searchRules = (data: any) =>
+  Promise.resolve(data.activation === 'true' ? apiResponseActive : apiResponseAll);
+(apiQP as any).getQualityProfiles = () =>
+  Promise.resolve({
+    compareToSonarWay: {
+      profile: 'sonarway',
+      profileName: 'Sonar way',
+      missingRuleCount: 4
+    }
+  });
+
+it('should render the quality profiles rules with sonarway comparison', () => {
+  const wrapper = shallow(<ProfileRules canAdmin={false} organization="foo" profile={PROFILE} />);
+  const instance = wrapper.instance() as any;
+  instance.mounted = true;
+  instance.loadRules();
+  return doAsync(() => {
+    wrapper.update();
+    expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(1);
+    expect(wrapper).toMatchSnapshot();
+  });
+});
+
+it('should show a button to activate more rules for admins', () => {
+  const wrapper = shallow(<ProfileRules canAdmin={true} organization="foo" profile={PROFILE} />);
+  expect(wrapper.find('.js-activate-rules')).toMatchSnapshot();
+});
+
+it('should show a deprecated rules warning message', () => {
+  const wrapper = shallow(
+    <ProfileRules
+      canAdmin={true}
+      organization="foo"
+      profile={{ ...PROFILE, activeDeprecatedRuleCount: 8 }}
+    />
+  );
+  expect(wrapper.find('ProfileRulesDeprecatedWarning')).toMatchSnapshot();
+});
+
+it('should not show a button to activate more rules on built in profiles', () => {
+  const wrapper = shallow(
+    <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
+  );
+  expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
+});
+
+it('should not show a button to activate more rules on built in profiles', () => {
+  const wrapper = shallow(
+    <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
+  );
+  expect(wrapper.find('.js-activate-rules')).toHaveLength(0);
+});
+
+it('should not show sonarway comparison for built in profiles', () => {
+  (apiQP as any).getQualityProfiles = jest.fn(() => Promise.resolve());
+  const wrapper = shallow(
+    <ProfileRules canAdmin={true} organization={null} profile={{ ...PROFILE, isBuiltIn: true }} />
+  );
+  const instance = wrapper.instance() as any;
+  instance.mounted = true;
+  instance.loadRules();
+  return doAsync(() => {
+    wrapper.update();
+    expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(0);
+    expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
+  });
+});
+
+it('should not show sonarway comparison if there is no missing rules', () => {
+  (apiQP as any).getQualityProfiles = jest.fn(() =>
+    Promise.resolve({
+      compareToSonarWay: {
+        profile: 'sonarway',
+        profileName: 'Sonar way',
+        missingRuleCount: 0
+      }
+    })
+  );
+  const wrapper = shallow(<ProfileRules canAdmin={true} organization={null} profile={PROFILE} />);
+  const instance = wrapper.instance() as any;
+  instance.mounted = true;
+  instance.loadRules();
+  return doAsync(() => {
+    wrapper.update();
+    expect(apiQP.getQualityProfiles).toHaveBeenCalledTimes(1);
+    expect(wrapper.find('ProfileRulesSonarWayComparison')).toHaveLength(0);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.js
deleted file mode 100644 (file)
index 9a33872..0000000
+++ /dev/null
@@ -1,31 +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 { shallow } from 'enzyme';
-import ProfileRulesDeprecatedWarning from '../ProfileRulesDeprecatedWarning';
-
-it('should render correctly', () => {
-  expect(
-    shallow(
-      <ProfileRulesDeprecatedWarning activeDeprecatedRules={18} organization="foo" profile="bar" />
-    )
-  ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesDeprecatedWarning-test.tsx
new file mode 100644 (file)
index 0000000..c306d68
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import ProfileRulesDeprecatedWarning from '../ProfileRulesDeprecatedWarning';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <ProfileRulesDeprecatedWarning activeDeprecatedRules={18} organization="foo" profile="bar" />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.js
deleted file mode 100644 (file)
index dea445e..0000000
+++ /dev/null
@@ -1,70 +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 { shallow } from 'enzyme';
-import ProfileRulesRowOfType from '../ProfileRulesRowOfType';
-
-it('should render correctly', () => {
-  expect(
-    shallow(
-      <ProfileRulesRowOfType count={3} organization="foo" qprofile="bar" total={10} type="BUG" />
-    )
-  ).toMatchSnapshot();
-});
-
-it('should render correctly if there is 0 rules', () => {
-  expect(
-    shallow(
-      <ProfileRulesRowOfType
-        count={0}
-        organization={null}
-        qprofile="bar"
-        total={0}
-        type="VULNERABILITY"
-      />
-    )
-  ).toMatchSnapshot();
-});
-
-it('should render correctly if there is missing data', () => {
-  expect(
-    shallow(
-      <ProfileRulesRowOfType
-        count={5}
-        organization={null}
-        qprofile="bar"
-        total={null}
-        type="VULNERABILITY"
-      />
-    )
-  ).toMatchSnapshot();
-  expect(
-    shallow(
-      <ProfileRulesRowOfType
-        count={null}
-        organization={null}
-        qprofile="foo"
-        total={10}
-        type="VULNERABILITY"
-      />
-    )
-  ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowOfType-test.tsx
new file mode 100644 (file)
index 0000000..b8a3bc5
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import ProfileRulesRowOfType from '../ProfileRulesRowOfType';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <ProfileRulesRowOfType count={3} organization="foo" qprofile="bar" total={10} type="BUG" />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render correctly if there is 0 rules', () => {
+  expect(
+    shallow(
+      <ProfileRulesRowOfType
+        count={0}
+        organization={null}
+        qprofile="bar"
+        total={0}
+        type="VULNERABILITY"
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render correctly if there is missing data', () => {
+  expect(
+    shallow(
+      <ProfileRulesRowOfType
+        count={5}
+        organization={null}
+        qprofile="bar"
+        total={null}
+        type="VULNERABILITY"
+      />
+    )
+  ).toMatchSnapshot();
+  expect(
+    shallow(
+      <ProfileRulesRowOfType
+        count={null}
+        organization={null}
+        qprofile="foo"
+        total={10}
+        type="VULNERABILITY"
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.js
deleted file mode 100644 (file)
index 20f2d69..0000000
+++ /dev/null
@@ -1,44 +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 { shallow } from 'enzyme';
-import ProfileRulesRowTotal from '../ProfileRulesRowTotal';
-
-it('should render correctly', () => {
-  expect(
-    shallow(<ProfileRulesRowTotal count={3} organization="foo" qprofile="bar" total={10} />)
-  ).toMatchSnapshot();
-});
-
-it('should render correctly if there is 0 rules', () => {
-  expect(
-    shallow(<ProfileRulesRowTotal count={0} organization={null} qprofile="bar" total={0} />)
-  ).toMatchSnapshot();
-});
-
-it('should render correctly if there is missing data', () => {
-  expect(
-    shallow(<ProfileRulesRowTotal count={5} organization={null} qprofile="bar" total={null} />)
-  ).toMatchSnapshot();
-  expect(
-    shallow(<ProfileRulesRowTotal count={null} organization={null} qprofile="foo" total={10} />)
-  ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesRowTotal-test.tsx
new file mode 100644 (file)
index 0000000..3428f58
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import ProfileRulesRowTotal from '../ProfileRulesRowTotal';
+
+it('should render correctly', () => {
+  expect(
+    shallow(<ProfileRulesRowTotal count={3} organization="foo" qprofile="bar" total={10} />)
+  ).toMatchSnapshot();
+});
+
+it('should render correctly if there is 0 rules', () => {
+  expect(
+    shallow(<ProfileRulesRowTotal count={0} organization={null} qprofile="bar" total={0} />)
+  ).toMatchSnapshot();
+});
+
+it('should render correctly if there is missing data', () => {
+  expect(
+    shallow(<ProfileRulesRowTotal count={5} organization={null} qprofile="bar" total={null} />)
+  ).toMatchSnapshot();
+  expect(
+    shallow(<ProfileRulesRowTotal count={null} organization={null} qprofile="foo" total={10} />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.js
deleted file mode 100644 (file)
index ef992d6..0000000
+++ /dev/null
@@ -1,37 +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 { shallow } from 'enzyme';
-import ProfileRulesSonarWayComparison from '../ProfileRulesSonarWayComparison';
-
-it('should render correctly', () => {
-  expect(
-    shallow(
-      <ProfileRulesSonarWayComparison
-        language="Java"
-        organization="foo"
-        profile="bar"
-        sonarway="baz"
-        sonarWayMissingRules={158}
-      />
-    )
-  ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/ProfileRulesSonarWayComparison-test.tsx
new file mode 100644 (file)
index 0000000..50914b7
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import ProfileRulesSonarWayComparison from '../ProfileRulesSonarWayComparison';
+
+it('should render correctly', () => {
+  expect(
+    shallow(
+      <ProfileRulesSonarWayComparison
+        language="Java"
+        organization="foo"
+        profile="bar"
+        sonarway="baz"
+        sonarWayMissingRules={158}
+      />
+    )
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.js.snap
deleted file mode 100644 (file)
index 22be426..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render the quality profiles rules with sonarway comparison 1`] = `
-<div
-  className="quality-profile-rules"
->
-  <div
-    className="quality-profile-rules-distribution"
-  >
-    <table
-      className="data condensed"
-    >
-      <thead>
-        <tr>
-          <th>
-            <h2>
-              rules
-            </h2>
-          </th>
-          <th>
-            active
-          </th>
-          <th>
-            inactive
-          </th>
-        </tr>
-      </thead>
-      <tbody>
-        <ProfileRulesRowTotal
-          count={68}
-          organization="foo"
-          qprofile="foo"
-          total={243}
-        />
-        <ProfileRulesRowOfType
-          count={68}
-          organization="foo"
-          qprofile="foo"
-          total={68}
-          type="BUG"
-        />
-        <ProfileRulesRowOfType
-          count={0}
-          organization="foo"
-          qprofile="foo"
-          total={7}
-          type="VULNERABILITY"
-        />
-        <ProfileRulesRowOfType
-          count={0}
-          organization="foo"
-          qprofile="foo"
-          total={168}
-          type="CODE_SMELL"
-        />
-      </tbody>
-    </table>
-  </div>
-  <ProfileRulesSonarWayComparison
-    language="java"
-    organization="foo"
-    profile="foo"
-    sonarWayMissingRules={4}
-    sonarway="sonarway"
-  />
-</div>
-`;
-
-exports[`should show a button to activate more rules for admins 1`] = `
-<Link
-  className="button js-activate-rules"
-  onlyActiveOnIndex={false}
-  style={Object {}}
-  to="/organizations/foo/rules#qprofile=foo|activation=false"
->
-  quality_profiles.activate_more
-</Link>
-`;
-
-exports[`should show a deprecated rules warning message 1`] = `
-<ProfileRulesDeprecatedWarning
-  activeDeprecatedRules={8}
-  organization="foo"
-  profile="foo"
-/>
-`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRules-test.tsx.snap
new file mode 100644 (file)
index 0000000..22be426
--- /dev/null
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render the quality profiles rules with sonarway comparison 1`] = `
+<div
+  className="quality-profile-rules"
+>
+  <div
+    className="quality-profile-rules-distribution"
+  >
+    <table
+      className="data condensed"
+    >
+      <thead>
+        <tr>
+          <th>
+            <h2>
+              rules
+            </h2>
+          </th>
+          <th>
+            active
+          </th>
+          <th>
+            inactive
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+        <ProfileRulesRowTotal
+          count={68}
+          organization="foo"
+          qprofile="foo"
+          total={243}
+        />
+        <ProfileRulesRowOfType
+          count={68}
+          organization="foo"
+          qprofile="foo"
+          total={68}
+          type="BUG"
+        />
+        <ProfileRulesRowOfType
+          count={0}
+          organization="foo"
+          qprofile="foo"
+          total={7}
+          type="VULNERABILITY"
+        />
+        <ProfileRulesRowOfType
+          count={0}
+          organization="foo"
+          qprofile="foo"
+          total={168}
+          type="CODE_SMELL"
+        />
+      </tbody>
+    </table>
+  </div>
+  <ProfileRulesSonarWayComparison
+    language="java"
+    organization="foo"
+    profile="foo"
+    sonarWayMissingRules={4}
+    sonarway="sonarway"
+  />
+</div>
+`;
+
+exports[`should show a button to activate more rules for admins 1`] = `
+<Link
+  className="button js-activate-rules"
+  onlyActiveOnIndex={false}
+  style={Object {}}
+  to="/organizations/foo/rules#qprofile=foo|activation=false"
+>
+  quality_profiles.activate_more
+</Link>
+`;
+
+exports[`should show a deprecated rules warning message 1`] = `
+<ProfileRulesDeprecatedWarning
+  activeDeprecatedRules={8}
+  organization="foo"
+  profile="foo"
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.js.snap
deleted file mode 100644 (file)
index 54fa91c..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
-  className="quality-profile-rules-deprecated clearfix"
->
-  <span
-    className="pull-left"
-  >
-    quality_profiles.deprecated_rules
-    <Tooltip
-      overlay="quality_profiles.deprecated_rules_description"
-      placement="bottom"
-    >
-      <i
-        className="icon-help spacer-left"
-      />
-    </Tooltip>
-  </span>
-  <Link
-    className="pull-right"
-    onlyActiveOnIndex={false}
-    style={Object {}}
-    to="/organizations/foo/rules#qprofile=bar|activation=true|statuses=DEPRECATED"
-  >
-    18
-  </Link>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesDeprecatedWarning-test.tsx.snap
new file mode 100644 (file)
index 0000000..54fa91c
--- /dev/null
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="quality-profile-rules-deprecated clearfix"
+>
+  <span
+    className="pull-left"
+  >
+    quality_profiles.deprecated_rules
+    <Tooltip
+      overlay="quality_profiles.deprecated_rules_description"
+      placement="bottom"
+    >
+      <i
+        className="icon-help spacer-left"
+      />
+    </Tooltip>
+  </span>
+  <Link
+    className="pull-right"
+    onlyActiveOnIndex={false}
+    style={Object {}}
+    to="/organizations/foo/rules#qprofile=bar|activation=true|statuses=DEPRECATED"
+  >
+    18
+  </Link>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.js.snap
deleted file mode 100644 (file)
index 6286609..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr>
-  <td>
-    <span>
-      <IssueTypeIcon
-        className="little-spacer-right"
-        query="BUG"
-      />
-      issue.type.BUG.plural
-    </span>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/rules#qprofile=bar|activation=true|types=BUG"
-    >
-      3
-    </Link>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      className="small text-muted"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/rules#qprofile=bar|activation=false|types=BUG"
-    >
-      7
-    </Link>
-  </td>
-</tr>
-`;
-
-exports[`should render correctly if there is 0 rules 1`] = `
-<tr>
-  <td>
-    <span>
-      <IssueTypeIcon
-        className="little-spacer-right"
-        query="VULNERABILITY"
-      />
-      issue.type.VULNERABILITY.plural
-    </span>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
-    >
-      0
-    </Link>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <span
-      className="note text-muted"
-    >
-      0
-    </span>
-  </td>
-</tr>
-`;
-
-exports[`should render correctly if there is missing data 1`] = `
-<tr>
-  <td>
-    <span>
-      <IssueTypeIcon
-        className="little-spacer-right"
-        query="VULNERABILITY"
-      />
-      issue.type.VULNERABILITY.plural
-    </span>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
-    >
-      5
-    </Link>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  />
-</tr>
-`;
-
-exports[`should render correctly if there is missing data 2`] = `
-<tr>
-  <td>
-    <span>
-      <IssueTypeIcon
-        className="little-spacer-right"
-        query="VULNERABILITY"
-      />
-      issue.type.VULNERABILITY.plural
-    </span>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  />
-  <td
-    className="thin nowrap text-right"
-  />
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowOfType-test.tsx.snap
new file mode 100644 (file)
index 0000000..6286609
--- /dev/null
@@ -0,0 +1,120 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr>
+  <td>
+    <span>
+      <IssueTypeIcon
+        className="little-spacer-right"
+        query="BUG"
+      />
+      issue.type.BUG.plural
+    </span>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/rules#qprofile=bar|activation=true|types=BUG"
+    >
+      3
+    </Link>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      className="small text-muted"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/rules#qprofile=bar|activation=false|types=BUG"
+    >
+      7
+    </Link>
+  </td>
+</tr>
+`;
+
+exports[`should render correctly if there is 0 rules 1`] = `
+<tr>
+  <td>
+    <span>
+      <IssueTypeIcon
+        className="little-spacer-right"
+        query="VULNERABILITY"
+      />
+      issue.type.VULNERABILITY.plural
+    </span>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
+    >
+      0
+    </Link>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <span
+      className="note text-muted"
+    >
+      0
+    </span>
+  </td>
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 1`] = `
+<tr>
+  <td>
+    <span>
+      <IssueTypeIcon
+        className="little-spacer-right"
+        query="VULNERABILITY"
+      />
+      issue.type.VULNERABILITY.plural
+    </span>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/coding_rules#qprofile=bar|activation=true|types=VULNERABILITY"
+    >
+      5
+    </Link>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  />
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 2`] = `
+<tr>
+  <td>
+    <span>
+      <IssueTypeIcon
+        className="little-spacer-right"
+        query="VULNERABILITY"
+      />
+      issue.type.VULNERABILITY.plural
+    </span>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  />
+  <td
+    className="thin nowrap text-right"
+  />
+</tr>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.js.snap
deleted file mode 100644 (file)
index f868665..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr>
-  <td>
-    <strong>
-      total
-    </strong>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/rules#qprofile=bar|activation=true"
-    >
-      <strong>
-        3
-      </strong>
-    </Link>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      className="small text-muted"
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/organizations/foo/rules#qprofile=bar|activation=false"
-    >
-      <strong>
-        7
-      </strong>
-    </Link>
-  </td>
-</tr>
-`;
-
-exports[`should render correctly if there is 0 rules 1`] = `
-<tr>
-  <td>
-    <strong>
-      total
-    </strong>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/coding_rules#qprofile=bar|activation=true"
-    >
-      <strong>
-        0
-      </strong>
-    </Link>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <span
-      className="note text-muted"
-    >
-      0
-    </span>
-  </td>
-</tr>
-`;
-
-exports[`should render correctly if there is missing data 1`] = `
-<tr>
-  <td>
-    <strong>
-      total
-    </strong>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <Link
-      onlyActiveOnIndex={false}
-      style={Object {}}
-      to="/coding_rules#qprofile=bar|activation=true"
-    >
-      <strong>
-        5
-      </strong>
-    </Link>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  />
-</tr>
-`;
-
-exports[`should render correctly if there is missing data 2`] = `
-<tr>
-  <td>
-    <strong>
-      total
-    </strong>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  />
-  <td
-    className="thin nowrap text-right"
-  />
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesRowTotal-test.tsx.snap
new file mode 100644 (file)
index 0000000..f868665
--- /dev/null
@@ -0,0 +1,112 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<tr>
+  <td>
+    <strong>
+      total
+    </strong>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/rules#qprofile=bar|activation=true"
+    >
+      <strong>
+        3
+      </strong>
+    </Link>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      className="small text-muted"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/organizations/foo/rules#qprofile=bar|activation=false"
+    >
+      <strong>
+        7
+      </strong>
+    </Link>
+  </td>
+</tr>
+`;
+
+exports[`should render correctly if there is 0 rules 1`] = `
+<tr>
+  <td>
+    <strong>
+      total
+    </strong>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/coding_rules#qprofile=bar|activation=true"
+    >
+      <strong>
+        0
+      </strong>
+    </Link>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <span
+      className="note text-muted"
+    >
+      0
+    </span>
+  </td>
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 1`] = `
+<tr>
+  <td>
+    <strong>
+      total
+    </strong>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  >
+    <Link
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      to="/coding_rules#qprofile=bar|activation=true"
+    >
+      <strong>
+        5
+      </strong>
+    </Link>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  />
+</tr>
+`;
+
+exports[`should render correctly if there is missing data 2`] = `
+<tr>
+  <td>
+    <strong>
+      total
+    </strong>
+  </td>
+  <td
+    className="thin nowrap text-right"
+  />
+  <td
+    className="thin nowrap text-right"
+  />
+</tr>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.js.snap
deleted file mode 100644 (file)
index 481846b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
-  className="quality-profile-rules-sonarway-missing clearfix"
->
-  <span
-    className="pull-left"
-  >
-    quality_profiles.sonarway_missing_rules
-    <Tooltip
-      overlay="quality_profiles.sonarway_missing_rules_description"
-      placement="bottom"
-    >
-      <i
-        className="icon-help spacer-left"
-      />
-    </Tooltip>
-  </span>
-  <Link
-    className="pull-right"
-    onlyActiveOnIndex={false}
-    style={Object {}}
-    to="/organizations/foo/rules#qprofile=bar|activation=false|compareToProfile=baz|languages=Java"
-  >
-    158
-  </Link>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/details/__tests__/__snapshots__/ProfileRulesSonarWayComparison-test.tsx.snap
new file mode 100644 (file)
index 0000000..481846b
--- /dev/null
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+  className="quality-profile-rules-sonarway-missing clearfix"
+>
+  <span
+    className="pull-left"
+  >
+    quality_profiles.sonarway_missing_rules
+    <Tooltip
+      overlay="quality_profiles.sonarway_missing_rules_description"
+      placement="bottom"
+    >
+      <i
+        className="icon-help spacer-left"
+      />
+    </Tooltip>
+  </span>
+  <Link
+    className="pull-right"
+    onlyActiveOnIndex={false}
+    style={Object {}}
+    to="/organizations/foo/rules#qprofile=bar|activation=false|compareToProfile=baz|languages=Java"
+  >
+    158
+  </Link>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.js
deleted file mode 100644 (file)
index d95984c..0000000
+++ /dev/null
@@ -1,204 +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 Modal from 'react-modal';
-import 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 {
-  /*:: form: HTMLFormElement; */
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = { importers: [], loading: false, name: '', preloading: true };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchImporters();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchImporters() {
-    getImporters().then(importers => {
-      if (this.mounted) {
-        this.setState({ importers, preloading: false });
-      }
-    });
-  }
-
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleNameChange = (event /*: { currentTarget: HTMLInputElement } */) => {
-    this.setState({ name: event.currentTarget.value });
-  };
-
-  handleLanguageChange = (option /*: { value: string } */) => {
-    this.setState({ language: option.value });
-  };
-
-  handleFormSubmit = (event /*: Event */) => {
-    event.preventDefault();
-
-    this.setState({ loading: true });
-
-    const data = new FormData(this.form);
-    if (this.props.organization) {
-      data.append('organization', this.props.organization);
-    }
-
-    createQualityProfile(data).then(
-      response => this.props.onCreate(response.profile),
-      error => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-        this.props.onRequestFail(error);
-      }
-    );
-  };
-
-  render() {
-    const header = translate('quality_profiles.new_profile');
-
-    const languages = sortBy(this.props.languages, 'name');
-    const selectedLanguage = this.state.language || languages[0].key;
-    const importers = this.state.importers.filter(importer =>
-      importer.languages.includes(selectedLanguage)
-    );
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <form
-          id="create-profile-form"
-          onSubmit={this.handleFormSubmit}
-          ref={node => (this.form = node)}>
-          <div className="modal-head">
-            <h2>
-              {header}
-            </h2>
-          </div>
-
-          {this.state.preloading
-            ? <div className="modal-body">
-                <i className="spinner" />
-              </div>
-            : <div className="modal-body">
-                <div className="modal-field">
-                  <label htmlFor="create-profile-name">
-                    {translate('name')}
-                    <em className="mandatory">*</em>
-                  </label>
-                  <input
-                    autoFocus={true}
-                    id="create-profile-name"
-                    maxLength="100"
-                    name="name"
-                    onChange={this.handleNameChange}
-                    required={true}
-                    size="50"
-                    type="text"
-                    value={this.state.name}
-                  />
-                </div>
-                <div className="modal-field spacer-bottom">
-                  <label htmlFor="create-profile-language">
-                    {translate('language')}
-                    <em className="mandatory">*</em>
-                  </label>
-                  <Select
-                    clearable={false}
-                    id="create-profile-language"
-                    name="language"
-                    onChange={this.handleLanguageChange}
-                    options={languages.map(language => ({
-                      label: language.name,
-                      value: language.key
-                    }))}
-                    value={selectedLanguage}
-                  />
-                </div>
-                {importers.map(importer =>
-                  <div
-                    className="modal-field spacer-bottom js-importer"
-                    data-key={importer.key}
-                    key={importer.key}>
-                    <label htmlFor={'create-profile-form-backup-' + importer.key}>
-                      {importer.name}
-                    </label>
-                    <input
-                      id={'create-profile-form-backup-' + importer.key}
-                      name={'backup_' + importer.key}
-                      type="file"
-                    />
-                    <p className="note">
-                      {translate('quality_profiles.optional_configuration_file')}
-                    </p>
-                  </div>
-                )}
-              </div>}
-
-          <div className="modal-foot">
-            {this.state.loading && <i className="spinner spacer-right" />}
-            {!this.state.preloading &&
-              <button disabled={this.state.loading} id="create-profile-submit">
-                {translate('create')}
-              </button>}
-            <a href="#" id="create-profile-cancel" onClick={this.handleCancelClick}>
-              {translate('cancel')}
-            </a>
-          </div>
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/CreateProfileForm.tsx
new file mode 100644 (file)
index 0000000..8f6d589
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * 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 Modal from 'react-modal';
+import * as Select from 'react-select';
+import { sortBy } from 'lodash';
+import { getImporters, createQualityProfile } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
+
+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;
+  state: State = { importers: [], loading: false, name: '', preloading: true };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchImporters();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchImporters() {
+    getImporters().then(
+      (importers: Array<{ key: string; languages: Array<string>; name: string }>) => {
+        if (this.mounted) {
+          this.setState({ importers, preloading: false });
+        }
+      }
+    );
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleNameChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
+    this.setState({ name: event.currentTarget.value });
+  };
+
+  handleLanguageChange = (option: { value: string }) => {
+    this.setState({ language: option.value });
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+
+    this.setState({ loading: true });
+
+    const data = new FormData(event.currentTarget);
+    if (this.props.organization) {
+      data.append('organization', this.props.organization);
+    }
+
+    createQualityProfile(data).then(
+      (response: any) => this.props.onCreate(response.profile),
+      (error: any) => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+        this.props.onRequestFail(error);
+      }
+    );
+  };
+
+  render() {
+    const header = translate('quality_profiles.new_profile');
+
+    const languages = sortBy(this.props.languages, 'name');
+    const selectedLanguage = this.state.language || languages[0].key;
+    const importers = this.state.importers.filter(importer =>
+      importer.languages.includes(selectedLanguage)
+    );
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="create-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>
+              {header}
+            </h2>
+          </div>
+
+          {this.state.preloading
+            ? <div className="modal-body">
+                <i className="spinner" />
+              </div>
+            : <div className="modal-body">
+                <div className="modal-field">
+                  <label htmlFor="create-profile-name">
+                    {translate('name')}
+                    <em className="mandatory">*</em>
+                  </label>
+                  <input
+                    autoFocus={true}
+                    id="create-profile-name"
+                    maxLength={100}
+                    name="name"
+                    onChange={this.handleNameChange}
+                    required={true}
+                    size={50}
+                    type="text"
+                    value={this.state.name}
+                  />
+                </div>
+                <div className="modal-field spacer-bottom">
+                  <label htmlFor="create-profile-language">
+                    {translate('language')}
+                    <em className="mandatory">*</em>
+                  </label>
+                  <Select
+                    clearable={false}
+                    name="language"
+                    onChange={this.handleLanguageChange}
+                    options={languages.map(language => ({
+                      label: language.name,
+                      value: language.key
+                    }))}
+                    value={selectedLanguage}
+                  />
+                </div>
+                {importers.map(importer =>
+                  <div
+                    className="modal-field spacer-bottom js-importer"
+                    data-key={importer.key}
+                    key={importer.key}>
+                    <label htmlFor={'create-profile-form-backup-' + importer.key}>
+                      {importer.name}
+                    </label>
+                    <input
+                      id={'create-profile-form-backup-' + importer.key}
+                      name={'backup_' + importer.key}
+                      type="file"
+                    />
+                    <p className="note">
+                      {translate('quality_profiles.optional_configuration_file')}
+                    </p>
+                  </div>
+                )}
+              </div>}
+
+          <div className="modal-foot">
+            {this.state.loading && <i className="spinner spacer-right" />}
+            {!this.state.preloading &&
+              <button disabled={this.state.loading} id="create-profile-submit">
+                {translate('create')}
+              </button>}
+            <a href="#" id="create-profile-cancel" onClick={this.handleCancelClick}>
+              {translate('cancel')}
+            </a>
+          </div>
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js
deleted file mode 100644 (file)
index 4be28f3..0000000
+++ /dev/null
@@ -1,48 +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 EvolutionDeprecated from './EvolutionDeprecated';
-import EvolutionStagnant from './EvolutionStagnant';
-import EvolutionRules from './EvolutionRules';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-type Props = {
-  organization: ?string,
-  profiles: Array<Profile>
-};
-*/
-
-export default class Evolution extends React.PureComponent {
-  /*:: props: Props; */
-
-  render() {
-    const { organization, profiles } = this.props;
-
-    return (
-      <div className="quality-profiles-evolution">
-        <EvolutionDeprecated organization={organization} profiles={profiles} />
-        <EvolutionStagnant organization={organization} profiles={profiles} />
-        <EvolutionRules organization={organization} />
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.tsx
new file mode 100644 (file)
index 0000000..f238580
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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 EvolutionDeprecated from './EvolutionDeprecated';
+import EvolutionStagnant from './EvolutionStagnant';
+import EvolutionRules from './EvolutionRules';
+import { IProfile } from '../types';
+
+interface Props {
+  organization: string | null;
+  profiles: IProfile[];
+}
+
+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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js
deleted file mode 100644 (file)
index a3eaef2..0000000
+++ /dev/null
@@ -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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.tsx
new file mode 100644 (file)
index 0000000..0f8da8a
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js
deleted file mode 100644 (file)
index 042c04a..0000000
+++ /dev/null
@@ -1,134 +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 { sortBy } from 'lodash';
-import { searchRules } from '../../../api/rules';
-import { translateWithParameters, translate } from '../../../helpers/l10n';
-import { getRulesUrl } from '../../../helpers/urls';
-import { formatMeasure } from '../../../helpers/measures';
-
-const RULES_LIMIT = 10;
-
-const PERIOD_START_MOMENT = moment().subtract(1, 'year');
-
-function parseRules(r) {
-  const { rules, actives } = r;
-  return rules.map(rule => {
-    const activations = actives[rule.key];
-    return { ...rule, activations: activations ? activations.length : 0 };
-  });
-}
-
-/*::
-type Props = {
-  organization: ?string
-};
-*/
-
-export default class EvolutionRules extends React.PureComponent {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state = {};
-
-  componentDidMount() {
-    this.mounted = true;
-    this.loadLatestRules();
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  loadLatestRules() {
-    const data = {
-      available_since: PERIOD_START_MOMENT.format('YYYY-MM-DD'),
-      s: 'createdAt',
-      asc: false,
-      ps: RULES_LIMIT,
-      f: 'name,langName,actives'
-    };
-
-    searchRules(data).then(r => {
-      if (this.mounted) {
-        this.setState({
-          latestRules: sortBy(parseRules(r), 'langName'),
-          latestRulesTotal: r.total
-        });
-      }
-    });
-  }
-
-  render() {
-    if (!this.state.latestRulesTotal) {
-      return null;
-    }
-
-    const newRulesUrl = getRulesUrl(
-      {
-        available_since: PERIOD_START_MOMENT.format('YYYY-MM-DD')
-      },
-      this.props.organization
-    );
-
-    return (
-      <div className="quality-profile-box quality-profiles-evolution-rules">
-        <div className="clearfix">
-          <strong className="pull-left">
-            {translate('quality_profiles.latest_new_rules')}
-          </strong>
-        </div>
-        <ul>
-          {this.state.latestRules.map(rule =>
-            <li key={rule.key} className="spacer-top">
-              <div className="text-ellipsis">
-                <Link
-                  to={getRulesUrl({ rule_key: rule.key }, this.props.organization)}
-                  className="link-no-underline">
-                  {' '}{rule.name}
-                </Link>
-                <div className="note">
-                  {rule.activations
-                    ? translateWithParameters(
-                        'quality_profiles.latest_new_rules.activated',
-                        rule.langName,
-                        rule.activations
-                      )
-                    : translateWithParameters(
-                        'quality_profiles.latest_new_rules.not_activated',
-                        rule.langName
-                      )}
-                </div>
-              </div>
-            </li>
-          )}
-        </ul>
-        {this.state.latestRulesTotal > RULES_LIMIT &&
-          <div className="spacer-top">
-            <Link to={newRulesUrl} className="small">
-              {translate('see_all')} {formatMeasure(this.state.latestRulesTotal, 'SHORT_INT')}
-            </Link>
-          </div>}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.tsx
new file mode 100644 (file)
index 0000000..c9e3e1f
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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 { sortBy } from 'lodash';
+import { searchRules } from '../../../api/rules';
+import { translateWithParameters, translate } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+import { formatMeasure } from '../../../helpers/measures';
+
+const RULES_LIMIT = 10;
+
+const PERIOD_START_MOMENT = moment().subtract(1, 'year');
+
+function parseRules(r: any) {
+  const { rules, actives } = r;
+  return rules.map((rule: any) => {
+    const activations = actives[rule.key];
+    return { ...rule, activations: activations ? activations.length : 0 };
+  });
+}
+
+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<Props, State> {
+  mounted: boolean;
+  state: State = {};
+
+  componentDidMount() {
+    this.mounted = true;
+    this.loadLatestRules();
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  loadLatestRules() {
+    const data = {
+      available_since: PERIOD_START_MOMENT.format('YYYY-MM-DD'),
+      s: 'createdAt',
+      asc: false,
+      ps: RULES_LIMIT,
+      f: 'name,langName,actives'
+    };
+
+    searchRules(data).then((r: any) => {
+      if (this.mounted) {
+        this.setState({
+          latestRules: sortBy<IRule>(parseRules(r), 'langName'),
+          latestRulesTotal: r.total
+        });
+      }
+    });
+  }
+
+  render() {
+    if (!this.state.latestRulesTotal || !this.state.latestRules) {
+      return null;
+    }
+
+    const newRulesUrl = getRulesUrl(
+      {
+        available_since: PERIOD_START_MOMENT.format('YYYY-MM-DD')
+      },
+      this.props.organization
+    );
+
+    return (
+      <div className="quality-profile-box quality-profiles-evolution-rules">
+        <div className="clearfix">
+          <strong className="pull-left">
+            {translate('quality_profiles.latest_new_rules')}
+          </strong>
+        </div>
+        <ul>
+          {this.state.latestRules.map(rule =>
+            <li key={rule.key} className="spacer-top">
+              <div className="text-ellipsis">
+                <Link
+                  to={getRulesUrl({ rule_key: rule.key }, this.props.organization)}
+                  className="link-no-underline">
+                  {' '}{rule.name}
+                </Link>
+                <div className="note">
+                  {rule.activations
+                    ? translateWithParameters(
+                        'quality_profiles.latest_new_rules.activated',
+                        rule.langName,
+                        rule.activations
+                      )
+                    : translateWithParameters(
+                        'quality_profiles.latest_new_rules.not_activated',
+                        rule.langName
+                      )}
+                </div>
+              </div>
+            </li>
+          )}
+        </ul>
+        {this.state.latestRulesTotal > RULES_LIMIT &&
+          <div className="spacer-top">
+            <Link to={newRulesUrl} className="small">
+              {translate('see_all')} {formatMeasure(this.state.latestRulesTotal, 'SHORT_INT', null)}
+            </Link>
+          </div>}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js
deleted file mode 100644 (file)
index 1eda4c0..0000000
+++ /dev/null
@@ -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>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.tsx
new file mode 100644 (file)
index 0000000..8b3fc2e
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js
deleted file mode 100644 (file)
index 95f63d8..0000000
+++ /dev/null
@@ -1,58 +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 PageHeader from './PageHeader';
-import Evolution from './Evolution';
-import ProfilesList from './ProfilesList';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-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; */
-
-  render() {
-    return (
-      <div>
-        <PageHeader {...this.props} />
-
-        <div className="page-with-sidebar">
-          <div className="page-main">
-            <ProfilesList {...this.props} />
-          </div>
-          <div className="page-sidebar">
-            <Evolution {...this.props} />
-          </div>
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.tsx
new file mode 100644 (file)
index 0000000..cd3b215
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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 PageHeader from './PageHeader';
+import Evolution from './Evolution';
+import ProfilesList from './ProfilesList';
+import { IProfile } from '../types';
+
+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>;
+}
+
+export default function HomeContainer(props: Props) {
+  return (
+    <div>
+      <PageHeader {...props} />
+
+      <div className="page-with-sidebar">
+        <div className="page-main">
+          <ProfilesList {...props} />
+        </div>
+        <div className="page-sidebar">
+          <Evolution {...props} />
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js
deleted file mode 100644 (file)
index 44ba30f..0000000
+++ /dev/null
@@ -1,134 +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 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';
-
-/*::
-type Props = {
-  canAdmin: boolean,
-  languages: Array<{ key: string, name: string }>,
-  onRequestFail: Object => void,
-  organization: ?string,
-  updateProfiles: () => Promise<*>
-};
-*/
-
-/*::
-type State = {
-  createFormOpen: boolean,
-  restoreFormOpen: boolean
-};
-*/
-
-export default class PageHeader extends React.PureComponent {
-  /*:: props: Props; */
-
-  static contextTypes = {
-    router: PropTypes.object
-  };
-
-  state /*: State */ = {
-    createFormOpen: false,
-    restoreFormOpen: false
-  };
-
-  handleCreateClick = (event /*: Event & { currentTarget: HTMLButtonElement } */) => {
-    event.preventDefault();
-    event.currentTarget.blur();
-    this.setState({ createFormOpen: true });
-  };
-
-  handleCreate = (profile /*: Profile */) => {
-    this.props.updateProfiles().then(() => {
-      this.context.router.push(
-        getProfilePath(profile.name, profile.language, this.props.organization)
-      );
-    });
-  };
-
-  closeCreateForm = () => {
-    this.setState({ createFormOpen: false });
-  };
-
-  handleRestoreClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.setState({ restoreFormOpen: true });
-  };
-
-  closeRestoreForm = () => {
-    this.setState({ restoreFormOpen: false });
-  };
-
-  render() {
-    return (
-      <header className="page-header">
-        <h1 className="page-title">
-          {translate('quality_profiles.page')}
-        </h1>
-
-        {this.props.canAdmin &&
-          <div className="page-actions button-group dropdown">
-            <button id="quality-profiles-create" onClick={this.handleCreateClick}>
-              {translate('create')}
-            </button>
-            <button className="dropdown-toggle js-more-admin-actions" data-toggle="dropdown">
-              <i className="icon-dropdown" />
-            </button>
-            <ul className="dropdown-menu dropdown-menu-right">
-              <li>
-                <a href="#" id="quality-profiles-restore" onClick={this.handleRestoreClick}>
-                  {translate('quality_profiles.restore_profile')}
-                </a>
-              </li>
-            </ul>
-          </div>}
-
-        <div className="page-description markdown">
-          {translate('quality_profiles.intro1')}
-          <br />
-          {translate('quality_profiles.intro2')}
-        </div>
-
-        {this.state.restoreFormOpen &&
-          <RestoreProfileForm
-            onClose={this.closeRestoreForm}
-            onRequestFail={this.props.onRequestFail}
-            onRestore={this.props.updateProfiles}
-            organization={this.props.organization}
-          />}
-
-        {this.state.createFormOpen &&
-          <CreateProfileForm
-            languages={this.props.languages}
-            onClose={this.closeCreateForm}
-            onRequestFail={this.props.onRequestFail}
-            onCreate={this.handleCreate}
-            organization={this.props.organization}
-          />}
-      </header>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.tsx
new file mode 100644 (file)
index 0000000..0f3b2b9
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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 PropTypes from 'prop-types';
+import CreateProfileForm from './CreateProfileForm';
+import RestoreProfileForm from './RestoreProfileForm';
+import { getProfilePath } from '../utils';
+import { translate } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  canAdmin: boolean;
+  languages: Array<{ key: string; name: string }>;
+  onRequestFail: (reason: any) => void;
+  organization: string | null;
+  updateProfiles: () => Promise<void>;
+}
+
+interface State {
+  createFormOpen: boolean;
+  restoreFormOpen: boolean;
+}
+
+export default class PageHeader extends React.PureComponent<Props, State> {
+  static contextTypes = {
+    router: PropTypes.object
+  };
+
+  state = {
+    createFormOpen: false,
+    restoreFormOpen: false
+  };
+
+  handleCreateClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    this.setState({ createFormOpen: true });
+  };
+
+  handleCreate = (profile: IProfile) => {
+    this.props.updateProfiles().then(() => {
+      this.context.router.push(
+        getProfilePath(profile.name, profile.language, this.props.organization)
+      );
+    });
+  };
+
+  closeCreateForm = () => {
+    this.setState({ createFormOpen: false });
+  };
+
+  handleRestoreClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.setState({ restoreFormOpen: true });
+  };
+
+  closeRestoreForm = () => {
+    this.setState({ restoreFormOpen: false });
+  };
+
+  render() {
+    return (
+      <header className="page-header">
+        <h1 className="page-title">
+          {translate('quality_profiles.page')}
+        </h1>
+
+        {this.props.canAdmin &&
+          <div className="page-actions button-group dropdown">
+            <button id="quality-profiles-create" onClick={this.handleCreateClick}>
+              {translate('create')}
+            </button>
+            <button className="dropdown-toggle js-more-admin-actions" data-toggle="dropdown">
+              <i className="icon-dropdown" />
+            </button>
+            <ul className="dropdown-menu dropdown-menu-right">
+              <li>
+                <a href="#" id="quality-profiles-restore" onClick={this.handleRestoreClick}>
+                  {translate('quality_profiles.restore_profile')}
+                </a>
+              </li>
+            </ul>
+          </div>}
+
+        <div className="page-description markdown">
+          {translate('quality_profiles.intro1')}
+          <br />
+          {translate('quality_profiles.intro2')}
+        </div>
+
+        {this.state.restoreFormOpen &&
+          <RestoreProfileForm
+            onClose={this.closeRestoreForm}
+            onRequestFail={this.props.onRequestFail}
+            onRestore={this.props.updateProfiles}
+            organization={this.props.organization}
+          />}
+
+        {this.state.createFormOpen &&
+          <CreateProfileForm
+            languages={this.props.languages}
+            onClose={this.closeCreateForm}
+            onRequestFail={this.props.onRequestFail}
+            onCreate={this.handleCreate}
+            organization={this.props.organization}
+          />}
+      </header>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js
deleted file mode 100644 (file)
index b912a96..0000000
+++ /dev/null
@@ -1,130 +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 { groupBy, pick, sortBy } from 'lodash';
-import ProfilesListRow from './ProfilesListRow';
-import ProfilesListHeader from './ProfilesListHeader';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-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 ProfilesList extends React.PureComponent {
-  /*:: props: Props; */
-
-  renderProfiles(profiles /*: Array<Profile> */) {
-    return profiles.map(profile =>
-      <ProfilesListRow
-        canAdmin={this.props.canAdmin}
-        key={profile.key}
-        onRequestFail={this.props.onRequestFail}
-        organization={this.props.organization}
-        profile={profile}
-        updateProfiles={this.props.updateProfiles}
-      />
-    );
-  }
-
-  renderHeader(languageKey /*: string */, profilesCount /*: number */) {
-    const language = this.props.languages.find(l => l.key === languageKey);
-
-    if (!language) {
-      return null;
-    }
-
-    return (
-      <thead>
-        <tr>
-          <th>
-            {language.name}
-            {', '}
-            {translateWithParameters('quality_profiles.x_profiles', profilesCount)}
-          </th>
-          <th className="text-right nowrap">
-            {translate('quality_profiles.list.projects')}
-          </th>
-          <th className="text-right nowrap">
-            {translate('quality_profiles.list.rules')}
-          </th>
-          <th className="text-right nowrap">
-            {translate('quality_profiles.list.updated')}
-          </th>
-          <th className="text-right nowrap">
-            {translate('quality_profiles.list.used')}
-          </th>
-          {this.props.canAdmin && <th>&nbsp;</th>}
-        </tr>
-      </thead>
-    );
-  }
-
-  render() {
-    const { profiles, languages } = this.props;
-    const { language } = this.props.location.query;
-
-    const profilesIndex = groupBy(profiles, profile => profile.language);
-    const profilesToShow = language ? pick(profilesIndex, language) : profilesIndex;
-
-    const languagesToShow = sortBy(Object.keys(profilesToShow));
-
-    return (
-      <div>
-        <ProfilesListHeader
-          currentFilter={language}
-          languages={languages}
-          organization={this.props.organization}
-        />
-
-        {Object.keys(profilesToShow).length === 0 &&
-          <div className="alert alert-warning spacer-top">
-            {translate('no_results')}
-          </div>}
-
-        {languagesToShow.map(languageKey =>
-          <div key={languageKey} className="quality-profile-box quality-profiles-table">
-            <table data-language={languageKey} className="data zebra zebra-hover">
-              {profilesToShow[languageKey] != null &&
-                this.renderHeader(languageKey, profilesToShow[languageKey].length)}
-
-              <TooltipsContainer>
-                <tbody>
-                  {profilesToShow[languageKey] != null &&
-                    this.renderProfiles(profilesToShow[languageKey])}
-                </tbody>
-              </TooltipsContainer>
-            </table>
-          </div>
-        )}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx
new file mode 100644 (file)
index 0000000..6f4c8f8
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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 { groupBy, pick, sortBy } from 'lodash';
+import ProfilesListRow from './ProfilesListRow';
+import ProfilesListHeader from './ProfilesListHeader';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { IProfile } from '../types';
+
+interface Props {
+  canAdmin: boolean;
+  languages: Array<{ key: string; name: string }>;
+  location: { query: { [p: string]: string } };
+  onRequestFail: (reason: any) => void;
+  organization: string | null;
+  profiles: IProfile[];
+  updateProfiles: () => Promise<void>;
+}
+
+export default class ProfilesList extends React.PureComponent<Props> {
+  renderProfiles(profiles: IProfile[]) {
+    return profiles.map(profile =>
+      <ProfilesListRow
+        canAdmin={this.props.canAdmin}
+        key={profile.key}
+        onRequestFail={this.props.onRequestFail}
+        organization={this.props.organization}
+        profile={profile}
+        updateProfiles={this.props.updateProfiles}
+      />
+    );
+  }
+
+  renderHeader(languageKey: string, profilesCount: number) {
+    const language = this.props.languages.find(l => l.key === languageKey);
+
+    if (!language) {
+      return null;
+    }
+
+    return (
+      <thead>
+        <tr>
+          <th>
+            {language.name}
+            {', '}
+            {translateWithParameters('quality_profiles.x_profiles', profilesCount)}
+          </th>
+          <th className="text-right nowrap">
+            {translate('quality_profiles.list.projects')}
+          </th>
+          <th className="text-right nowrap">
+            {translate('quality_profiles.list.rules')}
+          </th>
+          <th className="text-right nowrap">
+            {translate('quality_profiles.list.updated')}
+          </th>
+          <th className="text-right nowrap">
+            {translate('quality_profiles.list.used')}
+          </th>
+          {this.props.canAdmin && <th>&nbsp;</th>}
+        </tr>
+      </thead>
+    );
+  }
+
+  render() {
+    const { profiles, languages } = this.props;
+    const { language } = this.props.location.query;
+
+    const profilesIndex: { [language: string]: IProfile[] } = groupBy<IProfile>(
+      profiles,
+      profile => profile.language
+    );
+
+    const profilesToShow: { [language: string]: IProfile[] } = language
+      ? pick(profilesIndex, language)
+      : profilesIndex;
+
+    const languagesToShow = sortBy(Object.keys(profilesToShow));
+
+    return (
+      <div>
+        <ProfilesListHeader
+          currentFilter={language}
+          languages={languages}
+          organization={this.props.organization}
+        />
+
+        {Object.keys(profilesToShow).length === 0 &&
+          <div className="alert alert-warning spacer-top">
+            {translate('no_results')}
+          </div>}
+
+        {languagesToShow.map(languageKey =>
+          <div key={languageKey} className="quality-profile-box quality-profiles-table">
+            <table data-language={languageKey} className="data zebra zebra-hover">
+              {profilesToShow[languageKey] != null &&
+                this.renderHeader(languageKey, profilesToShow[languageKey].length)}
+
+              <tbody>
+                {profilesToShow[languageKey] != null &&
+                  this.renderProfiles(profilesToShow[languageKey])}
+              </tbody>
+            </table>
+          </div>
+        )}
+      </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js
deleted file mode 100644 (file)
index ab76d1a..0000000
+++ /dev/null
@@ -1,99 +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 { IndexLink } from 'react-router';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { getProfilesPath, getProfilesForLanguagePath } from '../utils';
-
-/*::
-type Props = {
-  currentFilter?: string,
-  languages: Array<{ key: string, name: string }>,
-  organization: ?string
-};
-*/
-
-export default class ProfilesListHeader extends React.PureComponent {
-  /*:: props: Props; */
-
-  renderFilterToggle() {
-    const { languages, currentFilter } = this.props;
-    const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
-
-    const label = currentLanguage
-      ? translateWithParameters('quality_profiles.x_Profiles', currentLanguage.name)
-      : translate('quality_profiles.all_profiles');
-
-    return (
-      <a
-        className="dropdown-toggle link-no-underline js-language-filter"
-        href="#"
-        data-toggle="dropdown">
-        {label} <i className="icon-dropdown" />
-      </a>
-    );
-  }
-
-  renderFilterMenu() {
-    return (
-      <ul className="dropdown-menu">
-        <li>
-          <IndexLink to={getProfilesPath(this.props.organization)}>
-            {translate('quality_profiles.all_profiles')}
-          </IndexLink>
-        </li>
-        {this.props.languages.map(language =>
-          <li key={language.key}>
-            <IndexLink
-              to={getProfilesForLanguagePath(language.key, this.props.organization)}
-              className="js-language-filter-option"
-              data-language={language.key}>
-              {language.name}
-            </IndexLink>
-          </li>
-        )}
-      </ul>
-    );
-  }
-
-  render() {
-    if (this.props.languages.length < 2) {
-      return null;
-    }
-
-    const { languages, currentFilter } = this.props;
-    const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
-
-    // if unknown language, then
-    if (currentFilter && !currentLanguage) {
-      return null;
-    }
-
-    return (
-      <header className="quality-profiles-list-header clearfix">
-        <div className="dropdown">
-          {this.renderFilterToggle()}
-          {this.renderFilterMenu()}
-        </div>
-      </header>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.tsx
new file mode 100644 (file)
index 0000000..15b98d6
--- /dev/null
@@ -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 { IndexLink } from 'react-router';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { getProfilesPath, getProfilesForLanguagePath } from '../utils';
+
+interface Props {
+  currentFilter?: string;
+  languages: Array<{ key: string; name: string }>;
+  organization: string | null;
+}
+
+export default class ProfilesListHeader extends React.PureComponent<Props> {
+  renderFilterToggle() {
+    const { languages, currentFilter } = this.props;
+    const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
+
+    const label = currentLanguage
+      ? translateWithParameters('quality_profiles.x_Profiles', currentLanguage.name)
+      : translate('quality_profiles.all_profiles');
+
+    return (
+      <a
+        className="dropdown-toggle link-no-underline js-language-filter"
+        href="#"
+        data-toggle="dropdown">
+        {label} <i className="icon-dropdown" />
+      </a>
+    );
+  }
+
+  renderFilterMenu() {
+    return (
+      <ul className="dropdown-menu">
+        <li>
+          <IndexLink to={getProfilesPath(this.props.organization)}>
+            {translate('quality_profiles.all_profiles')}
+          </IndexLink>
+        </li>
+        {this.props.languages.map(language =>
+          <li key={language.key}>
+            <IndexLink
+              to={getProfilesForLanguagePath(language.key, this.props.organization)}
+              className="js-language-filter-option"
+              data-language={language.key}>
+              {language.name}
+            </IndexLink>
+          </li>
+        )}
+      </ul>
+    );
+  }
+
+  render() {
+    if (this.props.languages.length < 2) {
+      return null;
+    }
+
+    const { languages, currentFilter } = this.props;
+    const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);
+
+    // if unknown language, then
+    if (currentFilter && !currentLanguage) {
+      return null;
+    }
+
+    return (
+      <header className="quality-profiles-list-header clearfix">
+        <div className="dropdown">
+          {this.renderFilterToggle()}
+          {this.renderFilterMenu()}
+        </div>
+      </header>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js
deleted file mode 100644 (file)
index f9d1805..0000000
+++ /dev/null
@@ -1,186 +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 ProfileLink from '../components/ProfileLink';
-import ProfileDate from '../components/ProfileDate';
-import ProfileActions from '../components/ProfileActions';
-import BuiltInBadge from '../components/BuiltInBadge';
-import { translate } from '../../../helpers/l10n';
-import { getRulesUrl } from '../../../helpers/urls';
-import { isStagnant } from '../utils';
-/*:: import type { Profile } from '../propTypes'; */
-
-/*::
-type Props = {
-  canAdmin: boolean,
-  onRequestFail: Object => void,
-  organization: ?string,
-  profile: Profile,
-  updateProfiles: () => Promise<*>
-};
-*/
-
-export default class ProfilesListRow extends React.PureComponent {
-  /*:: props: Props; */
-
-  renderName() {
-    const { profile } = this.props;
-    const offset = 25 * (profile.depth - 1);
-    return (
-      <div style={{ paddingLeft: offset }}>
-        <ProfileLink
-          language={profile.language}
-          name={profile.name}
-          organization={this.props.organization}>
-          {profile.name}
-        </ProfileLink>
-        {profile.isBuiltIn && <BuiltInBadge className="spacer-left" />}
-      </div>
-    );
-  }
-
-  renderProjects() {
-    const { profile } = this.props;
-
-    if (profile.isDefault) {
-      return (
-        <span className="badge">
-          {translate('default')}
-        </span>
-      );
-    }
-
-    return (
-      <span>
-        {profile.projectCount}
-      </span>
-    );
-  }
-
-  renderRules() {
-    const { profile } = this.props;
-
-    const activeRulesUrl = getRulesUrl(
-      {
-        qprofile: profile.key,
-        activation: 'true'
-      },
-      this.props.organization
-    );
-
-    const deprecatedRulesUrl = getRulesUrl(
-      {
-        qprofile: profile.key,
-        activation: 'true',
-        statuses: 'DEPRECATED'
-      },
-      this.props.organization
-    );
-
-    return (
-      <div>
-        {profile.activeDeprecatedRuleCount > 0 &&
-          <span className="spacer-right">
-            <Link
-              to={deprecatedRulesUrl}
-              className="badge badge-normal-size badge-danger-light"
-              title={translate('quality_profiles.deprecated_rules')}
-              data-toggle="tooltip">
-              {profile.activeDeprecatedRuleCount}
-            </Link>
-          </span>}
-
-        <Link to={activeRulesUrl}>
-          {profile.activeRuleCount}
-        </Link>
-      </div>
-    );
-  }
-
-  renderUpdateDate() {
-    const date = <ProfileDate date={this.props.profile.userUpdatedAt} />;
-    if (isStagnant(this.props.profile)) {
-      return (
-        <span className="badge badge-normal-size badge-focus">
-          {date}
-        </span>
-      );
-    } else {
-      return date;
-    }
-  }
-
-  renderUsageDate() {
-    const { lastUsed } = this.props.profile;
-    const date = <ProfileDate date={lastUsed} />;
-    if (!lastUsed) {
-      return (
-        <span className="badge badge-normal-size badge-focus">
-          {date}
-        </span>
-      );
-    } else {
-      return date;
-    }
-  }
-
-  render() {
-    return (
-      <tr
-        className="quality-profiles-table-row"
-        data-key={this.props.profile.key}
-        data-name={this.props.profile.name}>
-        <td className="quality-profiles-table-name">
-          {this.renderName()}
-        </td>
-        <td className="quality-profiles-table-projects thin nowrap text-right">
-          {this.renderProjects()}
-        </td>
-        <td className="quality-profiles-table-rules thin nowrap text-right">
-          {this.renderRules()}
-        </td>
-        <td className="quality-profiles-table-date thin nowrap text-right">
-          {this.renderUpdateDate()}
-        </td>
-        <td className="quality-profiles-table-date thin nowrap text-right">
-          {this.renderUsageDate()}
-        </td>
-        {this.props.canAdmin &&
-          <td className="quality-profiles-table-actions thin nowrap text-right">
-            <div className="dropdown">
-              <button className="dropdown-toggle" data-toggle="dropdown">
-                <i className="icon-dropdown" />
-              </button>
-              <ProfileActions
-                canAdmin={this.props.canAdmin}
-                fromList={true}
-                onRequestFail={this.props.onRequestFail}
-                organization={this.props.organization}
-                profile={this.props.profile}
-                updateProfiles={this.props.updateProfiles}
-              />
-            </div>
-          </td>}
-      </tr>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx
new file mode 100644 (file)
index 0000000..2d982c3
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * 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 ProfileLink from '../components/ProfileLink';
+import ProfileDate from '../components/ProfileDate';
+import ProfileActions from '../components/ProfileActions';
+import BuiltInBadge from '../components/BuiltInBadge';
+import { translate } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+import { isStagnant } from '../utils';
+import { IProfile } from '../types';
+import Tooltip from '../../../components/controls/Tooltip';
+
+interface Props {
+  canAdmin: boolean;
+  onRequestFail: (reason: any) => void;
+  organization: string | null;
+  profile: IProfile;
+  updateProfiles: () => Promise<void>;
+}
+
+export default class ProfilesListRow extends React.PureComponent<Props> {
+  renderName() {
+    const { profile } = this.props;
+    const offset = 25 * (profile.depth - 1);
+    return (
+      <div style={{ paddingLeft: offset }}>
+        <ProfileLink
+          language={profile.language}
+          name={profile.name}
+          organization={this.props.organization}>
+          {profile.name}
+        </ProfileLink>
+        {profile.isBuiltIn && <BuiltInBadge className="spacer-left" />}
+      </div>
+    );
+  }
+
+  renderProjects() {
+    const { profile } = this.props;
+
+    if (profile.isDefault) {
+      return (
+        <span className="badge">
+          {translate('default')}
+        </span>
+      );
+    }
+
+    return (
+      <span>
+        {profile.projectCount}
+      </span>
+    );
+  }
+
+  renderRules() {
+    const { profile } = this.props;
+
+    const activeRulesUrl = getRulesUrl(
+      {
+        qprofile: profile.key,
+        activation: 'true'
+      },
+      this.props.organization
+    );
+
+    const deprecatedRulesUrl = getRulesUrl(
+      {
+        qprofile: profile.key,
+        activation: 'true',
+        statuses: 'DEPRECATED'
+      },
+      this.props.organization
+    );
+
+    return (
+      <div>
+        {profile.activeDeprecatedRuleCount > 0 &&
+          <span className="spacer-right">
+            <Tooltip overlay={translate('quality_profiles.deprecated_rules')}>
+              <Link to={deprecatedRulesUrl} className="badge badge-normal-size badge-danger-light">
+                {profile.activeDeprecatedRuleCount}
+              </Link>
+            </Tooltip>
+          </span>}
+
+        <Link to={activeRulesUrl}>
+          {profile.activeRuleCount}
+        </Link>
+      </div>
+    );
+  }
+
+  renderUpdateDate() {
+    const date = <ProfileDate date={this.props.profile.userUpdatedAt} />;
+    if (isStagnant(this.props.profile)) {
+      return (
+        <span className="badge badge-normal-size badge-focus">
+          {date}
+        </span>
+      );
+    } else {
+      return date;
+    }
+  }
+
+  renderUsageDate() {
+    const { lastUsed } = this.props.profile;
+    const date = <ProfileDate date={lastUsed} />;
+    if (!lastUsed) {
+      return (
+        <span className="badge badge-normal-size badge-focus">
+          {date}
+        </span>
+      );
+    } else {
+      return date;
+    }
+  }
+
+  render() {
+    return (
+      <tr
+        className="quality-profiles-table-row"
+        data-key={this.props.profile.key}
+        data-name={this.props.profile.name}>
+        <td className="quality-profiles-table-name">
+          {this.renderName()}
+        </td>
+        <td className="quality-profiles-table-projects thin nowrap text-right">
+          {this.renderProjects()}
+        </td>
+        <td className="quality-profiles-table-rules thin nowrap text-right">
+          {this.renderRules()}
+        </td>
+        <td className="quality-profiles-table-date thin nowrap text-right">
+          {this.renderUpdateDate()}
+        </td>
+        <td className="quality-profiles-table-date thin nowrap text-right">
+          {this.renderUsageDate()}
+        </td>
+        {this.props.canAdmin &&
+          <td className="quality-profiles-table-actions thin nowrap text-right">
+            <div className="dropdown">
+              <button className="dropdown-toggle" data-toggle="dropdown">
+                <i className="icon-dropdown" />
+              </button>
+              <ProfileActions
+                canAdmin={this.props.canAdmin}
+                fromList={true}
+                onRequestFail={this.props.onRequestFail}
+                organization={this.props.organization}
+                profile={this.props.profile}
+                updateProfiles={this.props.updateProfiles}
+              />
+            </div>
+          </td>}
+      </tr>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.js
deleted file mode 100644 (file)
index 26411ab..0000000
+++ /dev/null
@@ -1,162 +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 Modal from 'react-modal';
-import { restoreQualityProfile } from '../../../api/quality-profiles';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  onClose: () => void,
-  onRequestFail: Object => void,
-  onRestore: Function,
-  organization: ?string
-};
-*/
-
-/*::
-type State = {
-  loading: boolean,
-  profile?: { name: string },
-  ruleFailures?: number,
-  ruleSuccesses?: number
-};
-*/
-
-export default class RestoreProfileForm extends React.PureComponent {
-  /*:: form: HTMLFormElement; */
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = { loading: false };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleCancelClick = (event /*: Event */) => {
-    event.preventDefault();
-    this.props.onClose();
-  };
-
-  handleFormSubmit = (event /*: Event */) => {
-    event.preventDefault();
-
-    this.setState({ loading: true });
-
-    const data = new FormData(this.form);
-    if (this.props.organization) {
-      data.append('organization', this.props.organization);
-    }
-
-    restoreQualityProfile(data).then(
-      response => {
-        if (this.mounted) {
-          this.setState({
-            loading: false,
-            profile: response.profile,
-            ruleFailures: response.ruleFailures,
-            ruleSuccesses: response.ruleSuccesses
-          });
-        }
-        this.props.onRestore();
-      },
-      error => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-        this.props.onRequestFail(error);
-      }
-    );
-  };
-
-  render() {
-    const header = translate('quality_profiles.restore_profile');
-
-    const { loading, profile, ruleFailures, ruleSuccesses } = this.state;
-
-    return (
-      <Modal
-        isOpen={true}
-        contentLabel={header}
-        className="modal"
-        overlayClassName="modal-overlay"
-        onRequestClose={this.props.onClose}>
-        <form
-          id="restore-profile-form"
-          onSubmit={this.handleFormSubmit}
-          ref={node => (this.form = node)}>
-          <div className="modal-head">
-            <h2>
-              {header}
-            </h2>
-          </div>
-
-          <div className="modal-body">
-            {profile != null && ruleSuccesses != null
-              ? ruleFailures
-                ? <div className="alert alert-warning">
-                    {translateWithParameters(
-                      'quality_profiles.restore_profile.warning',
-                      profile.name,
-                      ruleSuccesses,
-                      ruleFailures
-                    )}
-                  </div>
-                : <div className="alert alert-success">
-                    {translateWithParameters(
-                      'quality_profiles.restore_profile.success',
-                      profile.name,
-                      ruleSuccesses
-                    )}
-                  </div>
-              : <div className="modal-field">
-                  <label htmlFor="restore-profile-backup">
-                    {translate('backup')}
-                    <em className="mandatory">*</em>
-                  </label>
-                  <input id="restore-profile-backup" name="backup" required={true} type="file" />
-                </div>}
-          </div>
-
-          {ruleSuccesses == null
-            ? <div className="modal-foot">
-                {loading && <i className="spinner spacer-right" />}
-                <button disabled={loading} id="restore-profile-submit">
-                  {translate('restore')}
-                </button>
-                <a href="#" id="restore-profile-cancel" onClick={this.handleCancelClick}>
-                  {translate('cancel')}
-                </a>
-              </div>
-            : <div className="modal-foot">
-                <a href="#" onClick={this.handleCancelClick}>
-                  {translate('close')}
-                </a>
-              </div>}
-        </form>
-      </Modal>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/RestoreProfileForm.tsx
new file mode 100644 (file)
index 0000000..5f12d61
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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 Modal from 'react-modal';
+import { restoreQualityProfile } from '../../../api/quality-profiles';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+interface Props {
+  onClose: () => void;
+  onRequestFail: (reason: any) => void;
+  onRestore: () => void;
+  organization: string | null;
+}
+
+interface State {
+  loading: boolean;
+  profile?: { name: string };
+  ruleFailures?: number;
+  ruleSuccesses?: number;
+}
+
+export default class RestoreProfileForm extends React.PureComponent<Props, State> {
+  mounted: boolean;
+  state: State = { loading: false };
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  handleCancelClick = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    this.props.onClose();
+  };
+
+  handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+    event.preventDefault();
+
+    this.setState({ loading: true });
+
+    const data = new FormData(event.currentTarget);
+    if (this.props.organization) {
+      data.append('organization', this.props.organization);
+    }
+
+    restoreQualityProfile(data).then(
+      (response: any) => {
+        if (this.mounted) {
+          this.setState({
+            loading: false,
+            profile: response.profile,
+            ruleFailures: response.ruleFailures,
+            ruleSuccesses: response.ruleSuccesses
+          });
+        }
+        this.props.onRestore();
+      },
+      (error: any) => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+        this.props.onRequestFail(error);
+      }
+    );
+  };
+
+  render() {
+    const header = translate('quality_profiles.restore_profile');
+
+    const { loading, profile, ruleFailures, ruleSuccesses } = this.state;
+
+    return (
+      <Modal
+        isOpen={true}
+        contentLabel={header}
+        className="modal"
+        overlayClassName="modal-overlay"
+        onRequestClose={this.props.onClose}>
+        <form id="restore-profile-form" onSubmit={this.handleFormSubmit}>
+          <div className="modal-head">
+            <h2>
+              {header}
+            </h2>
+          </div>
+
+          <div className="modal-body">
+            {profile != null && ruleSuccesses != null
+              ? ruleFailures
+                ? <div className="alert alert-warning">
+                    {translateWithParameters(
+                      'quality_profiles.restore_profile.warning',
+                      profile.name,
+                      ruleSuccesses,
+                      ruleFailures
+                    )}
+                  </div>
+                : <div className="alert alert-success">
+                    {translateWithParameters(
+                      'quality_profiles.restore_profile.success',
+                      profile.name,
+                      ruleSuccesses
+                    )}
+                  </div>
+              : <div className="modal-field">
+                  <label htmlFor="restore-profile-backup">
+                    {translate('backup')}
+                    <em className="mandatory">*</em>
+                  </label>
+                  <input id="restore-profile-backup" name="backup" required={true} type="file" />
+                </div>}
+          </div>
+
+          {ruleSuccesses == null
+            ? <div className="modal-foot">
+                {loading && <i className="spinner spacer-right" />}
+                <button disabled={loading} id="restore-profile-submit">
+                  {translate('restore')}
+                </button>
+                <a href="#" id="restore-profile-cancel" onClick={this.handleCancelClick}>
+                  {translate('cancel')}
+                </a>
+              </div>
+            : <div className="modal-foot">
+                <a href="#" onClick={this.handleCancelClick}>
+                  {translate('close')}
+                </a>
+              </div>}
+        </form>
+      </Modal>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js b/server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js
deleted file mode 100644 (file)
index dbf1364..0000000
+++ /dev/null
@@ -1,76 +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 { shape, string, number, bool, arrayOf } from 'prop-types';
-
-/*::
-export type Profile = {
-  key: string,
-  name: string,
-  isBuiltIn: boolean,
-  isDefault: boolean,
-  isInherited: boolean,
-  language: string,
-  languageName: string,
-  activeRuleCount: number,
-  activeDeprecatedRuleCount: number,
-  projectCount?: number,
-  parentKey?: string,
-  parentName?: string,
-  userUpdatedAt?: string,
-  lastUsed?: string,
-  rulesUpdatedAt: string,
-  depth: number,
-  childrenCount: number
-};
-*/
-
-/*::
-export type Exporter = {
-  key: string,
-  name: string,
-  languages: Array<string>
-};
-*/
-
-export const ProfileType = shape({
-  key: string.isRequired,
-  name: string.isRequired,
-  isDefault: bool.isRequired,
-  isInherited: bool.isRequired,
-  language: string.isRequired,
-  languageName: string.isRequired,
-  activeRuleCount: number.isRequired,
-  activeDeprecatedRuleCount: number.isRequired,
-  projectCount: number,
-  parentKey: string,
-  parentName: string,
-  userUpdatedAt: string,
-  lastUsed: string
-});
-
-export const ProfilesListType = arrayOf(ProfileType);
-
-const LanguageType = shape({
-  key: string.isRequired,
-  name: string.isRequired
-});
-
-export const LanguagesListType = arrayOf(LanguageType);
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.js b/server/sonar-web/src/main/js/apps/quality-profiles/routes.js
deleted file mode 100644 (file)
index 1d7b96f..0000000
+++ /dev/null
@@ -1,68 +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 { withRouter } from 'react-router';
-
-const routes = [
-  {
-    getComponent(state, callback) {
-      import('./components/AppContainer').then(i => i.default).then(AppContainer => {
-        if (state.params.organizationKey) {
-          callback(null, AppContainer);
-        } else {
-          import('../organizations/forSingleOrganization')
-            .then(i => i.default)
-            .then(forSingleOrganization => callback(null, forSingleOrganization(AppContainer)));
-        }
-      });
-    },
-    getIndexRoute(_, callback) {
-      import('./home/HomeContainer').then(i => callback(null, { component: i.default }));
-    },
-    childRoutes: [
-      {
-        getComponent(_, callback) {
-          import('./components/ProfileContainer').then(i => callback(null, withRouter(i.default)));
-        },
-        childRoutes: [
-          {
-            path: 'show',
-            getComponent(_, callback) {
-              import('./details/ProfileDetails').then(i => callback(null, i.default));
-            }
-          },
-          {
-            path: 'changelog',
-            getComponent(_, callback) {
-              import('./changelog/ChangelogContainer').then(i => callback(null, i.default));
-            }
-          },
-          {
-            path: 'compare',
-            getComponent(_, callback) {
-              import('./compare/ComparisonContainer').then(i => callback(null, i.default));
-            }
-          }
-        ]
-      }
-    ]
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/routes.ts b/server/sonar-web/src/main/js/apps/quality-profiles/routes.ts
new file mode 100644 (file)
index 0000000..cac3704
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * 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 { withRouter, RouterState, IndexRouteProps, RouteComponent } from 'react-router';
+
+const routes = [
+  {
+    getComponent(state: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/AppContainer').then(i => i.default).then(AppContainer => {
+        if (state.params.organizationKey) {
+          callback(null, AppContainer);
+        } else {
+          import('../organizations/forSingleOrganization')
+            .then(i => i.default)
+            .then(forSingleOrganization => callback(null, forSingleOrganization(AppContainer)));
+        }
+      });
+    },
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./home/HomeContainer').then(i => callback(null, { component: i.default }));
+    },
+    childRoutes: [
+      {
+        getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+          import('./components/ProfileContainer').then(i => callback(null, withRouter(i.default)));
+        },
+        childRoutes: [
+          {
+            path: 'show',
+            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+              import('./details/ProfileDetails').then(i => callback(null, i.default));
+            }
+          },
+          {
+            path: 'changelog',
+            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+              import('./changelog/ChangelogContainer').then(i => callback(null, i.default));
+            }
+          },
+          {
+            path: 'compare',
+            getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+              import('./compare/ComparisonContainer').then(i => callback(null, i.default));
+            }
+          }
+        ]
+      }
+    ]
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/types.ts b/server/sonar-web/src/main/js/apps/quality-profiles/types.ts
new file mode 100644 (file)
index 0000000..73e4384
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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 IProfile {
+  key: string;
+  name: string;
+  isBuiltIn: boolean;
+  isDefault: boolean;
+  isInherited: boolean;
+  language: string;
+  languageName: string;
+  activeRuleCount: number;
+  activeDeprecatedRuleCount: number;
+  projectCount?: number;
+  parentKey?: string;
+  parentName?: string;
+  userUpdatedAt?: string;
+  lastUsed?: string;
+  rulesUpdatedAt: string;
+  depth: number;
+  childrenCount: number;
+}
+
+export interface IExporter {
+  key: string;
+  name: string;
+  languages: string[];
+}
+
+export interface IProfileChangelogEvent {
+  action: string;
+  authorName: string;
+  date: string;
+  params?: { [change: string]: string | null };
+  ruleKey: string;
+  ruleName: string;
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/utils.js b/server/sonar-web/src/main/js/apps/quality-profiles/utils.js
deleted file mode 100644 (file)
index 60ec46e..0000000
+++ /dev/null
@@ -1,128 +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 { sortBy } from 'lodash';
-import moment from 'moment';
-/*:: import type { Profile } from './propTypes'; */
-
-export function sortProfiles(profiles /*: Array<Profile> */) {
-  const result = [];
-  const sorted = sortBy(profiles, 'name');
-
-  function retrieveChildren(parent) {
-    return sorted.filter(
-      p => (parent == null && p.parentKey == null) || (parent != null && p.parentKey === parent.key)
-    );
-  }
-
-  function putProfile(profile = null, depth = 1) {
-    const children = retrieveChildren(profile);
-
-    if (profile != null) {
-      result.push({ ...profile, childrenCount: children.length, depth });
-    }
-
-    children.forEach(child => putProfile(child, depth + 1));
-  }
-
-  sorted
-    .filter(
-      profile => profile.parentKey == null || sorted.find(p => p.key === profile.parentKey) == null
-    )
-    .forEach(profile => putProfile(profile));
-
-  return result;
-}
-
-export function createFakeProfile(overrides /*: {} */) {
-  return {
-    key: 'key',
-    name: 'name',
-    isDefault: false,
-    isInherited: false,
-    language: 'js',
-    languageName: 'JavaScript',
-    activeRuleCount: 10,
-    activeDeprecatedRuleCount: 2,
-    projectCount: 3,
-    ...overrides
-  };
-}
-
-export function isStagnant(profile /*: Profile */) {
-  return moment().diff(moment(profile.userUpdatedAt), 'years') >= 1;
-}
-
-export const getProfilesPath = (organization /*: ?string */) =>
-  organization ? `/organizations/${organization}/quality_profiles` : '/profiles';
-
-export const getProfilesForLanguagePath = (
-  language /*: string */,
-  organization /*: ?string */
-) => ({
-  pathname: getProfilesPath(organization),
-  query: { language }
-});
-
-export const getProfilePath = (
-  name /*: string */,
-  language /*: string */,
-  organization /*: ?string */
-) => ({
-  pathname: getProfilesPath(organization) + '/show',
-  query: { name, language }
-});
-
-export const getProfileComparePath = (
-  name /*: string */,
-  language /*: string */,
-  organization /*: ?string */,
-  withKey /*: ?string */
-) => {
-  const query /*: Object */ = { language, name };
-  if (withKey) {
-    Object.assign(query, { withKey });
-  }
-  return {
-    pathname: getProfilesPath(organization) + '/compare',
-    query
-  };
-};
-
-export const getProfileChangelogPath = (
-  name /*: string */,
-  language /*: string */,
-  organization /*: ?string */,
-  filter /*: ?{ since?: string, to?: string } */
-) => {
-  const query /*: Object */ = { language, name };
-  if (filter) {
-    if (filter.since) {
-      Object.assign(query, { since: filter.since });
-    }
-    if (filter.to) {
-      Object.assign(query, { to: filter.to });
-    }
-  }
-  return {
-    pathname: getProfilesPath(organization) + '/changelog',
-    query
-  };
-};
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts b/server/sonar-web/src/main/js/apps/quality-profiles/utils.ts
new file mode 100644 (file)
index 0000000..890f387
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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 { sortBy } from 'lodash';
+import * as moment from 'moment';
+import { IProfile } from './types';
+
+export function sortProfiles(profiles: IProfile[]) {
+  const result: IProfile[] = [];
+  const sorted = sortBy(profiles, 'name');
+
+  function retrieveChildren(parent: IProfile | null) {
+    return sorted.filter(
+      p => (parent == null && p.parentKey == null) || (parent != null && p.parentKey === parent.key)
+    );
+  }
+
+  function putProfile(profile: IProfile | null = null, depth: number = 1) {
+    const children = retrieveChildren(profile);
+
+    if (profile != null) {
+      result.push({ ...profile, childrenCount: children.length, depth });
+    }
+
+    children.forEach(child => putProfile(child, depth + 1));
+  }
+
+  sorted
+    .filter(
+      profile => profile.parentKey == null || sorted.find(p => p.key === profile.parentKey) == null
+    )
+    .forEach(profile => putProfile(profile));
+
+  return result;
+}
+
+export function createFakeProfile(overrides?: any) {
+  return {
+    key: 'key',
+    name: 'name',
+    isDefault: false,
+    isInherited: false,
+    language: 'js',
+    languageName: 'JavaScript',
+    activeRuleCount: 10,
+    activeDeprecatedRuleCount: 2,
+    projectCount: 3,
+    ...overrides
+  };
+}
+
+export function isStagnant(profile: IProfile) {
+  return moment().diff(moment(profile.userUpdatedAt), 'years') >= 1;
+}
+
+export const getProfilesPath = (organization: string | null) =>
+  organization ? `/organizations/${organization}/quality_profiles` : '/profiles';
+
+export const getProfilesForLanguagePath = (language: string, organization: string | null) => ({
+  pathname: getProfilesPath(organization),
+  query: { language }
+});
+
+export const getProfilePath = (name: string, language: string, organization: string | null) => ({
+  pathname: getProfilesPath(organization) + '/show',
+  query: { name, language }
+});
+
+export const getProfileComparePath = (
+  name: string,
+  language: string,
+  organization: string | null,
+  withKey?: string
+) => {
+  const query = { language, name };
+  if (withKey) {
+    Object.assign(query, { withKey });
+  }
+  return {
+    pathname: getProfilesPath(organization) + '/compare',
+    query
+  };
+};
+
+export const getProfileChangelogPath = (
+  name: string,
+  language: string,
+  organization: string | null,
+  filter?: { since?: string; to?: string }
+) => {
+  const query = { language, name };
+  if (filter) {
+    if (filter.since) {
+      Object.assign(query, { since: filter.since });
+    }
+    if (filter.to) {
+      Object.assign(query, { to: filter.to });
+    }
+  }
+  return {
+    pathname: getProfilesPath(organization) + '/changelog',
+    query
+  };
+};
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js b/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.js
deleted file mode 100644 (file)
index a8f98da..0000000
+++ /dev/null
@@ -1,49 +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 PropTypes from 'prop-types';
-
-export default class Unauthorized extends React.PureComponent {
-  static propTypes = {
-    location: PropTypes.object.isRequired
-  };
-
-  render() {
-    const { message } = this.props.location.query;
-
-    return (
-      <div className="text-center">
-        <p id="unauthorized">
-          {"You're not authorized to access this page. Please contact the administrator."}
-        </p>
-
-        {!!message &&
-          <p className="spacer-top">
-            Reason : {message}
-          </p>}
-
-        <div className="big-spacer-top">
-          <a href={window.baseUrl + '/'}>Home</a>
-        </div>
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Unauthorized.tsx
new file mode 100644 (file)
index 0000000..edfa3d6
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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';
+
+interface Props {
+  location: {
+    query: {
+      message: string;
+    };
+  };
+}
+
+export default function Unauthorized(props: Props) {
+  const { message } = props.location.query;
+
+  return (
+    <div className="text-center">
+      <p id="unauthorized">
+        {"You're not authorized to access this page. Please contact the administrator."}
+      </p>
+
+      {!!message &&
+        <p className="spacer-top">
+          Reason : {message}
+        </p>}
+
+      <div className="big-spacer-top">
+        <Link to="/">Home</Link>
+      </div>
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.js b/server/sonar-web/src/main/js/apps/sessions/routes.js
deleted file mode 100644 (file)
index 32e367c..0000000
+++ /dev/null
@@ -1,41 +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.
- */
-const routes = [
-  {
-    path: 'new',
-    getComponent(_, callback) {
-      import('./components/LoginFormContainer').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'logout',
-    getComponent(_, callback) {
-      import('./components/Logout').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'unauthorized',
-    getComponent(_, callback) {
-      import('./components/Unauthorized').then(i => callback(null, i.default));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/sessions/routes.ts b/server/sonar-web/src/main/js/apps/sessions/routes.ts
new file mode 100644 (file)
index 0000000..ce1a2e8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 { RouterState, RouteComponent } from 'react-router';
+
+const routes = [
+  {
+    path: 'new',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/LoginFormContainer').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'logout',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/Logout').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'unauthorized',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/Unauthorized').then(i => callback(null, i.default));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/settings/routes.js b/server/sonar-web/src/main/js/apps/settings/routes.js
deleted file mode 100644 (file)
index 9a408f1..0000000
+++ /dev/null
@@ -1,46 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
-    }
-  },
-  {
-    path: 'licenses',
-    getComponent(_, callback) {
-      import('./licenses/LicensesApp').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'encryption',
-    getComponent(_, callback) {
-      import('./encryption/EncryptionAppContainer').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'server_id',
-    getComponent(_, callback) {
-      import('./serverId/ServerIdAppContainer').then(i => callback(null, i.default));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/settings/routes.ts b/server/sonar-web/src/main/js/apps/settings/routes.ts
new file mode 100644 (file)
index 0000000..54afd12
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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 { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/AppContainer').then(i => callback(null, { component: i.default }));
+    }
+  },
+  {
+    path: 'licenses',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./licenses/LicensesApp').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'encryption',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./encryption/EncryptionAppContainer').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'server_id',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./serverId/ServerIdAppContainer').then(i => callback(null, i.default));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/system/routes.js b/server/sonar-web/src/main/js/apps/system/routes.js
deleted file mode 100644 (file)
index 0ab080a..0000000
+++ /dev/null
@@ -1,28 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./main').then(i => callback(null, { component: i.default }));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/system/routes.ts b/server/sonar-web/src/main/js/apps/system/routes.ts
new file mode 100644 (file)
index 0000000..9f7f40c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./main').then(i => callback(null, { component: (i as any).default }));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/update-center/routes.js b/server/sonar-web/src/main/js/apps/update-center/routes.js
deleted file mode 100644 (file)
index 81c884a..0000000
+++ /dev/null
@@ -1,54 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/UpdateCenterAppContainer').then(i =>
-        callback(null, { component: i.default })
-      );
-    }
-  },
-  {
-    path: 'installed',
-    getComponent(_, callback) {
-      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'updates',
-    getComponent(_, callback) {
-      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'available',
-    getComponent(_, callback) {
-      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
-    }
-  },
-  {
-    path: 'system',
-    getComponent(_, callback) {
-      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/update-center/routes.ts b/server/sonar-web/src/main/js/apps/update-center/routes.ts
new file mode 100644 (file)
index 0000000..e41cfdc
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/UpdateCenterAppContainer').then(i =>
+        callback(null, { component: i.default })
+      );
+    }
+  },
+  {
+    path: 'installed',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'updates',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'available',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
+    }
+  },
+  {
+    path: 'system',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/UpdateCenterAppContainer').then(i => callback(null, i.default));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/users/routes.js b/server/sonar-web/src/main/js/apps/users/routes.js
deleted file mode 100644 (file)
index b07c3c9..0000000
+++ /dev/null
@@ -1,28 +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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/UsersAppContainer').then(i => callback(null, { component: i.default }));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/users/routes.ts b/server/sonar-web/src/main/js/apps/users/routes.ts
new file mode 100644 (file)
index 0000000..88751d8
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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 { RouterState, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/UsersAppContainer').then(i => callback(null, { component: i.default }));
+    }
+  }
+];
+
+export default routes;
diff --git a/server/sonar-web/src/main/js/apps/web-api/routes.js b/server/sonar-web/src/main/js/apps/web-api/routes.js
deleted file mode 100644 (file)
index 5993b47..0000000
+++ /dev/null
@@ -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.
- */
-const routes = [
-  {
-    getIndexRoute(_, callback) {
-      import('./components/WebApiApp').then(i => callback(null, { component: i.default }));
-    }
-  },
-  {
-    path: '**',
-    getComponent(_, callback) {
-      import('./components/WebApiApp').then(i => callback(null, i.default));
-    }
-  }
-];
-
-export default routes;
diff --git a/server/sonar-web/src/main/js/apps/web-api/routes.ts b/server/sonar-web/src/main/js/apps/web-api/routes.ts
new file mode 100644 (file)
index 0000000..a76c3f2
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 { RouterState, RouteComponent, IndexRouteProps } from 'react-router';
+
+const routes = [
+  {
+    getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) {
+      import('./components/WebApiApp').then(i => callback(null, { component: (i as any).default }));
+    }
+  },
+  {
+    path: '**',
+    getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) {
+      import('./components/WebApiApp').then(i => callback(null, (i as any).default));
+    }
+  }
+];
+
+export default routes;
index 9c89ab5517f4ca1346250409bb688340ef613d7a..23e2c7184eb6c1ab06e6a30ce550204981f833e8 100644 (file)
@@ -107,10 +107,9 @@ const SelectListItemView = Backbone.View.extend({
 
   remove(postpone) {
     if (postpone) {
-      const that = this;
-      that.$el.addClass(this.model.get('selected') ? 'added' : 'removed');
-      setTimeout(function() {
-        Backbone.View.prototype.remove.call(that, arguments);
+      this.$el.addClass(this.model.get('selected') ? 'added' : 'removed');
+      setTimeout(() => {
+        Backbone.View.prototype.remove.call(this, arguments);
       }, 500);
     } else {
       Backbone.View.prototype.remove.call(this, arguments);
diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.js b/server/sonar-web/src/main/js/components/controls/DateInput.js
deleted file mode 100644 (file)
index 9da950a..0000000
+++ /dev/null
@@ -1,90 +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 $ from 'jquery';
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import { pick } from 'lodash';
-import './styles.css';
-
-export default class DateInput extends React.PureComponent {
-  static propTypes = {
-    className: PropTypes.string,
-    value: PropTypes.string,
-    format: PropTypes.string,
-    name: PropTypes.string,
-    placeholder: PropTypes.string,
-    onChange: PropTypes.func.isRequired
-  };
-
-  static defaultProps = {
-    value: '',
-    format: 'yy-mm-dd'
-  };
-
-  componentDidMount() {
-    this.attachDatePicker();
-  }
-
-  componentWillReceiveProps(nextProps) {
-    this.refs.input.value = nextProps.value;
-  }
-
-  handleChange() {
-    const { value } = this.refs.input;
-    this.props.onChange(value);
-  }
-
-  attachDatePicker() {
-    const opts = {
-      dateFormat: this.props.format,
-      changeMonth: true,
-      changeYear: true,
-      onSelect: this.handleChange.bind(this)
-    };
-
-    if ($.fn && $.fn.datepicker) {
-      $(this.refs.input).datepicker(opts);
-    }
-  }
-
-  render() {
-    const inputProps = pick(this.props, ['placeholder', 'name']);
-
-    /* eslint max-len: 0 */
-    return (
-      <span className={classNames('date-input-control', this.props.className)}>
-        <input
-          className="date-input-control-input"
-          ref="input"
-          type="text"
-          defaultValue={this.props.value}
-          readOnly={true}
-          {...inputProps}
-        />
-        <span className="date-input-control-icon">
-          <svg width="14" height="14" viewBox="0 0 16 16">
-            <path d="M5.5 6h2v2h-2V6zm3 0h2v2h-2V6zm3 0h2v2h-2V6zm-9 6h2v2h-2v-2zm3 0h2v2h-2v-2zm3 0h2v2h-2v-2zm-3-3h2v2h-2V9zm3 0h2v2h-2V9zm3 0h2v2h-2V9zm-9 0h2v2h-2V9zm11-9v1h-2V0h-7v1h-2V0h-2v16h15V0h-2zm1 15h-13V4h13v11z" />
-          </svg>
-        </span>
-      </span>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/controls/DateInput.tsx b/server/sonar-web/src/main/js/components/controls/DateInput.tsx
new file mode 100644 (file)
index 0000000..a1f4a4e
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * 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 $ from 'jquery';
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { pick } from 'lodash';
+import './styles.css';
+
+interface Props {
+  className?: string;
+  value?: string;
+  format?: string;
+  name: string;
+  placeholder: string;
+  onChange: (value: string) => void;
+}
+
+export default class DateInput extends React.PureComponent<Props> {
+  input: HTMLInputElement;
+
+  static defaultProps = {
+    value: '',
+    format: 'yy-mm-dd'
+  };
+
+  componentDidMount() {
+    this.attachDatePicker();
+  }
+
+  componentWillReceiveProps(nextProps: Props) {
+    if (nextProps.value != null) {
+      this.input.value = nextProps.value;
+    }
+  }
+
+  handleChange() {
+    const { value } = this.input;
+    this.props.onChange(value);
+  }
+
+  attachDatePicker() {
+    const opts = {
+      dateFormat: this.props.format,
+      changeMonth: true,
+      changeYear: true,
+      onSelect: this.handleChange.bind(this)
+    };
+
+    if ($.fn && ($.fn as any).datepicker) {
+      ($(this.refs.input) as any).datepicker(opts);
+    }
+  }
+
+  render() {
+    const inputProps = pick(this.props, ['placeholder', 'name']);
+
+    return (
+      <span className={classNames('date-input-control', this.props.className)}>
+        <input
+          className="date-input-control-input"
+          ref={node => (this.input = node as HTMLInputElement)}
+          type="text"
+          defaultValue={this.props.value}
+          readOnly={true}
+          {...inputProps}
+        />
+        <span className="date-input-control-icon">
+          <svg width="14" height="14" viewBox="0 0 16 16">
+            <path d="M5.5 6h2v2h-2V6zm3 0h2v2h-2V6zm3 0h2v2h-2V6zm-9 6h2v2h-2v-2zm3 0h2v2h-2v-2zm3 0h2v2h-2v-2zm-3-3h2v2h-2V9zm3 0h2v2h-2V9zm3 0h2v2h-2V9zm-9 0h2v2h-2V9zm11-9v1h-2V0h-7v1h-2V0h-2v16h15V0h-2zm1 15h-13V4h13v11z" />
+          </svg>
+        </span>
+      </span>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.js b/server/sonar-web/src/main/js/components/controls/ListFooter.js
deleted file mode 100644 (file)
index 24711f0..0000000
+++ /dev/null
@@ -1,70 +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 classNames from 'classnames';
-import React from 'react';
-import PropTypes from 'prop-types';
-import { translate, translateWithParameters } from '../../helpers/l10n';
-import { formatMeasure } from '../../helpers/measures';
-
-export default class ListFooter extends React.PureComponent {
-  static propTypes = {
-    count: PropTypes.number.isRequired,
-    total: PropTypes.number.isRequired,
-    loadMore: PropTypes.func,
-    ready: PropTypes.bool
-  };
-
-  static defaultProps = {
-    ready: true
-  };
-
-  canLoadMore() {
-    return typeof this.props.loadMore === 'function';
-  }
-
-  handleLoadMore = event => {
-    event.preventDefault();
-    event.target.blur();
-    this.props.loadMore();
-  };
-
-  render() {
-    const hasMore = this.props.total > this.props.count;
-    const loadMoreLink = (
-      <a className="spacer-left" href="#" onClick={this.handleLoadMore}>
-        {translate('show_more')}
-      </a>
-    );
-    const className = classNames('spacer-top note text-center', {
-      'new-loading': !this.props.ready
-    });
-
-    return (
-      <footer className={className}>
-        {translateWithParameters(
-          'x_of_y_shown',
-          formatMeasure(this.props.count, 'INT'),
-          formatMeasure(this.props.total, 'INT')
-        )}
-        {this.canLoadMore() && hasMore ? loadMoreLink : null}
-      </footer>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx
new file mode 100644 (file)
index 0000000..7d5df67
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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 classNames from 'classnames';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+import { formatMeasure } from '../../helpers/measures';
+
+interface Props {
+  count: number;
+  loadMore?: () => void;
+  ready?: boolean;
+  total: number;
+}
+
+export default function ListFooter({ ready = true, ...props }: Props) {
+  const handleLoadMore = (event: React.SyntheticEvent<HTMLElement>) => {
+    event.preventDefault();
+    event.currentTarget.blur();
+    if (props.loadMore) {
+      props.loadMore();
+    }
+  };
+
+  const hasMore = props.total > props.count;
+  const loadMoreLink = (
+    <a className="spacer-left" href="#" onClick={handleLoadMore}>
+      {translate('show_more')}
+    </a>
+  );
+  const className = classNames('spacer-top note text-center', {
+    'new-loading': !ready
+  });
+
+  return (
+    <footer className={className}>
+      {translateWithParameters(
+        'x_of_y_shown',
+        formatMeasure(props.count, 'INT', null),
+        formatMeasure(props.total, 'INT', null)
+      )}
+      {props.loadMore != null && hasMore ? loadMoreLink : null}
+    </footer>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.js b/server/sonar-web/src/main/js/components/controls/Tooltip.js
deleted file mode 100644 (file)
index 6819775..0000000
+++ /dev/null
@@ -1,39 +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 TooltipCore from 'rc-tooltip';
-
-export default class Tooltip extends React.PureComponent {
-  /*:: props: {
-    placement?: string
-  };
-*/
-
-  static defaultProps = {
-    placement: 'bottom'
-  };
-
-  render() {
-    return (
-      <TooltipCore destroyTooltipOnHide={true} placement={this.props.placement} {...this.props} />
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
new file mode 100644 (file)
index 0000000..f33a4f1
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 TooltipCore from 'rc-tooltip';
+
+interface Props {
+  overlay: React.ReactNode;
+  placement?: string;
+  [attr: string]: any;
+}
+
+export default function Tooltip(props: Props) {
+  return <TooltipCore destroyTooltipOnHide={true} placement={props.placement} {...props} />;
+}
+
+(Tooltip as React.StatelessComponent).defaultProps = {
+  placement: 'bottom'
+};
diff --git a/server/sonar-web/src/main/js/components/icons-components/BugIcon.js b/server/sonar-web/src/main/js/components/icons-components/BugIcon.js
deleted file mode 100644 (file)
index 48726f8..0000000
+++ /dev/null
@@ -1,42 +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';
-
-/*::
-type Props = { className?: string, size?: number };
-*/
-
-export default function BugIcon({ className, size = 16 } /*: Props */) {
-  /* eslint-disable max-len */
-  return (
-    <svg
-      className={className}
-      xmlns="http://www.w3.org/2000/svg"
-      viewBox="0 0 16 16"
-      width={size}
-      height={size}>
-      <path
-        style={{ fill: 'currentColor' }}
-        d="M11 9h1.3l.5.8.8-.5-.8-1.3H11v-.3l2-2.3V3h-1v2l-1 1.2V5c-.1-.8-.7-1.5-1.4-1.9L11 1.8l-.7-.7-1.8 1.6-1.8-1.6-.7.7 1.5 1.3C6.7 3.5 6.1 4.2 6 5v1.1L5 5V3H4v2.3l2 2.3V8H4.2l-.7 1.2.8.5.4-.7H6v.3l-2 1.9V14h1v-2.4l1-1C6 12 7.1 13 8.4 13h.8c.7 0 1.4-.3 1.8-.9.3-.4.3-.9.2-1.4l.9.9V14h1v-2.8l-2-1.9V9zm-2 2H8V6h1v5z"
-      />
-    </svg>
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/icons-components/BugIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/BugIcon.tsx
new file mode 100644 (file)
index 0000000..eb5f504
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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';
+
+interface Props {
+  className?: string;
+  size?: number;
+}
+
+export default function BugIcon({ className, size = 16 }: Props) {
+  /* eslint-disable max-len */
+  return (
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width={size}
+      height={size}>
+      <path
+        style={{ fill: 'currentColor' }}
+        d="M11 9h1.3l.5.8.8-.5-.8-1.3H11v-.3l2-2.3V3h-1v2l-1 1.2V5c-.1-.8-.7-1.5-1.4-1.9L11 1.8l-.7-.7-1.8 1.6-1.8-1.6-.7.7 1.5 1.3C6.7 3.5 6.1 4.2 6 5v1.1L5 5V3H4v2.3l2 2.3V8H4.2l-.7 1.2.8.5.4-.7H6v.3l-2 1.9V14h1v-2.4l1-1C6 12 7.1 13 8.4 13h.8c.7 0 1.4-.3 1.8-.9.3-.4.3-.9.2-1.4l.9.9V14h1v-2.8l-2-1.9V9zm-2 2H8V6h1v5z"
+      />
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/CodeSmellIcon.js b/server/sonar-web/src/main/js/components/icons-components/CodeSmellIcon.js
deleted file mode 100644 (file)
index 108e420..0000000
+++ /dev/null
@@ -1,42 +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';
-
-/*::
-type Props = { className?: string, size?: number };
-*/
-
-export default function CodeSmellIcon({ className, size = 16 } /*: Props */) {
-  /* eslint-disable max-len */
-  return (
-    <svg
-      className={className}
-      xmlns="http://www.w3.org/2000/svg"
-      viewBox="0 0 16 16"
-      width={size}
-      height={size}>
-      <path
-        style={{ fill: 'currentColor' }}
-        d="M8 2C4.7 2 2 4.7 2 8s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zm-.5 5.5h.9v.9h-.9v-.9zm-3.8.2c-.1 0-.2-.1-.2-.2 0-.4.1-1.2.6-2S5.3 4.2 5.6 4c.2 0 .3 0 .3.1l1.3 2.3c0 .1 0 .2-.1.2-.1.2-.2.3-.3.5-.1.2-.2.4-.2.5 0 .1-.1.2-.2.2l-2.7-.1zM9.9 12c-.3.2-1.1.5-2 .5-.9 0-1.7-.3-2-.5-.1 0-.1-.2-.1-.3l1.3-2.3c0-.1.1-.1.2-.1.2.1.3.1.5.1s.4 0 .5-.1c.1 0 .2 0 .2.1l1.3 2.3c.2.2.2.3.1.3zm2.5-4.1L9.7 8c-.1 0-.2-.1-.2-.2 0-.2-.1-.4-.2-.5 0-.1-.2-.3-.3-.4-.1 0-.1-.1-.1-.2l1.3-2.3c.1-.1.2-.1.3-.1.3.2 1 .7 1.5 1.5s.6 1.6.6 2c0 0-.1.1-.2.1z"
-      />
-    </svg>
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/icons-components/CodeSmellIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/CodeSmellIcon.tsx
new file mode 100644 (file)
index 0000000..767a69f
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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';
+
+interface Props {
+  className?: string;
+  size?: number;
+}
+
+export default function CodeSmellIcon({ className, size = 16 }: Props) {
+  /* eslint-disable max-len */
+  return (
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width={size}
+      height={size}>
+      <path
+        style={{ fill: 'currentColor' }}
+        d="M8 2C4.7 2 2 4.7 2 8s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zm-.5 5.5h.9v.9h-.9v-.9zm-3.8.2c-.1 0-.2-.1-.2-.2 0-.4.1-1.2.6-2S5.3 4.2 5.6 4c.2 0 .3 0 .3.1l1.3 2.3c0 .1 0 .2-.1.2-.1.2-.2.3-.3.5-.1.2-.2.4-.2.5 0 .1-.1.2-.2.2l-2.7-.1zM9.9 12c-.3.2-1.1.5-2 .5-.9 0-1.7-.3-2-.5-.1 0-.1-.2-.1-.3l1.3-2.3c0-.1.1-.1.2-.1.2.1.3.1.5.1s.4 0 .5-.1c.1 0 .2 0 .2.1l1.3 2.3c.2.2.2.3.1.3zm2.5-4.1L9.7 8c-.1 0-.2-.1-.2-.2 0-.2-.1-.4-.2-.5 0-.1-.2-.3-.3-.4-.1 0-.1-.1-.1-.2l1.3-2.3c.1-.1.2-.1.3-.1.3.2 1 .7 1.5 1.5s.6 1.6.6 2c0 0-.1.1-.2.1z"
+      />
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/icons-components/VulnerabilityIcon.js b/server/sonar-web/src/main/js/components/icons-components/VulnerabilityIcon.js
deleted file mode 100644 (file)
index 1d3b5b1..0000000
+++ /dev/null
@@ -1,42 +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';
-
-/*::
-type Props = { className?: string, size?: number };
-*/
-
-export default function VulnerabilityIcon({ className, size = 16 } /*: Props */) {
-  /* eslint-disable max-len */
-  return (
-    <svg
-      className={className}
-      xmlns="http://www.w3.org/2000/svg"
-      viewBox="0 0 16 16"
-      width={size}
-      height={size}>
-      <path
-        style={{ fill: 'currentColor' }}
-        d="M10.8 5H6V3.9a2.28 2.28 0 0 1 2-2.5 2.22 2.22 0 0 1 1.8 1.2.48.48 0 0 0 .7.2.48.48 0 0 0 .2-.7A3 3 0 0 0 8 .4a3.34 3.34 0 0 0-3 3.5v1.2a2.16 2.16 0 0 0-2 2.1v4.4a2.22 2.22 0 0 0 2.2 2.2h5.6a2.22 2.22 0 0 0 2.2-2.2V7.2A2.22 2.22 0 0 0 10.8 5zm-2.2 5.5v1.2H7.4v-1.2a1.66 1.66 0 0 1-1.1-1.6A1.75 1.75 0 0 1 8 7.2a1.71 1.71 0 0 1 .6 3.3z"
-      />
-    </svg>
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/icons-components/VulnerabilityIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/VulnerabilityIcon.tsx
new file mode 100644 (file)
index 0000000..f0fbe93
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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';
+
+interface Props {
+  className?: string;
+  size?: number;
+}
+
+export default function VulnerabilityIcon({ className, size = 16 }: Props) {
+  /* eslint-disable max-len */
+  return (
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width={size}
+      height={size}>
+      <path
+        style={{ fill: 'currentColor' }}
+        d="M10.8 5H6V3.9a2.28 2.28 0 0 1 2-2.5 2.22 2.22 0 0 1 1.8 1.2.48.48 0 0 0 .7.2.48.48 0 0 0 .2-.7A3 3 0 0 0 8 .4a3.34 3.34 0 0 0-3 3.5v1.2a2.16 2.16 0 0 0-2 2.1v4.4a2.22 2.22 0 0 0 2.2 2.2h5.6a2.22 2.22 0 0 0 2.2-2.2V7.2A2.22 2.22 0 0 0 10.8 5zm-2.2 5.5v1.2H7.4v-1.2a1.66 1.66 0 0 1-1.1-1.6A1.75 1.75 0 0 1 8 7.2a1.71 1.71 0 0 1 .6 3.3z"
+      />
+    </svg>
+  );
+}
diff --git a/server/sonar-web/src/main/js/components/shared/QualifierIcon.js b/server/sonar-web/src/main/js/components/shared/QualifierIcon.js
deleted file mode 100644 (file)
index e00dc8a..0000000
+++ /dev/null
@@ -1,45 +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 classNames from 'classnames';
-
-/*::
-type Props = {
-  className?: string,
-  qualifier: ?string
-};
-*/
-
-export default class QualifierIcon extends React.PureComponent {
-  /*:: props: Props; */
-
-  render() {
-    if (!this.props.qualifier) {
-      return null;
-    }
-
-    const className = classNames(
-      'icon-qualifier-' + this.props.qualifier.toLowerCase(),
-      this.props.className
-    );
-
-    return <i className={className} />;
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/shared/QualifierIcon.tsx b/server/sonar-web/src/main/js/components/shared/QualifierIcon.tsx
new file mode 100644 (file)
index 0000000..802d90c
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 classNames from 'classnames';
+
+interface Props {
+  className?: string;
+  qualifier: string | null;
+}
+
+export default function QualifierIcon(props: Props) {
+  if (!props.qualifier) {
+    return null;
+  }
+
+  const className = classNames('icon-qualifier-' + props.qualifier.toLowerCase(), props.className);
+
+  return <i className={className} />;
+}
diff --git a/server/sonar-web/src/main/js/components/ui/IssueTypeIcon.js b/server/sonar-web/src/main/js/components/ui/IssueTypeIcon.js
deleted file mode 100644 (file)
index 83feca0..0000000
+++ /dev/null
@@ -1,65 +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 BugIcon from '../icons-components/BugIcon';
-import VulnerabilityIcon from '../icons-components/VulnerabilityIcon';
-import CodeSmellIcon from '../icons-components/CodeSmellIcon';
-
-export default class IssueTypeIcon extends React.PureComponent {
-  /*:: props: {
-    className?: string,
-    query: string
-  };
-*/
-
-  renderIcon() {
-    switch (this.props.query.toLowerCase()) {
-      case 'bug':
-      case 'bugs':
-      case 'new_bugs':
-        return <BugIcon />;
-      case 'vulnerability':
-      case 'vulnerabilities':
-      case 'new_vulnerabilities':
-        return <VulnerabilityIcon />;
-      case 'code_smell':
-      case 'code_smells':
-      case 'new_code_smells':
-        return <CodeSmellIcon />;
-      default:
-        return null;
-    }
-  }
-
-  render() {
-    const icon = this.renderIcon();
-
-    if (!icon) {
-      return null;
-    }
-
-    return this.props.className
-      ? <span className={this.props.className}>
-          {icon}
-        </span>
-      : icon;
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/ui/IssueTypeIcon.tsx b/server/sonar-web/src/main/js/components/ui/IssueTypeIcon.tsx
new file mode 100644 (file)
index 0000000..63aae3c
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 BugIcon from '../icons-components/BugIcon';
+import VulnerabilityIcon from '../icons-components/VulnerabilityIcon';
+import CodeSmellIcon from '../icons-components/CodeSmellIcon';
+
+interface Props {
+  className?: string;
+  query: string;
+}
+
+export default function IssueTypeIcon(props: Props) {
+  let icon;
+
+  switch (props.query.toLowerCase()) {
+    case 'bug':
+    case 'bugs':
+    case 'new_bugs':
+      icon = <BugIcon />;
+      break;
+    case 'vulnerability':
+    case 'vulnerabilities':
+    case 'new_vulnerabilities':
+      icon = <VulnerabilityIcon />;
+      break;
+    case 'code_smell':
+    case 'code_smells':
+    case 'new_code_smells':
+      icon = <CodeSmellIcon />;
+      break;
+  }
+
+  if (!icon) {
+    return null;
+  }
+
+  return props.className
+    ? <span className={props.className}>
+        {icon}
+      </span>
+    : icon;
+}
diff --git a/server/sonar-web/src/main/js/components/ui/Level.js b/server/sonar-web/src/main/js/components/ui/Level.js
deleted file mode 100644 (file)
index 2ba079f..0000000
+++ /dev/null
@@ -1,51 +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 classNames from 'classnames';
-import { formatMeasure } from '../../helpers/measures';
-import './Level.css';
-
-export default class Level extends React.PureComponent {
-  static propTypes = {
-    className: PropTypes.string,
-    level: PropTypes.oneOf(['ERROR', 'WARN', 'OK', 'NONE']).isRequired,
-    small: PropTypes.bool,
-    muted: PropTypes.bool
-  };
-
-  static defaultProps = {
-    small: false,
-    muted: false
-  };
-
-  render() {
-    const formatted = formatMeasure(this.props.level, 'LEVEL');
-    const className = classNames(this.props.className, 'level', 'level-' + this.props.level, {
-      'level-small': this.props.small,
-      'level-muted': this.props.muted
-    });
-    return (
-      <span className={className}>
-        {formatted}
-      </span>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/components/ui/Level.tsx b/server/sonar-web/src/main/js/components/ui/Level.tsx
new file mode 100644 (file)
index 0000000..b549569
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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 classNames from 'classnames';
+import { formatMeasure } from '../../helpers/measures';
+import './Level.css';
+
+interface Props {
+  className?: string;
+  level: string;
+  small?: boolean;
+  muted?: boolean;
+}
+
+export default function Level(props: Props) {
+  const formatted = formatMeasure(props.level, 'LEVEL', null);
+  const className = classNames(props.className, 'level', 'level-' + props.level, {
+    'level-small': props.small,
+    'level-muted': props.muted
+  });
+  return (
+    <span className={className}>
+      {formatted}
+    </span>
+  );
+}
diff --git a/server/sonar-web/src/main/js/typings/rc-tooltip.d.ts b/server/sonar-web/src/main/js/typings/rc-tooltip.d.ts
new file mode 100644 (file)
index 0000000..b10195e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+declare module 'rc-tooltip' {
+  export type Trigger = 'hover' | 'click' | 'focus';
+  export type Placement =
+    | 'left'
+    | 'right'
+    | 'top'
+    | 'bottom'
+    | 'topLeft'
+    | 'topRight'
+    | 'bottomLeft'
+    | 'bottomRight';
+
+  export interface Props extends React.Props<any> {
+    overlayClassName?: string;
+    trigger?: Trigger[];
+    mouseEnterDelay?: number;
+    mouseLeaveDelay?: number;
+    overlayStyle?: React.CSSProperties;
+    prefixCls?: string;
+    transitionName?: string;
+    onVisibleChange?: () => void;
+    visible?: boolean;
+    defaultVisible?: boolean;
+    placement?: Placement | Object;
+    align?: Object;
+    onPopupAlign?: (popupDomNode: Element, align: Object) => void;
+    overlay: React.ReactNode;
+    arrowContent?: React.ReactNode;
+    getTooltipContainer?: () => Element;
+    destroyTooltipOnHide?: boolean;
+  }
+
+  // the next line is crucial, it is absent in the original typings
+  export default class Tooltip extends React.Component<Props> {}
+}
index 6f5b468c4b0d60254a1b698bc0f331767272a74a..427192d12e754db536c580000009eb0adc5ed893 100644 (file)
@@ -9,7 +9,7 @@
     "target": "es5",
     "jsx": "react",
     "lib": ["es2017", "dom"],
-    "module": "es2015",
+    "module": "esnext",
     "moduleResolution": "node",
     "typeRoots": ["./src/main/js/typings", "./node_modules/@types"]
   },
index 75d358da6cb4098fe58acef3fdc7eede4ae35363..b3a14ca74f1f6753558a1e3119a16c2132f77644 100644 (file)
     "@types/cheerio" "*"
     "@types/react" "*"
 
+"@types/escape-html@0.0.19":
+  version "0.0.19"
+  resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-0.0.19.tgz#595ff7bd7ee510af54517819de24abdcea6f3507"
+
 "@types/history@^3":
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/@types/history/-/history-3.2.1.tgz#0039ab0e0be2a0cc22bac171d27a44588103d123"
   version "20.0.7"
   resolved "https://registry.yarnpkg.com/@types/jest/-/jest-20.0.7.tgz#39cd215db8bda03928dceb933a1e63eb2cbd210e"
 
+"@types/jquery@3.2.11":
+  version "3.2.11"
+  resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.2.11.tgz#9119f91bb103b16ae8c4375b019a9b341b409f50"
+
 "@types/lodash@4.14.73":
   version "4.14.73"
   resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.73.tgz#9837e47db8643ba5bcef2c7921f37d90f9c24213"
     "@types/node" "*"
     "@types/react" "*"
 
+"@types/react-helmet@5.0.3":
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.3.tgz#614e706cb73120936c7c067404809f8c2f1a840c"
+  dependencies:
+    "@types/react" "*"
+
+"@types/react-modal@2.2.0":
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@types/react-modal/-/react-modal-2.2.0.tgz#e92bb8454e53030581f263e3fb7e7d27e3eb85b8"
+  dependencies:
+    "@types/react" "*"
+
 "@types/react-redux@5.0.3":
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-5.0.3.tgz#cd68de0c49c516b940fdc6f688535596b5d6eca4"
     "@types/history" "^3"
     "@types/react" "*"
 
+"@types/react-select@1.0.51":
+  version "1.0.51"
+  resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-1.0.51.tgz#47e7787b068c34395251e95a0981cff8034eddcc"
+  dependencies:
+    "@types/react" "*"
+
 "@types/react@*", "@types/react@16.0.2":
   version "16.0.2"
   resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.2.tgz#0b31a73cdde6272b719e5b05a7df6d1e2654a804"