From d55ab8e0a6c65451bba8bd4a635d6f07d41a43b2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 20 Jun 2018 15:22:23 +0200 Subject: [PATCH] SONARCLOUD-68 Add google analytics to SonarCloud --- .../org/sonar/process/ProcessProperties.java | 4 +- .../org/sonar/server/ui/ws/GlobalAction.java | 2 + .../sonar/server/ui/ws/GlobalActionTest.java | 4 +- server/sonar-web/package.json | 1 + .../src/main/js/app/components/App.tsx | 6 +- .../main/js/app/components/PageTracker.tsx | 65 +++++++++++++++++++ server/sonar-web/yarn.lock | 16 +++++ 7 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/PageTracker.tsx diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java index 6c5deb927c7..9cec2c0a2b5 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java @@ -108,8 +108,8 @@ public class ProcessProperties { SONAR_UPDATECENTER_ACTIVATE("sonar.updatecenter.activate", "true"), SONARCLOUD_ENABLED("sonar.sonarcloud.enabled", "false"), - SONAR_PRISMIC_ACCESS_TOKEN("sonar.prismic.accessToken", ""), + SONAR_ANALYTICS_TRACKING_ID("sonar.analytics.trackingId", ""), BITBUCKETCLOUD_APP_KEY("sonar.bitbucketcloud.appKey", "sonarcloud"), BITBUCKETCLOUD_ENDPOINT("sonar.bitbucketcloud.endpoint", "https://api.bitbucket.org"), @@ -122,8 +122,6 @@ public class ProcessProperties { // whether the blue/green deployment of server is enabled BLUE_GREEN_ENABLED("sonar.blueGreenEnabled", "false"); - - private final String key; private final String defaultValue; diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java index f69de5baed5..841840822d7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/GlobalAction.java @@ -52,6 +52,7 @@ import static org.sonar.core.config.WebConstants.SONAR_LF_GRAVATAR_SERVER_URL; import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_URL; import static org.sonar.core.config.WebConstants.SONAR_LF_LOGO_WIDTH_PX; import static org.sonar.process.ProcessProperties.Property.SONARCLOUD_ENABLED; +import static org.sonar.process.ProcessProperties.Property.SONAR_ANALYTICS_TRACKING_ID; import static org.sonar.process.ProcessProperties.Property.SONAR_PRISMIC_ACCESS_TOKEN; import static org.sonar.process.ProcessProperties.Property.SONAR_UPDATECENTER_ACTIVATE; @@ -101,6 +102,7 @@ public class GlobalAction implements NavigationWsAction, Startable { boolean isOnSonarCloud = config.getBoolean(SONARCLOUD_ENABLED.getKey()).orElse(false); if (isOnSonarCloud) { this.systemSettingValuesByKey.put(SONAR_PRISMIC_ACCESS_TOKEN.getKey(), config.get(SONAR_PRISMIC_ACCESS_TOKEN.getKey()).orElse(null)); + this.systemSettingValuesByKey.put(SONAR_ANALYTICS_TRACKING_ID.getKey(), config.get(SONAR_ANALYTICS_TRACKING_ID.getKey()).orElse(null)); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java index b0cb60037e9..aed04367e5a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/GlobalActionTest.java @@ -130,11 +130,13 @@ public class GlobalActionTest { public void return_prismic_setting_on_sonarcloud_only() { settings.setProperty("sonar.sonarcloud.enabled", true); settings.setProperty("sonar.prismic.accessToken", "secret"); + settings.setProperty("sonar.analytics.trackingId", "ga_id"); init(); assertJson(call()).isSimilarTo("{" + " \"settings\": {" + " \"sonar.prismic.accessToken\": \"secret\"" + + " \"sonar.analytics.trackingId\": \"ga_id\"" + " }" + "}"); } @@ -248,7 +250,6 @@ public class GlobalActionTest { userSession.logIn().setRoot(); when(webServer.isStandalone()).thenReturn(true); - assertJson(call()).isSimilarTo("{\"standalone\":true}"); } @@ -258,7 +259,6 @@ public class GlobalActionTest { userSession.logIn().setRoot(); when(webServer.isStandalone()).thenReturn(false); - assertJson(call()).isSimilarTo("{\"standalone\":false}"); } diff --git a/server/sonar-web/package.json b/server/sonar-web/package.json index 241641cf904..a8b603c90d8 100644 --- a/server/sonar-web/package.json +++ b/server/sonar-web/package.json @@ -26,6 +26,7 @@ "react-day-picker": "7.1.8", "react-dom": "16.2.0", "react-draggable": "3.0.5", + "react-ga": "2.5.3", "react-helmet": "5.2.0", "react-intl": "2.4.0", "react-modal": "3.4.4", diff --git a/server/sonar-web/src/main/js/app/components/App.tsx b/server/sonar-web/src/main/js/app/components/App.tsx index 3930b084f79..91c65cc4e6c 100644 --- a/server/sonar-web/src/main/js/app/components/App.tsx +++ b/server/sonar-web/src/main/js/app/components/App.tsx @@ -26,7 +26,10 @@ import { CurrentUser } from '../types'; import { fetchCurrentUser } from '../../store/users/actions'; import { fetchLanguages, fetchAppState } from '../../store/rootActions'; import { fetchMyOrganizations } from '../../apps/account/organizations/actions'; -import { getInstance } from '../../helpers/system'; +import { getInstance, isSonarCloud } from '../../helpers/system'; +import { lazyLoad } from '../../components/lazyLoad'; + +const PageTracker = lazyLoad(() => import('./PageTracker')); interface Props { children: JSX.Element; @@ -115,6 +118,7 @@ class App extends React.PureComponent { return ( <> + {isSonarCloud() && } {this.props.children} ); diff --git a/server/sonar-web/src/main/js/app/components/PageTracker.tsx b/server/sonar-web/src/main/js/app/components/PageTracker.tsx new file mode 100644 index 00000000000..e2a4f0c04dd --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/PageTracker.tsx @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as GoogleAnalytics from 'react-ga'; +import { withRouter, WithRouterProps } from 'react-router'; +import { connect } from 'react-redux'; +import { getGlobalSettingValue } from '../../store/rootReducer'; + +interface StateProps { + trackingId?: string; +} + +type Props = WithRouterProps & StateProps; + +export class PageTracker extends React.PureComponent { + componentDidMount() { + if (this.props.trackingId) { + GoogleAnalytics.initialize(this.props.trackingId); + this.trackPage(); + } + } + + componentDidUpdate(prevProps: Props) { + const currentPage = this.props.location.pathname; + const prevPage = prevProps.location.pathname; + + if (currentPage !== prevPage) { + this.trackPage(); + } + } + + trackPage = () => { + const { location, trackingId } = this.props; + if (trackingId) { + GoogleAnalytics.pageview(location.pathname); + } + }; + + render() { + return null; + } +} + +const mapStateToProps = (state: any): StateProps => ({ + trackingId: (getGlobalSettingValue(state, 'sonar.analytics.trackingId') || {}).value +}); + +export default withRouter<{}>(connect(mapStateToProps)(PageTracker)); diff --git a/server/sonar-web/yarn.lock b/server/sonar-web/yarn.lock index e99a7491fcf..33dd4edc00b 100644 --- a/server/sonar-web/yarn.lock +++ b/server/sonar-web/yarn.lock @@ -6971,6 +6971,13 @@ react-error-overlay@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" +react-ga@2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.5.3.tgz#0f447c73664c069a5fc341f6f431262e3d4c23c4" + optionalDependencies: + prop-types "^15.6.0" + react "^15.6.2 || ^16.0" + react-helmet@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7" @@ -7082,6 +7089,15 @@ react@16.2.0: object-assign "^4.1.1" prop-types "^15.6.0" +"react@^15.6.2 || ^16.0": + version "16.4.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" -- 2.39.5