diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-05-09 15:43:19 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-05-10 11:56:20 +0200 |
commit | 9821488bd9e3f764dc4f3a7fdd4beda93e870254 (patch) | |
tree | 493e62164676d84fc491b1c9421d1ae0d20100c9 | |
parent | a6f37fa019f167563152670e3812efb383c4eeb2 (diff) | |
download | sonarqube-9821488bd9e3f764dc4f3a7fdd4beda93e870254.tar.gz sonarqube-9821488bd9e3f764dc4f3a7fdd4beda93e870254.zip |
SONAR-8369 SONAR-8722 Sanitize page titles
63 files changed, 365 insertions, 157 deletions
diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.js b/server/sonar-web/src/main/js/app/components/AdminContainer.js index 05d64849395..da8c9376dbc 100644 --- a/server/sonar-web/src/main/js/app/components/AdminContainer.js +++ b/server/sonar-web/src/main/js/app/components/AdminContainer.js @@ -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> diff --git a/server/sonar-web/src/main/js/app/components/App.js b/server/sonar-web/src/main/js/app/components/App.js index 8f4d06a1078..ea68bd92461 100644 --- a/server/sonar-web/src/main/js/app/components/App.js +++ b/server/sonar-web/src/main/js/app/components/App.js @@ -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 index 00000000000..6006da488b1 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/DefaultHelmetContainer.js @@ -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> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/ProjectContainer.js b/server/sonar-web/src/main/js/app/components/ProjectContainer.js index d1cf872b14f..8df53c24ff3 100644 --- a/server/sonar-web/src/main/js/app/components/ProjectContainer.js +++ b/server/sonar-web/src/main/js/app/components/ProjectContainer.js @@ -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> diff --git a/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.js b/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.js index a7f47a2fa3a..4e6da93deea 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.js +++ b/server/sonar-web/src/main/js/app/components/extensions/PortfoliosPage.js @@ -19,13 +19,18 @@ */ // @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> ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js index 9c02a29013d..42c3efaa232 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavBreadcrumbs.js @@ -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> ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap index 02834a1f242..385012e7de0 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavBreadcrumbs-test.js.snap @@ -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" diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index fa8714220bf..5b4a0d37e15 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/account/components/Account.js b/server/sonar-web/src/main/js/apps/account/components/Account.js index aed774b7bac..f9918546f6e 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Account.js +++ b/server/sonar-web/src/main/js/apps/account/components/Account.js @@ -19,9 +19,11 @@ */ 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} /> diff --git a/server/sonar-web/src/main/js/apps/account/components/Security.js b/server/sonar-web/src/main/js/apps/account/components/Security.js index f444538f1ee..d52a3b6e1ea 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Security.js +++ b/server/sonar-web/src/main/js/apps/account/components/Security.js @@ -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} /> diff --git a/server/sonar-web/src/main/js/apps/account/notifications/Notifications.js b/server/sonar-web/src/main/js/apps/account/notifications/Notifications.js index 6f27b3141dc..b8c596cba1d 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/Notifications.js +++ b/server/sonar-web/src/main/js/apps/account/notifications/Notifications.js @@ -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')} diff --git a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap index 51c289290b8..66eea69153b 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap +++ b/server/sonar-web/src/main/js/apps/account/notifications/__tests__/__snapshots__/Notifications-test.js.snap @@ -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" diff --git a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js index 34fad4cf4c3..82e5fb84797 100644 --- a/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js +++ b/server/sonar-web/src/main/js/apps/account/organizations/UserOrganizations.js @@ -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> 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 index b944998be0f..69aea617c93 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js +++ b/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js index 6591fc3c4c5..c27316d41bd 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js @@ -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 diff --git a/server/sonar-web/src/main/js/apps/code/components/App.js b/server/sonar-web/src/main/js/apps/code/components/App.js index 33c71efd189..1918cac2abf 100644 --- a/server/sonar-web/src/main/js/apps/code/components/App.js +++ b/server/sonar-web/src/main/js/apps/code/components/App.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js index cc7a145f49c..52b1b29d6a1 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesAppContainer.js @@ -19,9 +19,11 @@ */ // @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> ); diff --git a/server/sonar-web/src/main/js/apps/component-measures/app/App.js b/server/sonar-web/src/main/js/apps/component-measures/app/App.js index 1e005ee4c70..fd7caef81a8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/app/App.js +++ b/server/sonar-web/src/main/js/apps/component-measures/app/App.js @@ -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> ); diff --git a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js b/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js index 3ce24f3779d..1f40c41775f 100644 --- a/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js +++ b/server/sonar-web/src/main/js/apps/custom-measures/components/CustomMeasuresAppContainer.js @@ -18,9 +18,11 @@ * 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> + ); } } diff --git a/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js b/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js index acd77e833cb..16525d55ee5 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js +++ b/server/sonar-web/src/main/js/apps/groups/components/GroupsAppContainer.js @@ -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> + ); } } diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index a91ef8ea68f..4a9dca21d04 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -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)} diff --git a/server/sonar-web/src/main/js/apps/issues/utils.js b/server/sonar-web/src/main/js/apps/issues/utils.js index 140aea14e25..d33defd8135 100644 --- a/server/sonar-web/src/main/js/apps/issues/utils.js +++ b/server/sonar-web/src/main/js/apps/issues/utils.js @@ -217,6 +217,7 @@ export type ReferencedLanguage = { export type Component = { key: string, + name: string, organization: string, qualifier: string }; 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 index 6193721adef..f5f031e9839 100644 --- a/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js +++ b/server/sonar-web/src/main/js/apps/metrics/components/MetricsAppContainer.js @@ -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> + ); } } diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.js index be7b726a7f4..1cd73c0be0f 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js index d5d9870c116..e108da282a5 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js index 53654f58b01..be73dafc0af 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js @@ -20,11 +20,9 @@ // @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} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroups.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroups.js index 521d40d9184..4f751110345 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroups.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroups.js @@ -19,9 +19,11 @@ */ // @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> + ); } } diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js index c4e3f6bcd25..71f7d3f9551 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationMembers.js @@ -19,11 +19,13 @@ */ // @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"> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js index 7955b3c82fd..739948592e3 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.js @@ -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> diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js index 99b789f1af4..c342564368a 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js @@ -20,11 +20,9 @@ // @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} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.js.snap index 365adc0701d..fa13f06a074 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.js.snap @@ -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" diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap index 43c8be0ddb6..9fe30235a62 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.js.snap @@ -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" > diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap index f6da626a33f..20de4c58030 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationMembers-test.js.snap @@ -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} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap index a30c2f0a74b..6c8f73738d4 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap +++ b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.js.snap @@ -6,6 +6,10 @@ exports[`smoke test 1`] = `null`; exports[`smoke test 2`] = ` <div> + <HelmetWrapper + defaultTitle="Foo" + titleTemplate="%s - Foo" + /> <OrganizationNavigation organization={ Object { diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/App.js b/server/sonar-web/src/main/js/apps/permission-templates/components/App.js index 6d4d00d4c6f..b28c3564d9e 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/App.js +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/App.js @@ -20,8 +20,10 @@ 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> + ); + } } diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Home.js b/server/sonar-web/src/main/js/apps/permission-templates/components/Home.js index 0f70164617e..7b2135fc335 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Home.js +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Home.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js b/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js index 8ee95b10525..f36ded96c5b 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Template.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/App.js b/server/sonar-web/src/main/js/apps/permissions/global/components/App.js index d7a02f5c987..da906b66606 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/App.js +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/App.js @@ -19,16 +19,17 @@ */ // @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} /> diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.js b/server/sonar-web/src/main/js/apps/permissions/project/components/App.js index 7590041e302..1d7a82ff6f5 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js b/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js index 6ef724f1b77..e94863a7ac1 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js +++ b/server/sonar-web/src/main/js/apps/project-admin/deletion/Deletion.js @@ -18,10 +18,12 @@ * 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> diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js index b3f47fc3785..97cd01c3f1c 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js @@ -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" />} diff --git a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js index b8ac2300dc9..e06a045a9aa 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/links/Links.js +++ b/server/sonar-web/src/main/js/apps/project-admin/links/Links.js @@ -18,12 +18,14 @@ * 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> diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js b/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js index 944be04082b..fd5759a40b9 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-gate/QualityGate.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js index ee3d6a0e774..00e93a54471 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js +++ b/server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js @@ -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 diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js index 8cf6bcf2522..f1972c57bc3 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js @@ -19,12 +19,14 @@ */ // @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} diff --git a/server/sonar-web/src/main/js/apps/projects-admin/main.js b/server/sonar-web/src/main/js/apps/projects-admin/main.js index cb1c99b30f3..01b9b56fb11 100644 --- a/server/sonar-web/src/main/js/apps/projects-admin/main.js +++ b/server/sonar-web/src/main/js/apps/projects-admin/main.js @@ -19,14 +19,16 @@ */ // @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} diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js index add7d2bd892..e848e7657de 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js @@ -18,12 +18,14 @@ * 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"> 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 index 8897f1e6f36..2bd70ec15bd 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/App.js +++ b/server/sonar-web/src/main/js/apps/projects/components/App.js @@ -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> ); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js index 7d603a8baa1..60957d01b68 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Details.js @@ -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} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js index 6bfddd60fe8..575a9f212e7 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatesApp.js @@ -18,12 +18,14 @@ * 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)} /> 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 index 982553d7993..564996b4c5c 100644 --- 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 @@ -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> ); 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 index 0ee269b6a25..ed1d683ed1c 100644 --- 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 @@ -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} 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 index e675d6245f9..df44389a9c6 100644 --- 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 @@ -19,11 +19,9 @@ */ // @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"> diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js index c86abf3956a..d357cff1fbc 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/App.js +++ b/server/sonar-web/src/main/js/apps/settings/components/App.js @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.js b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.js index 05b0b9ca524..72927a664e3 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.js +++ b/server/sonar-web/src/main/js/apps/settings/encryption/EncryptionApp.js @@ -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" />} diff --git a/server/sonar-web/src/main/js/apps/settings/licenses/LicensesApp.js b/server/sonar-web/src/main/js/apps/settings/licenses/LicensesApp.js index 5a2a95b6544..8958da9608a 100644 --- a/server/sonar-web/src/main/js/apps/settings/licenses/LicensesApp.js +++ b/server/sonar-web/src/main/js/apps/settings/licenses/LicensesApp.js @@ -18,12 +18,15 @@ * 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> diff --git a/server/sonar-web/src/main/js/apps/settings/serverId/ServerIdApp.js b/server/sonar-web/src/main/js/apps/settings/serverId/ServerIdApp.js index 40c7197b3dd..ebe44676016 100644 --- a/server/sonar-web/src/main/js/apps/settings/serverId/ServerIdApp.js +++ b/server/sonar-web/src/main/js/apps/settings/serverId/ServerIdApp.js @@ -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" />} diff --git a/server/sonar-web/src/main/js/apps/system/main.js b/server/sonar-web/src/main/js/apps/system/main.js index 5707ee5604d..3bb364b83c0 100644 --- a/server/sonar-web/src/main/js/apps/system/main.js +++ b/server/sonar-web/src/main/js/apps/system/main.js @@ -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"> diff --git a/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js b/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js index 4745b919ec2..cd5a9f8f105 100644 --- a/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js +++ b/server/sonar-web/src/main/js/apps/update-center/components/UpdateCenterAppContainer.js @@ -18,9 +18,11 @@ * 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> ); diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js b/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js index 99bdd44584a..d70383fd891 100644 --- a/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js +++ b/server/sonar-web/src/main/js/apps/users/components/UsersAppContainer.js @@ -18,9 +18,11 @@ * 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> + ); } } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.js b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.js index a97c27b9d7a..166babf4c99 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.js +++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.js @@ -19,12 +19,14 @@ */ // @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 index 00000000000..ffffec47d4b --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/OrganizationHelmet.js @@ -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} />; +} diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 42cdecd025a..654c487f390 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 |