From 5a13ee1077d58c3a060bfd06f0bea397feca6ae5 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Fri, 11 Nov 2016 17:03:01 +0000 Subject: [PATCH] use anlaytis api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - don’t talk to postgres - show recent blog post announcments - proxy all events to analytics api --- .../Analytics/AnalyticsManager.coffee | 69 ++++++++---------- .../AnnouncementsController.coffee | 21 ++++++ .../Announcements/AnnouncementsHandler.coffee | 36 ++++++++++ .../coffee/Features/Blog/BlogHandler.coffee | 24 +++++++ .../Features/Project/ProjectController.coffee | 4 ++ services/web/app/coffee/router.coffee | 4 ++ services/web/app/views/project/list.jade | 8 ++- services/web/config/settings.defaults.coffee | 3 +- services/web/package.json | 3 +- services/web/public/coffee/main.coffee | 1 + .../public/coffee/main/announcements.coffee | 20 ++++++ services/web/public/coffee/main/event.coffee | 10 --- .../AnnouncementsHandlerTests.coffee | 72 +++++++++++++++++++ 13 files changed, 222 insertions(+), 53 deletions(-) create mode 100644 services/web/app/coffee/Features/Announcements/AnnouncementsController.coffee create mode 100644 services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee create mode 100644 services/web/app/coffee/Features/Blog/BlogHandler.coffee create mode 100644 services/web/public/coffee/main/announcements.coffee create mode 100644 services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee diff --git a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee index f906118b16..341a0a68ba 100644 --- a/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee +++ b/services/web/app/coffee/Features/Analytics/AnalyticsManager.coffee @@ -1,43 +1,36 @@ -Settings = require "settings-sharelatex" +settings = require "settings-sharelatex" logger = require "logger-sharelatex" _ = require "underscore" +request = require "request" -if !Settings.analytics?.postgres? - module.exports = - recordEvent: (user_id, event, segmentation, callback = () ->) -> - logger.log {user_id, event, segmentation}, "no event tracking configured, logging event" - callback() -else - Sequelize = require "sequelize" - options = _.extend {logging:false}, Settings.analytics.postgres - sequelize = new Sequelize( - Settings.analytics.postgres.database, - Settings.analytics.postgres.username, - Settings.analytics.postgres.password, - options - ) - - Event = sequelize.define("Event", { - user_id: Sequelize.STRING, - event: Sequelize.STRING, - segmentation: Sequelize.JSONB - }) - module.exports = - recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) -> - if user_id? and typeof(user_id) != "string" - user_id = user_id.toString() - if user_id == Settings.smokeTest?.userId - # Don't record smoke tests analytics - return callback() - Event - .create({ user_id, event, segmentation }) - .then( - (result) -> callback(), - (error) -> - logger.err {err: error, user_id, event, segmentation}, "error recording analytics event" - callback(error) - ) - - sync: () -> sequelize.sync() \ No newline at end of file +module.exports = + + recordEvent: (user_id, event, segmentation = {}, callback = (error) ->) -> + opts = + body: + event:event + segmentation:segmentation + json:true + method:"POST" + timeout:1000 + url: "#{settings.apis.analytics.url}/user/#{user_id}/event" + request opts, callback + + + getLastOccurance: (user_id, event, callback = (error) ->) -> + opts = + body: + event:event + json:true + method:"POST" + timeout:1000 + url: "#{settings.apis.analytics.url}/user/#{user_id}/event/last_occurnace" + request opts, (err, response, body)-> + if err? + console.log response, opts + logger.err {user_id, err}, "error getting last occurance of event" + return callback err + else + return callback null, body \ No newline at end of file diff --git a/services/web/app/coffee/Features/Announcements/AnnouncementsController.coffee b/services/web/app/coffee/Features/Announcements/AnnouncementsController.coffee new file mode 100644 index 0000000000..5545ebcc26 --- /dev/null +++ b/services/web/app/coffee/Features/Announcements/AnnouncementsController.coffee @@ -0,0 +1,21 @@ +AnnouncementsHandler = require("./AnnouncementsHandler") +AuthenticationController = require("../Authentication/AuthenticationController") +logger = require("logger-sharelatex") + + +module.exports = + + getUndreadAnnouncements: (req, res, next)-> + user_id = AuthenticationController.getLoggedInUserId(req) + logger.log {user_id}, "getting unread announcements" + AnnouncementsHandler.getUnreadAnnouncements user_id, (err, announcements)-> + if err? + logger.err {err, user_id}, "unable to get unread announcements" + next(err) + else + res.json announcements + + + + + diff --git a/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee b/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee new file mode 100644 index 0000000000..b1173dbb78 --- /dev/null +++ b/services/web/app/coffee/Features/Announcements/AnnouncementsHandler.coffee @@ -0,0 +1,36 @@ +AnalyticsManager = require("../Analytics/AnalyticsManager") +BlogHandler = require("../Blog/BlogHandler") +async = require("async") +_ = require("lodash") +logger = require("logger-sharelatex") +settings = require("settings-sharelatex") + +module.exports = + + getUnreadAnnouncements : (user_id, callback = (err, announcements)->)-> + if !settings?.apis?.analytics?.url? or !settings.apis.blog.url? + return callback null, [] + + async.parallel { + lastEvent: (cb)-> + AnalyticsManager.getLastOccurance user_id, "announcement-alert-dismissed", cb + announcements: (cb)-> + BlogHandler.getLatestAnnouncements cb + }, (err, results)-> + if err? + logger.err err:err, user_id:user_id, "error getting unread announcements" + return callback(err) + + announcements = _.sortBy(results.announcements, "date").reverse() + + lastSeenBlogId = results?.lastEvent?.segmentation?.blogPostId + + announcementIndex = _.findIndex announcements, (announcement)-> + announcement.id == lastSeenBlogId + + if announcementIndex != -1 + announcements = announcements.slice(0, announcementIndex) + + logger.log announcementsLength:announcements?.length, user_id:user_id, "returning announcements" + + callback null, announcements diff --git a/services/web/app/coffee/Features/Blog/BlogHandler.coffee b/services/web/app/coffee/Features/Blog/BlogHandler.coffee new file mode 100644 index 0000000000..6f8f41b5fd --- /dev/null +++ b/services/web/app/coffee/Features/Blog/BlogHandler.coffee @@ -0,0 +1,24 @@ +request = require "request" +settings = require "settings-sharelatex" +_ = require("underscore") +logger = require "logger-sharelatex" + +module.exports = BlogHandler = + + getLatestAnnouncements: (callback)-> + blogUrl = "#{settings.apis.blog.url}/blog/latestannouncements.json" + opts = + url:blogUrl + json:true + timeout:500 + request.get opts, (err, res, announcements)-> + if err? + return callback err + if res.statusCode != 200 + return callback("blog announcement returned non 200") + logger.log announcementsLength: announcements?.length, "announcements returned" + announcements = _.map announcements, (announcement)-> + announcement.url = "/blog#{announcement.url}" + announcement.date = new Date(announcement.date) + return announcement + callback(err, announcements) diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 440534bf11..69aa698137 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -20,6 +20,8 @@ ProjectGetter = require("./ProjectGetter") PrivilegeLevels = require("../Authorization/PrivilegeLevels") AuthenticationController = require("../Authentication/AuthenticationController") PackageVersions = require("../../infrastructure/PackageVersions") +BlogHandler = require("../Blog/BlogHandler") + module.exports = ProjectController = @@ -153,6 +155,8 @@ module.exports = ProjectController = return next(err) logger.log results:results, user_id:user_id, "rendering project list" tags = results.tags[0] + + notifications = require("underscore").map results.notifications, (notification)-> notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts) return notification diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index a027f6359e..14ac3b8d22 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -39,6 +39,7 @@ ReferencesController = require('./Features/References/ReferencesController') AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlewear') BetaProgramController = require('./Features/BetaProgram/BetaProgramController') AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter') +AnnouncementsController = require("./Features/Announcements/AnnouncementsController") logger = require("logger-sharelatex") _ = require("underscore") @@ -187,6 +188,9 @@ module.exports = class Router webRouter.get '/notifications', AuthenticationController.requireLogin(), NotificationsController.getAllUnreadNotifications webRouter.delete '/notifications/:notification_id', AuthenticationController.requireLogin(), NotificationsController.markNotificationAsRead + webRouter.get '/announcements', AuthenticationController.requireLogin(), AnnouncementsController.getUndreadAnnouncements + + # Deprecated in favour of /internal/project/:project_id but still used by versioning apiRouter.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.jade index a6a3957720..196d96d044 100644 --- a/services/web/app/views/project/list.jade +++ b/services/web/app/views/project/list.jade @@ -10,7 +10,7 @@ block content window.data = { projects: !{JSON.stringify(projects).replace(/\//g, '\\/')}, tags: !{JSON.stringify(tags).replace(/\//g, '\\/')}, - notifications: !{JSON.stringify(notifications).replace(/\//g, '\\/')} + notifications: !{JSON.stringify(notifications).replace(/\//g, '\\/')}, }; window.algolia = { institutions: { @@ -21,7 +21,11 @@ block content .content.content-alt(ng-controller="ProjectPageController") .container - + + div(ng-controller="AnnouncementsController", ng-cloak) + .alert.alert-success(ng-show="dataRecived") + a(href, ng-click="openLink()") {{title}} and {{totalAnnouncements}} others + .row(ng-cloak) span(ng-if="projects.length > 0") aside.col-md-2.col-xs-3 diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index daf16ae1f4..280f79d170 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -108,7 +108,8 @@ module.exports = settings = # url: "http://localhost:3040" notifications: url: "http://localhost:3042" - + + templates: user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2" showSocialButtons: false diff --git a/services/web/package.json b/services/web/package.json index d302cc30d8..929a102744 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -25,6 +25,7 @@ "dateformat": "1.0.4-1.2.3", "express": "4.13.0", "express-session": "1.11.3", + "feedparser": "^1.1.5", "grunt": "^0.4.5", "heapdump": "^0.3.7", "http-proxy": "^1.8.1", @@ -49,8 +50,6 @@ "passport": "^0.3.2", "passport-ldapauth": "^0.6.0", "passport-local": "^1.0.0", - "pg": "^6.0.3", - "pg-hstore": "^2.3.2", "redback": "0.4.0", "redis": "0.10.1", "redis-sharelatex": "0.0.9", diff --git a/services/web/public/coffee/main.coffee b/services/web/public/coffee/main.coffee index 60cc38ae6a..471870f280 100644 --- a/services/web/public/coffee/main.coffee +++ b/services/web/public/coffee/main.coffee @@ -14,6 +14,7 @@ define [ "main/subscription-dashboard" "main/new-subscription" "main/annual-upgrade" + "main/announcements" "main/register-users" "main/subscription/group-subscription-invite-controller" "main/contact-us" diff --git a/services/web/public/coffee/main/announcements.coffee b/services/web/public/coffee/main/announcements.coffee new file mode 100644 index 0000000000..b0960bcc09 --- /dev/null +++ b/services/web/public/coffee/main/announcements.coffee @@ -0,0 +1,20 @@ +define [ + "base" +], (App) -> + App.controller "AnnouncementsController", ($scope, $http, event_tracking, $window) -> + + $scope.dataRecived = false + announcement = null + $http.get("/announcements").success (announcements) -> + if announcements?[0]? + announcement = announcements[0] + $scope.title = announcement.title + $scope.totalAnnouncements = announcements.length + $scope.dataRecived = true + + dismissannouncement = -> + event_tracking.sendMB "announcement-alert-dismissed", {blogPostId:announcement.id} + + $scope.openLink = -> + dismissannouncement() + $window.location.href = announcement.url diff --git a/services/web/public/coffee/main/event.coffee b/services/web/public/coffee/main/event.coffee index b2847bc5a0..b788057b77 100644 --- a/services/web/public/coffee/main/event.coffee +++ b/services/web/public/coffee/main/event.coffee @@ -53,16 +53,6 @@ define [ @sendMB key, segmentation } - # App.directive "countlyTrack", () -> - # return { - # restrict: "A" - # scope: false, - # link: (scope, el, attrs) -> - # eventKey = attrs.countlyTrack - # if (eventKey?) - # el.on "click", () -> - # console.log eventKey - # } #header $('.navbar a').on "click", (e)-> diff --git a/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee b/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee new file mode 100644 index 0000000000..7578ea3a64 --- /dev/null +++ b/services/web/test/UnitTests/coffee/Announcement/AnnouncementsHandlerTests.coffee @@ -0,0 +1,72 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +modulePath = path.join __dirname, '../../../../app/js/Features/Announcements/AnnouncementsHandler' +sinon = require("sinon") +expect = require("chai").expect + + +describe 'AnnouncementsHandler', -> + + beforeEach -> + @user_id = "some_id" + @AnalyticsManager = + getLastOccurance: sinon.stub() + @BlogHandler = + getLatestAnnouncements:sinon.stub() + @handler = SandboxedModule.require modulePath, requires: + "../Analytics/AnalyticsManager":@AnalyticsManager + "../Blog/BlogHandler":@BlogHandler + "logger-sharelatex": + log:-> + + + describe "getUnreadAnnouncements", -> + beforeEach -> + @stubbedAnnouncements = [ + { + date: new Date(1478836800000), + id: '/2016/11/01/introducting-latex-code-checker' + }, { + date: new Date(1308369600000), + id: '/2013/08/02/thesis-series-pt1' + }, { + date: new Date(1108369600000), + id: '/2011/08/04/somethingelse' + }, { + date: new Date(1208369600000), + id: '/2014/04/12/title-date-irrelivant' + } + ] + @BlogHandler.getLatestAnnouncements.callsArgWith(0, null, @stubbedAnnouncements) + + + it "should return all announcements if there are no getLastOccurance", (done)-> + @AnalyticsManager.getLastOccurance.callsArgWith(2, null, []) + @handler.getUnreadAnnouncements @user_id, (err, announcements)=> + announcements.length.should.equal 4 + done() + + it "should should be sorted again to ensure correct order", (done)-> + @AnalyticsManager.getLastOccurance.callsArgWith(2, null, []) + @handler.getUnreadAnnouncements @user_id, (err, announcements)=> + announcements[3].should.equal @stubbedAnnouncements[2] + announcements[2].should.equal @stubbedAnnouncements[3] + announcements[1].should.equal @stubbedAnnouncements[1] + announcements[0].should.equal @stubbedAnnouncements[0] + done() + + it "should return ones older than the last blog id", (done)-> + @AnalyticsManager.getLastOccurance.callsArgWith(2, null, {segmentation:{blogPostId:"/2014/04/12/title-date-irrelivant"}}) + @handler.getUnreadAnnouncements @user_id, (err, announcements)=> + announcements.length.should.equal 2 + announcements[0].id.should.equal @stubbedAnnouncements[0].id + announcements[1].id.should.equal @stubbedAnnouncements[1].id + done() + + it "should return none when the latest id is the first element", (done)-> + @AnalyticsManager.getLastOccurance.callsArgWith(2, null, {segmentation:{blogPostId:"/2016/11/01/introducting-latex-code-checker"}}) + @handler.getUnreadAnnouncements @user_id, (err, announcements)=> + announcements.length.should.equal 0 + done()