]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8369 SONAR-8722 Sanitize page titles
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 9 May 2017 13:43:19 +0000 (15:43 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 10 May 2017 09:56:20 +0000 (11:56 +0200)
63 files changed:
server/sonar-web/src/main/js/app/components/AdminContainer.js
server/sonar-web/src/main/js/app/components/App.js
server/sonar-web/src/main/js/app/components/DefaultHelmetContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/ProjectContainer.js
server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.js
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap
server/sonar-web/src/main/js/app/utils/startReactApp.js
server/sonar-web/src/main/js/apps/account/components/Account.js
server/sonar-web/src/main/js/apps/account/components/Security.js
server/sonar-web/src/main/js/apps/account/notifications/Notifications.js
server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap
server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js
server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js
server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
server/sonar-web/src/main/js/apps/code/components/App.js
server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js
server/sonar-web/src/main/js/apps/component-measures/app/App.js
server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js
server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js
server/sonar-web/src/main/js/apps/issues/components/App.js
server/sonar-web/src/main/js/apps/issues/utils.js
server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroups.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js
server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap
server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap
server/sonar-web/src/main/js/apps/permission-templates/components/App.js
server/sonar-web/src/main/js/apps/permission-templates/components/Home.js
server/sonar-web/src/main/js/apps/permission-templates/components/Template.js
server/sonar-web/src/main/js/apps/permissions/global/components/App.js
server/sonar-web/src/main/js/apps/permissions/project/components/App.js
server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js
server/sonar-web/src/main/js/apps/project-admin/key/Key.js
server/sonar-web/src/main/js/apps/project-admin/links/Links.js
server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js
server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
server/sonar-web/src/main/js/apps/projects-admin/main.js
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
server/sonar-web/src/main/js/apps/projects/components/App.js
server/sonar-web/src/main/js/apps/quality-gates/components/Details.js
server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js
server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js
server/sonar-web/src/main/js/apps/settings/components/App.js
server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.js
server/sonar-web/src/main/js/apps/settings/licenses/LicensesApp.js
server/sonar-web/src/main/js/apps/settings/serverId/ServerIdApp.js
server/sonar-web/src/main/js/apps/system/main.js
server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js
server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js
server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.js
server/sonar-web/src/main/js/components/common/OrganizationHelmet.js [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 05d64849395d2010dd10a4f4bef25deedb86ae0b..da8c9376dbc2c503b0a9800a30b2f1d0a8777ee0 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import SettingsNav from './nav/settings/SettingsNav';
 import { getCurrentUser, getAppState } from '../../store/rootReducer';
@@ -25,6 +26,7 @@ import { isUserAdmin } from '../../helpers/users';
 import { onFail } from '../../store/rootActions';
 import { getSettingsNavigation } from '../../api/nav';
 import { setAdminPages } from '../../store/appState/duck';
+import { translate } from '../../helpers/l10n';
 
 class AdminContainer extends React.PureComponent {
   componentDidMount() {
@@ -50,6 +52,7 @@ class AdminContainer extends React.PureComponent {
 
     return (
       <div>
+        <Helmet title={translate('layout.settings')} />
         <SettingsNav location={this.props.location} extensions={this.props.adminPages} />
         {this.props.children}
       </div>
index 8f4d06a1078a7db59b41740812dee376be510bdc..ea68bd92461cd9dc75faa8b92cbb58b119ad87a5 100644 (file)
@@ -61,7 +61,6 @@ class App extends React.PureComponent {
     if (this.state.loading) {
       return <GlobalLoading />;
     }
-
     return this.props.children;
   }
 }
diff --git a/server/sonar-web/src/main/js/app/components/DefaultHelmetContainer.js b/server/sonar-web/src/main/js/app/components/DefaultHelmetContainer.js
new file mode 100644 (file)
index 0000000..6006da4
--- /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 React from 'react';
+import Helmet from 'react-helmet';
+
+export default function DefaultHelmetContainer({ children }) {
+  return (
+    <div>
+      <Helmet defaultTitle="SonarQube" />
+      {children}
+    </div>
+  );
+}
index d1cf872b14f17f0a79245e238867036f325d408f..8df53c24ff356c54e3868cac764dec72fa81ce50 100644 (file)
@@ -37,6 +37,7 @@ class ProjectContainer extends React.PureComponent {
     },
     project?: {
       configuration: {},
+      name: string,
       qualifier: string
     },
     fetchProject: string => Promise<*>,
@@ -68,27 +69,23 @@ class ProjectContainer extends React.PureComponent {
   };
 
   render() {
+    const { project } = this.props;
+
     // check `breadcrumbs` to be sure that /api/navigation/component has been already called
-    if (!this.props.project || this.props.project.breadcrumbs == null) {
+    if (!project || project.breadcrumbs == null) {
       return null;
     }
 
-    const isFile = ['FIL', 'UTS'].includes(this.props.project.qualifier);
-
-    // $FlowFixMe `this.props.project` is always defined at this point
-    const configuration = this.props.project.configuration || {};
+    const isFile = ['FIL', 'UTS'].includes(project.qualifier);
+    const configuration = project.configuration || {};
 
     return (
       <div>
         {!isFile &&
-          <ComponentNav
-            component={this.props.project}
-            conf={configuration}
-            location={this.props.location}
-          />}
+          <ComponentNav component={project} conf={configuration} location={this.props.location} />}
         {/* $FlowFixMe */}
         {React.cloneElement(this.props.children, {
-          component: this.props.project,
+          component: project,
           onComponentChange: this.handleProjectChange
         })}
       </div>
index a7f47a2fa3aa71d142a41004fb552a363cddae5d..4e6da93deeab4a871c63e5afb063b5f3cb858481 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import GlobalPageExtension from './GlobalPageExtension';
+import { translate } from '../../../helpers/l10n';
 
 export default function PortfoliosPage(props: Object) {
   return (
-    <GlobalPageExtension
-      location={props.location}
-      params={{ pluginKey: 'governance', extensionKey: 'portfolios' }}
-    />
+    <div>
+      <Helmet title={translate('portfolios.page')} />
+      <GlobalPageExtension
+        location={props.location}
+        params={{ pluginKey: 'governance', extensionKey: 'portfolios' }}
+      />
+    </div>
   );
 }
index 9c02a29013d0d6106f3cd8a2ce3c341694d2c080..42c3efaa2321e9cbee4bbc8c745746ab79da26f0 100644 (file)
@@ -22,6 +22,7 @@ import { connect } from 'react-redux';
 import { Link } from 'react-router';
 import QualifierIcon from '../../../../components/shared/QualifierIcon';
 import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../store/rootReducer';
+import OrganizationHelmet from '../../../../components/common/OrganizationHelmet';
 import OrganizationLink from '../../../../components/ui/OrganizationLink';
 import PrivateBadge from '../../../../components/common/PrivateBadge';
 import { collapsePath, limitComponentName } from '../../../../helpers/path';
@@ -35,7 +36,7 @@ class ComponentNavBreadcrumbs extends React.PureComponent {
   };
 
   render() {
-    const { breadcrumbs, organization, shouldOrganizationBeDisplayed } = this.props;
+    const { breadcrumbs, component, organization, shouldOrganizationBeDisplayed } = this.props;
 
     if (!breadcrumbs) {
       return null;
@@ -70,6 +71,10 @@ class ComponentNavBreadcrumbs extends React.PureComponent {
 
     return (
       <h2 className="navbar-context-title">
+        <OrganizationHelmet
+          title={component.name}
+          organization={displayOrganization ? organization : null}
+        />
         {displayOrganization &&
           <span>
             <span className="navbar-context-title-qualifier little-spacer-right">
@@ -81,7 +86,7 @@ class ComponentNavBreadcrumbs extends React.PureComponent {
             <span className="slash-separator" />
           </span>}
         {items}
-        {this.props.component.visibility === 'private' && <PrivateBadge className="spacer-left" />}
+        {component.visibility === 'private' && <PrivateBadge className="spacer-left" />}
       </h2>
     );
   }
index 02834a1f242866cfa93d5b88b489b4f259cfa3e3..385012e7de0a4e867c1208496a86b89f232fee4d 100644 (file)
@@ -4,6 +4,10 @@ exports[`should not render breadcrumbs with one element 1`] = `
 <h2
   className="navbar-context-title"
 >
+  <OrganizationHelmet
+    organization={null}
+    title="My Project"
+  />
   <span>
     <span
       className="navbar-context-title-qualifier little-spacer-right"
@@ -38,6 +42,15 @@ exports[`should render organization 1`] = `
 <h2
   className="navbar-context-title"
 >
+  <OrganizationHelmet
+    organization={
+      Object {
+        "key": "foo",
+        "name": "The Foo Organization",
+      }
+    }
+    title="My Project"
+  />
   <span>
     <span
       className="navbar-context-title-qualifier little-spacer-right"
index fa8714220bfe441a12d55efc0122a0eff5cd0d2b..5b4a0d37e1563fe8f72796020690942c35969384 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import { render } from 'react-dom';
 import { Router, Route, IndexRoute, Redirect } from 'react-router';
 import { Provider } from 'react-redux';
+import DefaultHelmetContainer from '../components/DefaultHelmetContainer';
 import LocalizationContainer from '../components/LocalizationContainer';
 import MigrationContainer from '../components/MigrationContainer';
 import App from '../components/App';
@@ -127,82 +128,87 @@ const startReactApp = () => {
 
         <Route path="markdown/help" component={MarkdownHelp} />
 
-        <Route component={LocalizationContainer}>
-          <Route component={SimpleContainer}>
-            <Route path="maintenance">{maintenanceRoutes}</Route>
-            <Route path="setup">{setupRoutes}</Route>
-          </Route>
-
-          <Route component={MigrationContainer}>
-            <Route component={SimpleSessionsContainer}>
-              <Route path="/sessions">{sessionsRoutes}</Route>
+        <Route component={DefaultHelmetContainer}>
+          <Route component={LocalizationContainer}>
+            <Route component={SimpleContainer}>
+              <Route path="maintenance">{maintenanceRoutes}</Route>
+              <Route path="setup">{setupRoutes}</Route>
             </Route>
 
-            <Route path="/" component={App}>
-
-              <IndexRoute component={Landing} />
-
-              <Route component={GlobalContainer}>
-                <Route path="about" childRoutes={aboutRoutes} />
-                <Route path="account" childRoutes={accountRoutes} />
-                <Route path="coding_rules" childRoutes={codingRulesRoutes} />
-                <Route path="component" childRoutes={componentRoutes} />
-                <Route path="extension/:pluginKey/:extensionKey" component={GlobalPageExtension} />
-                <Route path="issues" childRoutes={issuesRoutes} />
-                <Route path="organizations" childRoutes={organizationsRoutes} />
-                <Route path="projects" childRoutes={projectsRoutes} />
-                <Route path="quality_gates" childRoutes={qualityGatesRoutes} />
-                <Route path="portfolios" component={PortfoliosPage} />
-                <Route path="profiles" childRoutes={qualityProfilesRoutes} />
-                <Route path="web_api" childRoutes={webAPIRoutes} />
-
-                <Route component={ProjectContainer}>
-                  <Route path="code" childRoutes={codeRoutes} />
-                  <Route path="component_measures" childRoutes={componentMeasuresRoutes} />
-                  <Route path="custom_measures" childRoutes={customMeasuresRoutes} />
-                  <Route path="dashboard" childRoutes={overviewRoutes} />
-                  <Route path="project">
-                    <Route path="activity" childRoutes={projectActivityRoutes} />
-                    <Route path="admin" component={ProjectAdminContainer}>
+            <Route component={MigrationContainer}>
+              <Route component={SimpleSessionsContainer}>
+                <Route path="/sessions">{sessionsRoutes}</Route>
+              </Route>
+
+              <Route path="/" component={App}>
+
+                <IndexRoute component={Landing} />
+
+                <Route component={GlobalContainer}>
+                  <Route path="about" childRoutes={aboutRoutes} />
+                  <Route path="account" childRoutes={accountRoutes} />
+                  <Route path="coding_rules" childRoutes={codingRulesRoutes} />
+                  <Route path="component" childRoutes={componentRoutes} />
+                  <Route
+                    path="extension/:pluginKey/:extensionKey"
+                    component={GlobalPageExtension}
+                  />
+                  <Route path="issues" childRoutes={issuesRoutes} />
+                  <Route path="organizations" childRoutes={organizationsRoutes} />
+                  <Route path="projects" childRoutes={projectsRoutes} />
+                  <Route path="quality_gates" childRoutes={qualityGatesRoutes} />
+                  <Route path="portfolios" component={PortfoliosPage} />
+                  <Route path="profiles" childRoutes={qualityProfilesRoutes} />
+                  <Route path="web_api" childRoutes={webAPIRoutes} />
+
+                  <Route component={ProjectContainer}>
+                    <Route path="code" childRoutes={codeRoutes} />
+                    <Route path="component_measures" childRoutes={componentMeasuresRoutes} />
+                    <Route path="custom_measures" childRoutes={customMeasuresRoutes} />
+                    <Route path="dashboard" childRoutes={overviewRoutes} />
+                    <Route path="project">
+                      <Route path="activity" childRoutes={projectActivityRoutes} />
+                      <Route path="admin" component={ProjectAdminContainer}>
+                        <Route
+                          path="extension/:pluginKey/:extensionKey"
+                          component={ProjectAdminPageExtension}
+                        />
+                      </Route>
+                      <Redirect from="extension/governance/governance" to="/view" />
                       <Route
                         path="extension/:pluginKey/:extensionKey"
-                        component={ProjectAdminPageExtension}
+                        component={ProjectPageExtension}
                       />
+                      <Route path="background_tasks" childRoutes={backgroundTasksRoutes} />
+                      <Route path="issues" childRoutes={issuesRoutes} />
+                      <Route path="settings" childRoutes={settingsRoutes} />
+                      {projectAdminRoutes}
                     </Route>
-                    <Redirect from="extension/governance/governance" to="/view" />
+                    <Route path="project_roles" childRoutes={projectPermissionsRoutes} />
+                    <Route path="view" component={ViewDashboard} />
+                  </Route>
+
+                  <Route component={AdminContainer}>
                     <Route
-                      path="extension/:pluginKey/:extensionKey"
-                      component={ProjectPageExtension}
+                      path="admin/extension/:pluginKey/:extensionKey"
+                      component={GlobalAdminPageExtension}
                     />
                     <Route path="background_tasks" childRoutes={backgroundTasksRoutes} />
-                    <Route path="issues" childRoutes={issuesRoutes} />
+                    <Route path="groups" childRoutes={groupsRoutes} />
+                    <Route path="metrics" childRoutes={metricsRoutes} />
+                    <Route path="permission_templates" childRoutes={permissionTemplatesRoutes} />
+                    <Route path="projects_admin" childRoutes={projectsAdminRoutes} />
+                    <Route path="roles/global" childRoutes={globalPermissionsRoutes} />
                     <Route path="settings" childRoutes={settingsRoutes} />
-                    {projectAdminRoutes}
+                    <Route path="system" childRoutes={systemRoutes} />
+                    <Route path="updatecenter" childRoutes={updateCenterRoutes} />
+                    <Route path="users" childRoutes={usersRoutes} />
                   </Route>
-                  <Route path="project_roles" childRoutes={projectPermissionsRoutes} />
-                  <Route path="view" component={ViewDashboard} />
                 </Route>
 
-                <Route component={AdminContainer}>
-                  <Route
-                    path="admin/extension/:pluginKey/:extensionKey"
-                    component={GlobalAdminPageExtension}
-                  />
-                  <Route path="background_tasks" childRoutes={backgroundTasksRoutes} />
-                  <Route path="groups" childRoutes={groupsRoutes} />
-                  <Route path="metrics" childRoutes={metricsRoutes} />
-                  <Route path="permission_templates" childRoutes={permissionTemplatesRoutes} />
-                  <Route path="projects_admin" childRoutes={projectsAdminRoutes} />
-                  <Route path="roles/global" childRoutes={globalPermissionsRoutes} />
-                  <Route path="settings" childRoutes={settingsRoutes} />
-                  <Route path="system" childRoutes={systemRoutes} />
-                  <Route path="updatecenter" childRoutes={updateCenterRoutes} />
-                  <Route path="users" childRoutes={usersRoutes} />
-                </Route>
+                <Route path="not_found" component={NotFound} />
+                <Route path="*" component={NotFound} />
               </Route>
-
-              <Route path="not_found" component={NotFound} />
-              <Route path="*" component={NotFound} />
             </Route>
           </Route>
         </Route>
index aed774b7bac27c004a04f36959235cdab9ef26a9..f9918546f6e9e1d0faeb37639343093efe4112f4 100644 (file)
  */
 import React from 'react';
 import { connect } from 'react-redux';
+import Helmet from 'react-helmet';
 import Nav from './Nav';
 import UserCard from './UserCard';
 import { getCurrentUser, areThereCustomOrganizations } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
 import '../account.css';
 
@@ -39,8 +41,10 @@ class Account extends React.PureComponent {
       return null;
     }
 
+    const title = translate('my_account.page');
     return (
       <div id="account-page">
+        <Helmet defaultTitle={title} titleTemplate={'%s - ' + title} />
         <header className="account-header">
           <div className="account-container clearfix">
             <UserCard user={currentUser} />
index f444538f1ee92337152da44d45468a5186576c8d..d52a3b6e1eaf316f7ed0af441120451a93c64288 100644 (file)
@@ -18,8 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
-import { connect } from 'react-redux';
 import Helmet from 'react-helmet';
+import { connect } from 'react-redux';
 import Password from './Password';
 import Tokens from './Tokens';
 import { translate } from '../../../helpers/l10n';
@@ -28,11 +28,9 @@ import { getCurrentUser } from '../../../store/rootReducer';
 function Security(props) {
   const { user } = props;
 
-  const title = translate('my_account.page') + ' - ' + translate('my_account.security');
-
   return (
     <div className="account-body account-container">
-      <Helmet title={title} titleTemplate="SonarQube - %s" />
+      <Helmet title={translate('my_account.security')} />
 
       <Tokens user={user} />
 
index 6f27b3141dc4b5928bba1ffa95792ddac6c92e73..b8c596cba1d97c792eaf5f5f5fbadfdf5d85a0c8 100644 (file)
@@ -36,11 +36,9 @@ class Notifications extends React.PureComponent {
   }
 
   render() {
-    const title = translate('my_account.page') + ' - ' + translate('my_account.notifications');
-
     return (
       <div className="account-body account-container">
-        <Helmet title={title} titleTemplate="SonarQube - %s" />
+        <Helmet title={translate('my_account.notifications')} />
 
         <p className="big-spacer-bottom">
           {translate('notification.dispatcher.information')}
index 51c289290b80815284c0ab3f0747c368e5b1db23..66eea69153b52892fb1b75178f4cd4bb4e302670 100644 (file)
@@ -5,8 +5,7 @@ exports[`should match snapshot 1`] = `
   className="account-body account-container"
 >
   <HelmetWrapper
-    title="my_account.page - my_account.notifications"
-    titleTemplate="SonarQube - %s"
+    title="my_account.notifications"
   />
   <p
     className="big-spacer-bottom"
index 34fad4cf4c3df94669592d38e2fdd1587bad9c95..82e5fb84797d519dcbfd1325a7480a044ff17e85 100644 (file)
@@ -62,8 +62,6 @@ class UserOrganizations extends React.PureComponent {
   }
 
   render() {
-    const title = translate('my_account.organizations') + ' - ' + translate('my_account.page');
-
     const anyoneCanCreate =
       this.props.anyoneCanCreate != null && this.props.anyoneCanCreate.value === 'true';
 
@@ -72,7 +70,7 @@ class UserOrganizations extends React.PureComponent {
 
     return (
       <div className="account-body account-container">
-        <Helmet title={title} titleTemplate="%s - SonarQube" />
+        <Helmet title={translate('my_account.organizations')} />
 
         <header className="page-header">
           <h2 className="page-title">{translate('my_account.organizations')}</h2>
index b944998be0f3266b50dd8c33a69001a4605d6d36..69aea617c93a6cceb4b69dac74237473ffbaeddf 100644 (file)
@@ -74,20 +74,20 @@ export default class ProjectsContainer extends React.PureComponent {
   }
 
   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>
       );
     }
 
-    const title = translate('my_account.page') + ' - ' + translate('my_account.projects');
-
     return (
       <div className="account-body account-container">
-        <Helmet title={title} titleTemplate="SonarQube - %s" />
-
+        {helmet}
         <Projects
           projects={this.state.projects}
           total={this.state.total}
index 6591fc3c4c52f4d1ec8ebfd8e085b4d20180bc95..c27316d41bd28051de5ff73d9e7f6d52ddd1a13c 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { debounce, uniq } from 'lodash';
 import { connect } from 'react-redux';
 import { DEFAULT_FILTERS, DEBOUNCE_DELAY, STATUSES, CURRENTS } from './../constants';
@@ -39,6 +40,7 @@ import { Task } from '../types';
 import { getComponent } from '../../../store/rootReducer';
 import '../background-tasks.css';
 import { fetchOrganizations } from '../../../store/rootActions';
+import { translate } from '../../../helpers/l10n';
 
 type Props = {
   component: Object,
@@ -211,6 +213,7 @@ class BackgroundTasksApp extends React.PureComponent {
 
     return (
       <div className="page page-limited">
+        <Helmet title={translate('background_tasks.page')} />
         <Header />
 
         <Stats
index 33c71efd189485d15e9375b9d727e2b281229eae..1918cac2abf94643bc301e372f653c75ea48fa88 100644 (file)
@@ -19,6 +19,7 @@
  */
 import classNames from 'classnames';
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Components from './Components';
 import Breadcrumbs from './Breadcrumbs';
@@ -33,6 +34,7 @@ import {
 } from '../utils';
 import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket';
 import { getComponent } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 import '../code.css';
 
 class App extends React.PureComponent {
@@ -179,6 +181,8 @@ class App extends React.PureComponent {
 
     return (
       <div className="page page-limited">
+        <Helmet title={translate('code')} />
+
         {error &&
           <div className="alert alert-danger">
             {error}
index cc7a145f49c18d5369d81dabe9beeb52f9be33c5..52b1b29d6a1ea2b4bdbe56058b09253c02824d2d 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import { withRouter } from 'react-router';
 import { getAppState } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 import init from '../init';
 
 class CodingRulesAppContainer extends React.PureComponent {
@@ -70,6 +72,7 @@ class CodingRulesAppContainer extends React.PureComponent {
     // but react wants it to be there to unmount it
     return (
       <div>
+        <Helmet title={translate('rules')} />
         <div ref="container" />
       </div>
     );
index 1e005ee4c7066060e8d15c965f236de9db1af9c4..fd7caef81a87aa6f606808ba7e601d457e432fcf 100644 (file)
@@ -18,7 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import Spinner from './../components/Spinner';
+import { translate } from '../../../helpers/l10n';
 import '../styles.css';
 
 export default class App extends React.PureComponent {
@@ -37,6 +39,7 @@ export default class App extends React.PureComponent {
 
     return (
       <main id="component-measures">
+        <Helmet title={translate('layout.measures')} />
         {this.props.children}
       </main>
     );
index 3ce24f3779d8f045e561923c67473ef51079e180..1f40c41775ffa2f08395f4f01a42ff54286beea8 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import init from '../init';
 import { getComponent } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 class CustomMeasuresAppContainer extends React.PureComponent {
   componentDidMount() {
@@ -28,7 +30,12 @@ class CustomMeasuresAppContainer extends React.PureComponent {
   }
 
   render() {
-    return <div ref="container" />;
+    return (
+      <div>
+        <Helmet title={translate('custom_measures.page')} />
+        <div ref="container" />
+      </div>
+    );
   }
 }
 
index acd77e833cbb616420484446bc0f9d0b6ec9e312..16525d55ee548cb04329e76bd023c02364e3ed22 100644 (file)
@@ -18,7 +18,9 @@
  * 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 GroupsAppContainer extends React.PureComponent {
   componentDidMount() {
@@ -26,6 +28,11 @@ export default class GroupsAppContainer extends React.PureComponent {
   }
 
   render() {
-    return <div ref="container" />;
+    return (
+      <div>
+        <Helmet title={translate('user_groups.page')} />
+        <div ref="container" />
+      </div>
+    );
   }
 }
index a91ef8ea68ff4de00b3e4838290cdf28190946a4..4a9dca21d04f1fe077a3102903bef2f6f5313060 100644 (file)
@@ -793,16 +793,13 @@ export default class App extends React.PureComponent {
       </div>
     );
   }
-
   render() {
     const { component } = this.props;
     const { openIssue, paging } = this.state;
-
     const selectedIndex = this.getSelectedIndex();
-
     return (
       <div className="layout-page issues" id="issues-page">
-        <Helmet title={translate('issues.page')} titleTemplate="%s - SonarQube" />
+        <Helmet title={translate('issues.page')} />
 
         {this.renderSide(openIssue)}
 
index 140aea14e251d211f908a5ca8a84946cc3064413..d33defd813584e758a21168b0f2ae7da37c69900 100644 (file)
@@ -217,6 +217,7 @@ export type ReferencedLanguage = {
 
 export type Component = {
   key: string,
+  name: string,
   organization: string,
   qualifier: string
 };
index 6193721adef481d375535992693c4804a205e283..f5f031e98392afb2c9ab3c4f3a504c774f3e78f0 100644 (file)
@@ -18,7 +18,9 @@
  * 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() {
@@ -26,6 +28,11 @@ export default class MetricsAppContainer extends React.PureComponent {
   }
 
   render() {
-    return <div ref="container" />;
+    return (
+      <div>
+        <Helmet title={translate('custom_metrics.page')} />
+        <div ref="container" />
+      </div>
+    );
   }
 }
index be7b726a7f453c9554514713196a6661b1351593..1cd73c0be0fcd29fa0d7011e102a1de79937fab5 100644 (file)
@@ -19,8 +19,8 @@
  */
 // @flow
 import React from 'react';
-import Modal from 'react-modal';
 import Helmet from 'react-helmet';
+import Modal from 'react-modal';
 import { connect } from 'react-redux';
 import { withRouter } from 'react-router';
 import { translate } from '../../../helpers/l10n';
@@ -97,10 +97,7 @@ class OrganizationDelete extends React.PureComponent {
   render() {
     return (
       <div className="page page-limited">
-        <Helmet
-          title={`${translate('organization.delete')} - ${this.props.organization.name}`}
-          titleTemplate="%s - SonarQube"
-        />
+        <Helmet title={translate('delete')} />
 
         <header className="page-header">
           <h1 className="page-title">{translate('organization.delete')}</h1>
index d5d9870c1167557946661d6f4edb1d84563a1212..e108da282a5a6f76cdf149a78ce4a62975a1715f 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import { debounce } from 'lodash';
 import { translate } from '../../../helpers/l10n';
@@ -96,6 +97,8 @@ class OrganizationEdit extends React.PureComponent {
   render() {
     return (
       <div className="page page-limited">
+        <Helmet title={translate('edit')} />
+
         <header className="page-header">
           <h1 className="page-title">{translate('organization.edit')}</h1>
         </header>
index 53654f58b01e788cbd8eccd89f7084cec497d144..be73dafc0aff7ff6958aab232c9f84465f37cc97 100644 (file)
 // @flow
 import React from 'react';
 import { connect } from 'react-redux';
-import Helmet from 'react-helmet';
 import FavoriteProjectsContainer from '../../projects/components/FavoriteProjectsContainer';
 import { getOrganizationByKey } from '../../../store/rootReducer';
 import { updateOrganization } from '../actions';
-import { translate } from '../../../helpers/l10n';
 
 class OrganizationFavoriteProjects extends React.PureComponent {
   props: {
@@ -52,7 +50,6 @@ class OrganizationFavoriteProjects extends React.PureComponent {
   render() {
     return (
       <div id="projects-page">
-        <Helmet title={translate('projects.page')} titleTemplate="%s - SonarQube" />
         <FavoriteProjectsContainer
           location={this.props.location}
           organization={this.props.organization}
index 521d40d918446b0d9df64af08225287f5e4b694f..4f751110345c2ab02adbeeecd3c1f62d259f5dc7 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import init from '../../groups/init';
 import { getOrganizationByKey } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 import type { Organization } from '../../../store/organizations/duck';
 
 class OrganizationGroups extends React.PureComponent {
@@ -34,7 +36,12 @@ class OrganizationGroups extends React.PureComponent {
   }
 
   render() {
-    return <div ref="container" />;
+    return (
+      <div>
+        <Helmet title={translate('global_permissions.groups')} />
+        <div ref="container" />
+      </div>
+    );
   }
 }
 
index c4e3f6bcd25ab8bb97147265772e05229bfdd741..71f7d3f955157cdb8cb06f365123013fcffdc975 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import MembersPageHeader from './MembersPageHeader';
 import MembersListHeader from './MembersListHeader';
 import MembersList from './MembersList';
 import AddMemberForm from './forms/AddMemberForm';
 import ListFooter from '../../../components/controls/ListFooter';
+import { translate } from '../../../helpers/l10n';
 import type { Organization, OrgGroup } from '../../../store/organizations/duck';
 import type { Member } from '../../../store/organizationsMembers/actions';
 
@@ -80,6 +82,7 @@ export default class OrganizationMembers extends React.PureComponent {
     const { organization, status, members } = this.props;
     return (
       <div className="page page-limited">
+        <Helmet title={translate('organization.members.page')} />
         <MembersPageHeader loading={status.loading} total={status.total}>
           {organization.canAdmin &&
             <div className="page-actions">
index 7955b3c82fd62f49e62cb358f139d979fe84d7c0..739948592e306d9d0b1cbfa587c11118196296ef 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import OrganizationNavigation from '../navigation/OrganizationNavigation';
 import { fetchOrganization } from '../actions';
@@ -71,6 +72,7 @@ class OrganizationPage extends React.PureComponent {
 
     return (
       <div>
+        <Helmet defaultTitle={organization.name} titleTemplate={'%s - ' + organization.name} />
         <OrganizationNavigation organization={organization} location={this.props.location} />
         {this.props.children}
       </div>
index 99b789f1af4ca26971ddde3c01990ee4935a62ad..c342564368a58faed35ba3e2e04e939ab0db31ee 100644 (file)
 // @flow
 import React from 'react';
 import { connect } from 'react-redux';
-import Helmet from 'react-helmet';
 import AllProjectsContainer from '../../projects/components/AllProjectsContainer';
 import { getOrganizationByKey } from '../../../store/rootReducer';
 import { updateOrganization } from '../actions';
-import { translate } from '../../../helpers/l10n';
 
 class OrganizationProjects extends React.PureComponent {
   props: {
@@ -52,7 +50,6 @@ class OrganizationProjects extends React.PureComponent {
   render() {
     return (
       <div id="projects-page">
-        <Helmet title={translate('projects.page')} titleTemplate="%s - SonarQube" />
         <AllProjectsContainer
           isFavorite={false}
           location={this.props.location}
index 365adc0701d99df82965fe182352d17db43f65b1..fa13f06a074b1cacf5d19c15e0b938c6b87c465b 100644 (file)
@@ -5,8 +5,7 @@ exports[`smoke test 1`] = `
   className="page page-limited"
 >
   <HelmetWrapper
-    title="organization.delete - Foo"
-    titleTemplate="%s - SonarQube"
+    title="delete"
   />
   <header
     className="page-header"
@@ -39,8 +38,7 @@ exports[`smoke test 2`] = `
   className="page page-limited"
 >
   <HelmetWrapper
-    title="organization.delete - Foo"
-    titleTemplate="%s - SonarQube"
+    title="delete"
   />
   <header
     className="page-header"
@@ -121,8 +119,7 @@ exports[`smoke test 3`] = `
   className="page page-limited"
 >
   <HelmetWrapper
-    title="organization.delete - Foo"
-    titleTemplate="%s - SonarQube"
+    title="delete"
   />
   <header
     className="page-header"
index 43c8be0ddb63e5e4e98a7aab9311d4a31d501474..9fe30235a6233528d3a758cbc1486e60c8d404b1 100644 (file)
@@ -4,6 +4,9 @@ exports[`smoke test 1`] = `
 <div
   className="page page-limited"
 >
+  <HelmetWrapper
+    title="edit"
+  />
   <header
     className="page-header"
   >
@@ -132,6 +135,9 @@ exports[`smoke test 2`] = `
 <div
   className="page page-limited"
 >
+  <HelmetWrapper
+    title="edit"
+  />
   <header
     className="page-header"
   >
@@ -275,6 +281,9 @@ exports[`smoke test 3`] = `
 <div
   className="page page-limited"
 >
+  <HelmetWrapper
+    title="edit"
+  />
   <header
     className="page-header"
   >
index f6da626a33f6cf945848d623ebc217bd1e9a7b2a..20de4c58030b9b402e27bec1e887eff500dd87a4 100644 (file)
@@ -4,6 +4,9 @@ exports[`should not render actions for non admin 1`] = `
 <div
   className="page page-limited"
 >
+  <HelmetWrapper
+    title="organization.members.page"
+  />
   <MembersPageHeader
     total={2}
   />
@@ -50,6 +53,9 @@ exports[`should render actions for admin 1`] = `
 <div
   className="page page-limited"
 >
+  <HelmetWrapper
+    title="organization.members.page"
+  />
   <MembersPageHeader
     loading={true}
     total={2}
index a30c2f0a74bba2357225cae8731dcb373778d015..6c8f73738d410d63e89989aee3754376eabf16f7 100644 (file)
@@ -6,6 +6,10 @@ exports[`smoke test 1`] = `null`;
 
 exports[`smoke test 2`] = `
 <div>
+  <HelmetWrapper
+    defaultTitle="Foo"
+    titleTemplate="%s - Foo"
+  />
   <OrganizationNavigation
     organization={
       Object {
index 6d4d00d4c6fd05a208c8523b4e91b245a46ae74c..b28c3564d9eb6c0834fe7109cd1d879b68d1a72d 100644 (file)
 import React from 'react';
 import Home from './Home';
 import Template from './Template';
+import OrganizationHelmet from '../../../components/common/OrganizationHelmet';
 import { getPermissionTemplates } from '../../../api/permissions';
 import { sortPermissions, mergePermissionsToTemplates, mergeDefaultsToTemplates } from '../utils';
+import { translate } from '../../../helpers/l10n';
 import '../../permissions/styles.css';
 
 export default class App extends React.PureComponent {
@@ -83,13 +85,7 @@ export default class App extends React.PureComponent {
     );
   }
 
-  render() {
-    const { id } = this.props.location.query;
-
-    if (id) {
-      return this.renderTemplate(id);
-    }
-
+  renderHome() {
     return (
       <Home
         organization={this.props.organization}
@@ -101,4 +97,19 @@ export default class App extends React.PureComponent {
       />
     );
   }
+
+  render() {
+    const { id } = this.props.location.query;
+    return (
+      <div>
+        <OrganizationHelmet
+          title={translate('permission_templates.page')}
+          organization={this.props.organization}
+        />
+
+        {id && this.renderTemplate(id)}
+        {!id && this.renderHome()}
+      </div>
+    );
+  }
 }
index 0f70164617e15f020760668f1f9e71277a78830a..7b2135fc33517767ed32eeb1022dcaefe56cf15c 100644 (file)
@@ -36,7 +36,7 @@ export default class Home extends React.PureComponent {
   render() {
     return (
       <div className="page page-limited">
-        <Helmet title={translate('permission_templates.page')} titleTemplate="SonarQube - %s" />
+        <Helmet title={translate('permission_templates.page')} />
 
         <Header
           organization={this.props.organization}
index 8ee95b10525836679ab8c52d1f9b49903cf8568a..f36ded96c5b0a368d6c67bbe60fc5654adac85c2 100644 (file)
@@ -171,8 +171,6 @@ export default class Template extends React.PureComponent {
   };
 
   render() {
-    const title = translate('permission_templates.page') + ' - ' + this.props.template.name;
-
     const permissions = PERMISSIONS_ORDER_FOR_PROJECT.map(p => ({
       key: p,
       name: translate('projects_role', p),
@@ -197,7 +195,7 @@ export default class Template extends React.PureComponent {
 
     return (
       <div className="page page-limited">
-        <Helmet title={title} titleTemplate="SonarQube - %s" />
+        <Helmet title={this.props.template.name} />
 
         <TemplateHeader
           organization={this.props.organization}
index d7a02f5c98732a8d85c82d4f2a52da8fc254c91b..da906b66606a6b11154dee136f20c2cbd8c9f3e8 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import PageHeader from './PageHeader';
 import AllHoldersList from './AllHoldersList';
 import PageError from '../../shared/components/PageError';
+import { translate } from '../../../../helpers/l10n';
 import '../../styles.css';
 
-// TODO helmet
-
 export default function App(props: { organization?: {} }) {
   return (
     <div className="page page-limited">
+      <Helmet title={translate('global_permissions.permission')} />
       <PageHeader organization={props.organization} />
       <PageError />
       <AllHoldersList organization={props.organization} />
index 7590041e3026481d944099666b2a2b6fc7d890ba..1d7a82ff6f5634d687d543523648d73c0ebe79a9 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { without } from 'lodash';
 import PageHeader from './PageHeader';
 import UpgradeOrganizationBox from '../../../../components/common/UpgradeOrganizationBox';
@@ -27,10 +28,9 @@ import AllHoldersList from './AllHoldersList';
 import PublicProjectDisclaimer from './PublicProjectDisclaimer';
 import PageError from '../../shared/components/PageError';
 import * as api from '../../../../api/permissions';
+import { translate } from '../../../../helpers/l10n';
 import '../../styles.css';
 
-// TODO helmet
-
 export type Props = {|
   component: {
     configuration?: {
@@ -339,6 +339,8 @@ export default class App extends React.PureComponent {
 
     return (
       <div className="page page-limited" id="project-permissions-page">
+        <Helmet title={translate('permissions.page')} />
+
         <PageHeader
           component={this.props.component}
           loading={this.state.loading}
index 6ef724f1b7711ec3bc37f6852891405ad0545a5b..e94863a7ac15fbc3d14d0811e12137100e571c35 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Header from './Header';
 import Form from './Form';
 import { getComponent } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 class Deletion extends React.PureComponent {
   static propTypes = {
@@ -35,6 +37,7 @@ class Deletion extends React.PureComponent {
 
     return (
       <div className="page page-limited">
+        <Helmet title={translate('deletion.page')} />
         <Header component={this.props.component} />
         <Form component={this.props.component} />
       </div>
index b3f47fc37850557759cf0f84110e09dafb4606dc..97cd01c3f1cd9174b0d54b5bdbfba44bbcfc514f 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Header from './Header';
 import UpdateForm from './UpdateForm';
@@ -87,6 +88,7 @@ class Key extends React.PureComponent {
 
     return (
       <div id="project-key" className="page page-limited">
+        <Helmet title={translate('update_key.page')} />
         <Header />
 
         {modules == null && <i className="spinner" />}
index b8ac2300dc9e3e112810aebae67cf3a9879ea143..e06a045a9aa58b320f64a639ba009b25f9dd0bd7 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Header from './Header';
 import Table from './Table';
 import DeletionModal from './views/DeletionModal';
 import { fetchProjectLinks, deleteProjectLink, createProjectLink } from '../store/actions';
 import { getProjectAdminProjectLinks, getComponent } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 class Links extends React.PureComponent {
   static propTypes = {
@@ -55,6 +57,7 @@ class Links extends React.PureComponent {
   render() {
     return (
       <div className="page page-limited">
+        <Helmet title={translate('project_links.page')} />
         <Header onCreate={this.handleCreateLink} />
         <Table links={this.props.links} onDelete={this.handleDeleteLink} />
       </div>
index 944be04082bf953565104c1d0eb3c176f3362d04..fd5759a40b97c0ea7c4c84d3440788c63b85823b 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Header from './Header';
 import Form from './Form';
@@ -27,6 +28,7 @@ import {
   getProjectAdminProjectGate,
   getComponent
 } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 class QualityGate extends React.PureComponent {
   static propTypes = {
@@ -46,6 +48,7 @@ class QualityGate extends React.PureComponent {
   render() {
     return (
       <div id="project-quality-gate" className="page page-limited">
+        <Helmet title={translate('project_quality_gate.page')} />
         <Header />
         <Form
           allGates={this.props.allGates}
index ee3d6a0e7747e1f846cb0a577be6d5a31aa2b691..00e93a544715fc85811fcbe155314914df50cadc 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import Header from './Header';
 import Table from './Table';
@@ -29,6 +30,7 @@ import {
   getProjectAdminProjectProfiles,
   getComponent
 } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 type Props = {
   allProfiles: Array<{}>,
@@ -59,6 +61,8 @@ class QualityProfiles extends React.PureComponent {
 
     return (
       <div className="page page-limited">
+        <Helmet title={translate('project_quality_profiles.page')} />
+
         <Header />
 
         {profiles.length > 0
index 8cf6bcf2522a40d06fa5cf089a4273e98ef8ce5e..f1972c57bc36e41c23d31dc2c4b4f1d8cfa114cb 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import ProjectActivityPageHeader from './ProjectActivityPageHeader';
 import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
 import ProjectActivityPageFooter from './ProjectActivityPageFooter';
 import { fetchProjectActivity } from '../actions';
 import { getComponent } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 import './projectActivity.css';
 
 type Props = {
@@ -71,6 +73,8 @@ class ProjectActivityApp extends React.PureComponent {
 
     return (
       <div id="project-activity" className="page page-limited">
+        <Helmet title={translate('project_activity.page')} />
+
         <ProjectActivityPageHeader
           project={project}
           filter={this.state.filter}
index cb1c99b30f3c00f5837357b7304d75aa7b8c057c..01b9b56fb11585ed8ce169c3048c144a40335f83 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { debounce, uniq, without } from 'lodash';
 import Header from './header';
 import Search from './search';
 import Projects from './projects';
 import CreateProjectForm from './CreateProjectForm';
+import ListFooter from '../../components/controls/ListFooter';
 import { PAGE_SIZE, TYPE } from './constants';
 import { getComponents, getProvisioned, getGhosts, deleteComponents } from '../../api/components';
-import ListFooter from '../../components/controls/ListFooter';
+import { translate } from '../../helpers/l10n';
 import type { Organization } from '../../store/organizations/duck';
 
 type Props = {|
@@ -231,6 +233,8 @@ export default class Main extends React.PureComponent {
   render() {
     return (
       <div className="page page-limited" id="projects-management-page">
+        <Helmet title={translate('projects_management')} />
+
         <Header
           hasProvisionPermission={this.props.hasProvisionPermission}
           onProjectCreate={this.openCreateProjectForm}
index add7d2bd892d1dec229269ae1098d98fb2893913..e848e7657de7b3da0f3385502159726d8f56d492 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import PageHeaderContainer from './PageHeaderContainer';
 import ProjectsListContainer from './ProjectsListContainer';
 import ProjectsListFooterContainer from './ProjectsListFooterContainer';
 import PageSidebar from './PageSidebar';
 import VisualizationsContainer from '../visualizations/VisualizationsContainer';
 import { parseUrlQuery } from '../store/utils';
+import { translate } from '../../../helpers/l10n';
 import '../styles.css';
 
 export default class AllProjects extends React.PureComponent {
@@ -96,6 +98,7 @@ export default class AllProjects extends React.PureComponent {
 
     return (
       <div className="layout-page projects-page">
+        <Helmet title={translate('projects.page')} />
         <div className="layout-page-side-outer">
           <div className="layout-page-side" style={{ top }}>
             <div className="layout-page-side-inner">
index 8897f1e6f36a70ad5e3da35778488fa9297ce4e7..2bd70ec15bd24e0539674757050a6918ab8a5d49 100644 (file)
@@ -18,8 +18,6 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
-import Helmet from 'react-helmet';
-import { translate } from '../../../helpers/l10n';
 
 export default class App extends React.PureComponent {
   componentDidMount() {
@@ -33,7 +31,6 @@ export default class App extends React.PureComponent {
   render() {
     return (
       <div id="projects-page">
-        <Helmet title={translate('projects.page')} titleTemplate="%s - SonarQube" />
         {this.props.children}
       </div>
     );
index 7d603a8baa1826c0a8becddf2b524ffd4011d252..60957d01b68d2aefb4529dfdfb9ebc8af861c939 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React, { Component } from 'react';
+import Helmet from 'react-helmet';
 import {
   fetchQualityGate,
   setQualityGateAsDefault,
@@ -113,6 +114,7 @@ export default class Details extends Component {
 
     return (
       <div className="search-navigator-workspace">
+        <Helmet title={qualityGate.name} />
         <DetailsHeader
           qualityGate={qualityGate}
           edit={edit}
index 6bfddd60fe87a253079a1a25e600aae95c277657..575a9f212e7de49d3b46785fd25fb49e957d5f3c 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React, { Component } from 'react';
+import Helmet from 'react-helmet';
 import ListHeader from './ListHeader';
 import List from './List';
 import {
   fetchQualityGatesAppDetails,
   fetchQualityGates as fetchQualityGatesAPI
 } from '../../../api/quality-gates';
+import { translate } from '../../../helpers/l10n';
 import '../styles.css';
 
 export default class QualityGatesApp extends Component {
@@ -52,9 +54,11 @@ export default class QualityGatesApp extends Component {
 
   render() {
     const { children, qualityGates, edit } = this.props;
-
+    const defaultTitle = translate('quality_gates.page');
     return (
       <div className="search-navigator sticky search-navigator-extended-view">
+        <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} />
+
         <div className="search-navigator-side search-navigator-side-light" style={{ top: 30 }}>
           <div className="search-navigator-filters">
             <ListHeader canEdit={edit} onAdd={this.handleAdd.bind(this)} />
index 982553d7993b742cab93fdd0f7f79fdfcf396a07..564996b4c5ce8bf79cd1d14f14ce062ffad2fbcb 100644 (file)
@@ -21,6 +21,8 @@
 import React from 'react';
 import { getQualityProfiles, 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';
 
@@ -28,7 +30,7 @@ type Props = {
   children: React.Element<*>,
   currentUser: { permissions: { global: Array<string> } },
   languages: Array<*>,
-  organization: { canAdmin?: boolean, key: string } | null
+  organization: { name: string, canAdmin?: boolean, key: string } | null
 };
 
 type State = {
@@ -94,11 +96,11 @@ export default class App extends React.PureComponent {
     if (this.state.loading) {
       return <i className="spinner" />;
     }
-
+    const { organization } = this.props;
     const finalLanguages = Object.values(this.props.languages);
 
-    const canAdmin = this.props.organization
-      ? this.props.organization.canAdmin
+    const canAdmin = organization
+      ? organization.canAdmin
       : this.props.currentUser.permissions.global.includes('profileadmin');
 
     return React.cloneElement(this.props.children, {
@@ -106,7 +108,7 @@ export default class App extends React.PureComponent {
       languages: finalLanguages,
       exporters: this.state.exporters,
       updateProfiles: this.updateProfiles,
-      organization: this.props.organization ? this.props.organization.key : null,
+      organization: organization ? organization.key : null,
       canAdmin
     });
   }
@@ -114,6 +116,11 @@ export default class App extends React.PureComponent {
   render() {
     return (
       <div className="page page-limited">
+        <OrganizationHelmet
+          title={translate('quality_profiles.page')}
+          organization={this.props.organization}
+        />
+
         {this.renderChild()}
       </div>
     );
index 0ee269b6a25cd40cf4ec4c9ed7e288050d70cd19..ed1d683ed1c0fbd036adf7983e71243ee6fecb1c 100644 (file)
@@ -22,7 +22,6 @@ import React from 'react';
 import Helmet from 'react-helmet';
 import ProfileNotFound from './ProfileNotFound';
 import ProfileHeader from '../details/ProfileHeader';
-import { translate } from '../../../helpers/l10n';
 import type { Profile } from '../propTypes';
 
 type Props = {
@@ -85,12 +84,9 @@ export default class ProfileContainer extends React.PureComponent {
       ...other
     });
 
-    const title = translate('quality_profiles.page') + ' - ' + profile.name;
-
     return (
       <div>
-        <Helmet title={title} titleTemplate="SonarQube - %s" />
-
+        <Helmet title={profile.name} />
         <ProfileHeader
           canAdmin={this.props.canAdmin}
           organization={organization}
index e675d6245f9a760018f0e6ad1c97efded0e2fea7..df44389a9c66c15f79cf9626211f044e728ed177 100644 (file)
  */
 // @flow
 import React from 'react';
-import Helmet from 'react-helmet';
 import PageHeader from './PageHeader';
 import Evolution from './Evolution';
 import ProfilesList from './ProfilesList';
-import { translate } from '../../../helpers/l10n';
 import type { Profile } from '../propTypes';
 
 type Props = {
@@ -41,8 +39,6 @@ export default class HomeContainer extends React.PureComponent {
   render() {
     return (
       <div>
-        <Helmet title={translate('quality_profiles.page')} titleTemplate="SonarQube - %s" />
-
         <PageHeader {...this.props} />
 
         <div className="page-with-sidebar">
index c86abf3956a915a3f53f3c28f71b3ff9221e3f2b..d357cff1fbc8201de73b7c0a90f1cf4adb5c24ee 100644 (file)
@@ -19,6 +19,7 @@
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import PageHeader from './PageHeader';
 import CategoryDefinitionsList from './CategoryDefinitionsList';
@@ -26,6 +27,7 @@ import AllCategoriesList from './AllCategoriesList';
 import WildcardsHelp from './WildcardsHelp';
 import { fetchSettings } from '../store/actions';
 import { getSettingsAppDefaultCategory } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 import '../styles.css';
 
 type Props = {
@@ -78,6 +80,8 @@ class App extends React.PureComponent {
 
     return (
       <div id="settings-page" className="page page-limited">
+        <Helmet title={translate('settings.page')} />
+
         <PageHeader component={this.props.component} />
         <div className="settings-layout">
           <div className="settings-side">
index 05b0b9ca524f989f1239bd04d63e908e400e9d20..72927a664e35e05693e9257434b6f7e02b9f7e62 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import GenerateSecretKeyForm from './GenerateSecretKeyForm';
 import EncryptionForm from './EncryptionForm';
 import { translate } from '../../../helpers/l10n';
@@ -42,6 +43,7 @@ export default class EncryptionApp extends React.PureComponent {
   render() {
     return (
       <div id="encryption-page" className="page page-limited">
+        <Helmet title={translate('property.category.security.encryption')} />
         <header className="page-header">
           <h1 className="page-title">{translate('property.category.security.encryption')}</h1>
           {this.props.loading && <i className="spinner" />}
index 5a2a95b6544fcb2cac15d4c9d29547bc505c67e5..8958da9608a881c40d7a5de59f3b312bec449d35 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import LicensesAppHeader from './LicensesAppHeader';
 import LicensesListContainer from './LicensesListContainer';
+import { translate } from '../../../helpers/l10n';
 
 export default function LicensesApp() {
   return (
     <div id="licenses-page" className="page page-limited">
+      <Helmet title={translate('property.category.licenses')} />
       <LicensesAppHeader />
       <LicensesListContainer />
     </div>
index 40c7197b3dd74e04da57b38296d7f3233a2486ac..ebe44676016773b5cdbe815bfedb00d007bace9f 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { translate } from '../../../helpers/l10n';
 import { getServerId, generateServerId } from '../../../api/settings';
 import { parseError } from '../../code/utils';
@@ -76,6 +77,7 @@ export default class ServerIdApp extends React.PureComponent {
   render() {
     return (
       <div id="server-id-page" className="page page-limited">
+        <Helmet title={translate('property.category.server_id')} />
         <header className="page-header">
           <h1 className="page-title">{translate('property.category.server_id')}</h1>
           {this.state.loading && <i className="spinner" />}
index 5707ee5604d38230604647e6e4b08bf920838086..3bb364b83c023141dc7f4b7e7d7903145fd9a2d6 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { sortBy } from 'lodash';
 import { getSystemInfo } from '../../api/system';
 import Section from './section';
@@ -75,6 +76,7 @@ export default class Main extends React.PureComponent {
 
     return (
       <div className="page">
+        <Helmet title={translate('system_info.page')} />
         <header className="page-header">
           <h1 className="page-title">{translate('system_info.page')}</h1>
           <div className="page-actions">
index 4745b919ec28cc5ef452a03f236d3c3f744ace03..cd5a9f8f105c32ed34acf47a2d33249ef0928193 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import init from '../init';
 import { getSettingValue } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 class UpdateCenterAppContainer extends React.PureComponent {
   componentDidMount() {
@@ -38,6 +40,7 @@ class UpdateCenterAppContainer extends React.PureComponent {
     // but react wants it to be there to unmount it
     return (
       <div>
+        <Helmet title={translate('update_center.page')} />
         <div ref="container" />
       </div>
     );
index 99bdd44584ab94f607c13df0198277a69843d381..d70383fd891610ba439b266d424ea5faba2c4fbd 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
+import Helmet from 'react-helmet';
 import { connect } from 'react-redux';
 import init from '../init';
 import { getCurrentUser } from '../../../store/rootReducer';
+import { translate } from '../../../helpers/l10n';
 
 class UsersAppContainer extends React.PureComponent {
   static propTypes = {
@@ -32,7 +34,12 @@ class UsersAppContainer extends React.PureComponent {
   }
 
   render() {
-    return <div ref="container" />;
+    return (
+      <div>
+        <Helmet title={translate('users.page')} />
+        <div ref="container" />
+      </div>
+    );
   }
 }
 
index a97c27b9d7a2ba0fb7fcebba726f8f43e49a740e..166babf4c99f19a7f2a77e4d48face9b3f8c0258 100644 (file)
  */
 // @flow
 import React from 'react';
+import Helmet from 'react-helmet';
 import { Link } from 'react-router';
 import { fetchWebApi } from '../../../api/web-api';
 import Menu from './Menu';
 import Search from './Search';
 import Domain from './Domain';
 import { getActionKey, isDomainPathActive } from '../utils';
+import { translate } from '../../../helpers/l10n';
 import type { Domain as DomainType } from '../../../api/web-api';
 import '../styles/web-api.css';
 
@@ -142,10 +144,11 @@ export default class WebApiApp extends React.PureComponent {
 
     return (
       <div className="search-navigator sticky">
+        <Helmet title={translate('api_documentation.page')} />
         <div className="search-navigator-side search-navigator-side-light" style={{ top: 30 }}>
           <div className="web-api-page-header">
             <Link to="/web_api/">
-              <h1>Web API</h1>
+              <h1>{translate('api_documentation.page')}</h1>
             </Link>
           </div>
 
diff --git a/server/sonar-web/src/main/js/components/common/OrganizationHelmet.js b/server/sonar-web/src/main/js/components/common/OrganizationHelmet.js
new file mode 100644 (file)
index 0000000..ffffec4
--- /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.
+ */
+// @flow
+import React from 'react';
+import Helmet from 'react-helmet';
+
+type Props = {
+  title: string,
+  organization?: ?{ name: string }
+};
+
+export default function OrganizationHelmet({ title, organization }: Props) {
+  const defaultTitle = title + (organization ? ' - ' + organization.name : '');
+  return <Helmet defaultTitle={defaultTitle} titleTemplate={'%s - ' + defaultTitle} />;
+}
index 42cdecd025af737e52a1531d6434fe9b42c0519f..654c487f390f47b935c6084b38cc32f977da127f 100644 (file)
@@ -510,6 +510,7 @@ analysis_reports.page=Project Computation
 coding_rules.page=Rules
 global_permissions.page=Global Permissions
 global_permissions.page.description=Grant and revoke permissions to make changes at the global level. These permissions include editing quality profiles, sharing dashboards, and performing global system administration.
+custom_metrics.page=Custom Metrics
 manual_metrics.page=Manual Metrics
 manual_metrics.page.description=These metrics are available for all projects. Manual measures can be set at project level via the configuration interface.
 manual_metrics.add_manual_metric=Add New Manual Metric