From 3e93a948d6fd3af5d6a699bd44ebd7597e196858 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sat, 28 May 2016 18:47:42 +0100 Subject: [PATCH 1/5] Added POC of websockets to todos --- Gemfile | 3 ++ Gemfile.lock | 9 +++++ app/assets/javascripts/application.js.coffee | 14 +++++++ app/models/todo.rb | 12 ++++++ app/views/layouts/header/_default.html.haml | 5 +-- scripts/socket.rb | 41 ++++++++++++++++++++ 6 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 scripts/socket.rb diff --git a/Gemfile b/Gemfile index b897dc0a7412..af3c10577209 100644 --- a/Gemfile +++ b/Gemfile @@ -334,3 +334,6 @@ gem "paranoia", "~> 2.0" # Health check gem 'health_check', '~> 1.5.1' + +gem 'em-websocket' +gem 'em-hiredis' diff --git a/Gemfile.lock b/Gemfile.lock index fa2b72b2524f..6291b849e901 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -177,6 +177,12 @@ GEM railties (>= 3.2) dropzonejs-rails (0.7.2) rails (> 3.1) + em-hiredis (0.3.1) + eventmachine (~> 1.0) + hiredis (~> 0.6.0) + em-websocket (0.5.0) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.5.3) email_reply_parser (0.5.8) email_spec (1.6.0) launchy (~> 2.1) @@ -409,6 +415,7 @@ GEM hipchat (1.5.2) httparty mimemagic + hiredis (0.6.1) html-pipeline (1.11.0) activesupport (>= 2) nokogiri (~> 1.4) @@ -925,6 +932,8 @@ DEPENDENCIES diffy (~> 3.0.3) doorkeeper (~> 3.1) dropzonejs-rails (~> 0.7.1) + em-hiredis + em-websocket email_reply_parser (~> 0.5.8) email_spec (~> 1.6.0) factory_girl_rails (~> 4.6.0) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 7c547ac843b7..b2a7062b156a 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -263,3 +263,17 @@ $ -> checkInitialSidebarSize() new Aside() + + if not window.socket? + window.socket = new WebSocket "ws://localhost:8080/user/#{gon.current_user_id}" + window.socket.onmessage = (event) -> + data = JSON.parse(event.data) + + if data.channel is 'todos' + $('.todos-pending-count') + .text data.data.count + + if data.data.count is 0 + $('.todos-pending-count').addClass 'hidden' + else + $('.todos-pending-count').removeClass 'hidden' diff --git a/app/models/todo.rb b/app/models/todo.rb index 3a0913733291..f2e9ba96c9c9 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -15,6 +15,8 @@ class Todo < ActiveRecord::Base validates :target_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? + after_create :publish_event + default_scope { reorder(id: :desc) } scope :pending, -> { with_state(:pending) } @@ -61,4 +63,14 @@ def target_reference target.to_reference end end + + private + def publish_event + Gitlab::Redis.with do |redis| + data = { + count: self.user.todos.pending.count + } + redis.publish("todos.#{self.user_id}", {count: self.user.todos.pending.count}.to_json) + end + end end diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c33740e23fad..82f0d787aedd 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -27,9 +27,8 @@ %li = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('bell fw') - - unless todos_pending_count == 0 - %span.badge.todos-pending-count - = todos_pending_count + %span.badge.todos-pending-count{ class: ('hidden' if todos_pending_count == 0)} + = todos_pending_count - if current_user.can_create_project? %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do diff --git a/scripts/socket.rb b/scripts/socket.rb new file mode 100644 index 000000000000..fcf8c0f70133 --- /dev/null +++ b/scripts/socket.rb @@ -0,0 +1,41 @@ +require "em-websocket" +require "em-hiredis" +require "json" + +EM.run do + @channels = Hash.new + EM::WebSocket.start(host: "0.0.0.0", port: "8080", debug: false) do |socket| + socket.onopen do |handshake| + channel = channel_for_socket(handshake) + + @redis = EM::Hiredis.connect("unix:///Users/phil/Projects/gdk-ce/redis/redis.socket") + pubsub = @redis.pubsub + pubsub.subscribe "todos.#{path(handshake)}" + pubsub.on(:message) do |redis_channel, message| + data = { + channel: redis_channel.split(".").first, + data: JSON.parse(message) + } + channel.push data.to_json + end + + sid = channel.subscribe do |msg| + socket.send msg + end + + socket.onclose do + pubsub.unsubscribe "todos.#{path(handshake)}" + channel.unsubscribe(sid) + end + end + end + + def path(handshake) + handshake.path.split("/").last + end + + def channel_for_socket(handshake) + channel_path = path(handshake) + @channels[channel_path] ||= EM::Channel.new + end +end -- GitLab From 85f469b00a1b04a4d6d546b8b4b6390ae2fb2e82 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sun, 29 May 2016 22:34:39 +0100 Subject: [PATCH 2/5] Uses Sidekiq to queue the redis publish --- app/models/todo.rb | 7 +------ app/workers/todo_worker.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 app/workers/todo_worker.rb diff --git a/app/models/todo.rb b/app/models/todo.rb index f2e9ba96c9c9..1f0bffb2cae0 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -66,11 +66,6 @@ def target_reference private def publish_event - Gitlab::Redis.with do |redis| - data = { - count: self.user.todos.pending.count - } - redis.publish("todos.#{self.user_id}", {count: self.user.todos.pending.count}.to_json) - end + Sidekiq::Client.enqueue(TodoWorker, self.id) end end diff --git a/app/workers/todo_worker.rb b/app/workers/todo_worker.rb new file mode 100644 index 000000000000..3ecb9bfc4a82 --- /dev/null +++ b/app/workers/todo_worker.rb @@ -0,0 +1,15 @@ +class TodoWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(todo_id) + Gitlab::Redis.with do |redis| + todo = Todo.find(todo_id) + data = { + count: todo.user.todos.pending.count + } + redis.publish("todos.#{todo.user_id}", {count: todo.user.todos.pending.count}.to_json) + end + end +end -- GitLab From bd49c1044aa6aa5a9bfb507e05e23f46cf560786 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Sun, 29 May 2016 22:39:16 +0100 Subject: [PATCH 3/5] Updates todo count on save --- app/assets/javascripts/todos.js.coffee | 6 ------ app/models/todo.rb | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/assets/javascripts/todos.js.coffee b/app/assets/javascripts/todos.js.coffee index 10bef96f43d5..19c87e4f601c 100644 --- a/app/assets/javascripts/todos.js.coffee +++ b/app/assets/javascripts/todos.js.coffee @@ -34,7 +34,6 @@ class @Todos success: (data) => @redirectIfNeeded data.count @clearDone $this.closest('li') - @updateBadges data allDoneClicked: (e) => e.preventDefault() @@ -51,7 +50,6 @@ class @Todos success: (data) => $this.remove() $('.js-todos-list').remove() - @updateBadges data clearDone: ($row) -> $ul = $row.closest('ul') @@ -60,10 +58,6 @@ class @Todos if not $ul.find('li').length $ul.parents('.panel').remove() - updateBadges: (data) -> - $('.todos-pending .badge, .todos-pending-count').text data.count - $('.todos-done .badge').text data.done_count - getTotalPages: -> @el.data('totalPages') diff --git a/app/models/todo.rb b/app/models/todo.rb index 1f0bffb2cae0..e903dcbc9533 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -16,6 +16,7 @@ class Todo < ActiveRecord::Base validates :commit_id, presence: true, if: :for_commit? after_create :publish_event + after_save :publish_event default_scope { reorder(id: :desc) } -- GitLab From 02fff124caa2c3fa79e057de427b6beba0a6531f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 30 May 2016 11:26:49 +0100 Subject: [PATCH 4/5] updates to sockets file --- app/models/todo.rb | 12 +++++++---- app/workers/todo_worker.rb | 15 -------------- scripts/socket.rb | 42 ++++++++++++++++++++++++-------------- 3 files changed, 35 insertions(+), 34 deletions(-) delete mode 100644 app/workers/todo_worker.rb diff --git a/app/models/todo.rb b/app/models/todo.rb index e903dcbc9533..d79eaab99199 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -15,8 +15,7 @@ class Todo < ActiveRecord::Base validates :target_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - after_create :publish_event - after_save :publish_event + after_commit :publish_event default_scope { reorder(id: :desc) } @@ -65,8 +64,13 @@ def target_reference end end - private def publish_event - Sidekiq::Client.enqueue(TodoWorker, self.id) + Gitlab::Redis.with do |redis| + data = { + user_id: self.user_id, + count: self.user.todos.pending.count, + } + redis.publish("todos", data.to_json) + end end end diff --git a/app/workers/todo_worker.rb b/app/workers/todo_worker.rb deleted file mode 100644 index 3ecb9bfc4a82..000000000000 --- a/app/workers/todo_worker.rb +++ /dev/null @@ -1,15 +0,0 @@ -class TodoWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(todo_id) - Gitlab::Redis.with do |redis| - todo = Todo.find(todo_id) - data = { - count: todo.user.todos.pending.count - } - redis.publish("todos.#{todo.user_id}", {count: todo.user.todos.pending.count}.to_json) - end - end -end diff --git a/scripts/socket.rb b/scripts/socket.rb index fcf8c0f70133..5e0b1725702f 100644 --- a/scripts/socket.rb +++ b/scripts/socket.rb @@ -4,28 +4,36 @@ EM.run do @channels = Hash.new + @redis = EM::Hiredis.connect("unix:///Users/phil/Projects/gdk-ce/redis/redis.socket") + pubsub = @redis.pubsub + pubsub.subscribe "todos" + pubsub.on(:message) do |redis_channel, message| + message = JSON.parse(message) + data = { + channel: redis_channel, + data: message + } + + if redis_channel == "todos" + @channels.each do |key, channel| + if channel[:user_id].to_i == message["user_id"] + channel[:channel].push data.to_json + end + end + end + end + EM::WebSocket.start(host: "0.0.0.0", port: "8080", debug: false) do |socket| socket.onopen do |handshake| channel = channel_for_socket(handshake) - @redis = EM::Hiredis.connect("unix:///Users/phil/Projects/gdk-ce/redis/redis.socket") - pubsub = @redis.pubsub - pubsub.subscribe "todos.#{path(handshake)}" - pubsub.on(:message) do |redis_channel, message| - data = { - channel: redis_channel.split(".").first, - data: JSON.parse(message) - } - channel.push data.to_json - end - - sid = channel.subscribe do |msg| + sid = channel[:channel].subscribe do |msg| socket.send msg end socket.onclose do - pubsub.unsubscribe "todos.#{path(handshake)}" - channel.unsubscribe(sid) + channel[:channel].unsubscribe(sid) + @channels.delete(path(handshake)) end end end @@ -36,6 +44,10 @@ def path(handshake) def channel_for_socket(handshake) channel_path = path(handshake) - @channels[channel_path] ||= EM::Channel.new + @channels[channel_path] ||= { + channel: EM::Channel.new, + user_id: channel_path, + subscribed: ['todos'] + } end end -- GitLab From 2a53154049a477539d47569dfe4ab8e09eb7f632 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 30 May 2016 11:42:54 +0100 Subject: [PATCH 5/5] Changed socket path --- scripts/socket.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/scripts/socket.rb b/scripts/socket.rb index 5e0b1725702f..2a9f31dda51b 100644 --- a/scripts/socket.rb +++ b/scripts/socket.rb @@ -4,7 +4,7 @@ EM.run do @channels = Hash.new - @redis = EM::Hiredis.connect("unix:///Users/phil/Projects/gdk-ce/redis/redis.socket") + @redis = EM::Hiredis.connect("unix://#{File.expand_path('..')}/redis/redis.socket") pubsub = @redis.pubsub pubsub.subscribe "todos" pubsub.on(:message) do |redis_channel, message| @@ -15,11 +15,8 @@ } if redis_channel == "todos" - @channels.each do |key, channel| - if channel[:user_id].to_i == message["user_id"] - channel[:channel].push data.to_json - end - end + channel = @channels[message["user_id"].to_s] + channel[:channel].push data.to_json end end -- GitLab