Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Form Collaboration: Replace Poxa with Action Cable #3105

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ AWS_SECRET_ACCESS_KEY=xxx
AWS_REGION=xxx
AWS_S3_BUCKET_NAME=xxx
DISPLAY_SOCIAL_MOBILITY_AWARD=true
PUSHER_SOCKET_HOST=localhost
PUSHER_WS_PORT=8080
PUSHER_APP_ID=app_id
PUSHER_APP_KEY=app_key
PUSHER_SECRET=secret
GOV_UK_NOTIFY_API_KEY=key
GOV_UK_NOTIFY_API_TEMPLATE_ID=id
SESSION_TIMEOUT=1
Expand Down
4 changes: 1 addition & 3 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@ gem "redis-store", "~> 1.4"
# We use it for communicating with api.debounce.io
gem "rest-client"

# We are using Pusher with Poxa server for realtime collaborator editing
gem "pusher", "0.15.2"

# Text Search
gem "pg_search", "~> 2.3.3"

Expand Down Expand Up @@ -177,6 +174,7 @@ group :development, :test do
end

group :test do
gem "action-cable-testing"
gem "factory_bot_rails"
gem "capybara", "~> 3.39.0"
gem "poltergeist"
Expand Down
9 changes: 3 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
action-cable-testing (0.6.1)
actioncable (>= 5.0)
actioncable (7.0.8.4)
actionpack (= 7.0.8.4)
activesupport (= 7.0.8.4)
Expand Down Expand Up @@ -429,11 +431,6 @@ GEM
nio4r (~> 2.0)
pundit (0.3.0)
activesupport (>= 3.0.0)
pusher (0.15.2)
httpclient (~> 2.5)
multi_json (~> 1.0)
pusher-signature (~> 0.1.8)
pusher-signature (0.1.8)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.9)
Expand Down Expand Up @@ -732,6 +729,7 @@ PLATFORMS
x86_64-linux

DEPENDENCIES
action-cable-testing
active_hash
amoeba (= 3.0.0)
binding_of_caller
Expand Down Expand Up @@ -788,7 +786,6 @@ DEPENDENCIES
pry-byebug
puma (~> 6.4.3)
pundit (~> 0.3)
pusher (= 0.15.2)
rack-cors (~> 1.0)
rack-mini-profiler (>= 0.10.1)
rack-protection (= 3.0.5)
Expand Down
6 changes: 3 additions & 3 deletions app/assets/javascripts/application.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#= require moment.min
#= require core
#= require libs/suchi/isOld.js
#= require libs/pusher.min.js
#= require mobile
#= require browser-check
#= require vendor/zxcvbn
Expand All @@ -23,6 +22,7 @@
#= require_tree ./frontend
#= require ./frontend/financial_summary_tables/fst_base.js
#= require_tree ./frontend/financial_summary_tables
#= require channels
#
#= require offline

Expand Down Expand Up @@ -740,7 +740,7 @@ jQuery ->

CollaboratorsLog.log("[COLLABORATOR MODE] ------------ redirect_url ----------- " + redirect_url)

if ApplicationCollaboratorsAccessManager.does_im_current_editor()
if ApplicationCollaboratorsAccessManager.i_am_current_editor()
CollaboratorsLog.log("[COLLABORATOR MODE] -------------I'm EDITOR---------- SAVE AND REDIRECT")
# If I'm current editor
# -> then save form data and once it saved redirect me to proper section in a callback
Expand All @@ -755,7 +755,7 @@ jQuery ->
window.location.href = redirect_url

else
CollaboratorsLog.log("[STANDART MODE] ----------------------- ")
CollaboratorsLog.log("[STANDARD MODE] ----------------------- ")

autosave()

Expand Down
6 changes: 6 additions & 0 deletions app/assets/javascripts/channels/index.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#= require action_cable
#= require_self
#= require_tree .

@App = {}
App.cable = ActionCable.createConsumer()
Original file line number Diff line number Diff line change
@@ -1,133 +1,59 @@
window.ApplicationCollaboratorsAccessManager =

register_member: (member) ->
member_info = ApplicationCollaboratorsAccessManager.get_member_info(member)

if member.id is String(window.pusher_current_channel.members.me.id)
CollaboratorsLog.log("[ME IS MEMBER] ------------------------- " + member_info)
else
CollaboratorsLog.log("[ANOTHER MEMBER] ------------------------- " + member_info)

set_access_mode: () ->
editor = ApplicationCollaboratorsAccessManager.current_editor()
CollaboratorsLog.log("[SET ACCESS MODE] ------------ CURRENT EDITOR IS -------- " + editor.id)

ApplicationCollaboratorsAccessManager.track_current_editor(editor)

if ApplicationCollaboratorsAccessManager.does_im_current_editor()
CollaboratorsLog.log("[EDITOR MODE] ----------------------")

else
CollaboratorsLog.log("[READ ONLY MODE] ----------------------")

ApplicationCollaboratorsFormLocker.lock_current_form_section()
ApplicationCollaboratorsEditorBar.render_collaborators_bar()
editor_id = ApplicationCollaboratorsAccessManager.current_editor().id

login_to_the_room: (current_step) ->
# Unsubscribe client from current section room
room = window.pusher_current_channel.name;
window.pusher.unsubscribe(window.pusher_current_channel.name);
CollaboratorsLog.log("[SET ACCESS MODE] ------------ CURRENT EDITOR IS -------- " + editor_id)
previous_editor_id = window.last_editor_id

# Clean up last editor id as we switched to another section
window.pusher_last_editor_id = null;
ApplicationCollaboratorsAccessManager.track_current_editor(editor_id)

# And hide collaborators info bar
ApplicationCollaboratorsEditorBar.hide_collaborators_bar()

CollaboratorsLog.log("[TAB SWITCH] ------------- Log out from (" + room + ") to " + current_step + " --------------------")

# Set new pusher section
window.pusher_section = current_step;
if ApplicationCollaboratorsAccessManager.i_am_current_editor()
CollaboratorsLog.log("[EDITOR MODE] ----------------------")

# Set new login timestamp for user
timestamp = new Date().getTime();
ApplicationCollaboratorsEditorBar.hide_collaborators_bar()

# Init new room based on selected section
ApplicationCollaboratorsConnectionManager.init_pusher(timestamp)
ApplicationCollaboratorsConnectionManager.init_room()
if previous_editor_id != undefined && previous_editor_id != ApplicationCollaboratorsAccessManager.current_editor().id
CollaboratorsLog.log("[NOW IM EDITOR] ---- REFRESHING PAGE")

my_position_in_members_queue: () ->
i = 0
my_index = 0
ApplicationCollaboratorsEditorBar.show_loading_bar()

members = window.pusher_current_channel.members
me = members.me
# Redirect user to same page in order to get the new changes
redirect_url = $(".js-step-link.step-current a").attr('href')
redirect_url += "&form_refresh=true"

members.each (member) ->
if member.id is String(me.id)
my_index = i
#
# In case it was an attempt to submit and validation errors are present
# then we are passing validate_on_form_load option
# in order to show validation errors to user after redirection
#
if window.location.href.search("validate_on_form_load") > 0
redirect_url += "&validate_on_form_load=true"

i += 1
window.location.href = redirect_url
else
CollaboratorsLog.log("[READ ONLY MODE] ----------------------")

my_index
ApplicationCollaboratorsFormLocker.lock_current_form_section()
ApplicationCollaboratorsEditorBar.render_collaborators_bar()

does_im_current_editor: () ->
ApplicationCollaboratorsAccessManager.my_position_in_members_queue() == 0
i_am_current_editor: () ->
ApplicationCollaboratorsAccessManager.current_editor().id == window.user_id &&
window.tab_ident == ApplicationCollaboratorsAccessManager.current_editor().tab_ident

im_in_viewer_mode: () ->
!ApplicationCollaboratorsAccessManager.does_im_current_editor()

normalized_members_array: () ->
list = []

members = window.pusher_current_channel.members
members.each (member) ->
list.push(member)

return list

get_member_info: (m) ->
return ("ID: " + m.id + ", NAME: " + m.info.name + ", JOINED AT: " + m.info.joined_at)
!ApplicationCollaboratorsAccessManager.i_am_current_editor()

current_editor: () ->
editor = ApplicationCollaboratorsAccessManager.normalized_members_array()[0]
member_info = ApplicationCollaboratorsAccessManager.get_member_info(editor)

CollaboratorsLog.log("[CURRENT EDITOR] ------------- " + member_info + " --------------------")

return editor

try_mark_as_editor: () ->
editor = ApplicationCollaboratorsAccessManager.current_editor()

if ApplicationCollaboratorsAccessManager.can_be_marked_as_editor(editor)
CollaboratorsLog.log("[NOW IM EDITOR] ----------------------")

ApplicationCollaboratorsEditorBar.show_loading_bar()

# Redirect user to same page in order to login him
redirect_url = $(".js-step-link.step-current a").attr('href')
redirect_url += "&form_refresh=true"

#
# In case if was attempt to submit and validation errors are present
# then we are passing validate_on_form_load option
# in order to show validation errors to user after redirection
#
if window.location.href.search("validate_on_form_load") > 0
redirect_url += "&validate_on_form_load=true"

window.location.href = redirect_url
else
#
# If I'm not next in queue to be marked as editor
# or I'm already editor
#
# Then do not need to refresh page
#

if window.pusher_last_editor_id == editor.id
CollaboratorsLog.log("[ACCESS CALC] ---------------------- I'M ALREADY EDITOR OF CURRENT TAB!")
else
CollaboratorsLog.log("[ACCESS CALC] ---------------------- NOPE - I STILL HAVE TO WAIT!")

track_current_editor: (editor) ->
window.pusher_last_editor_id = editor.id

can_be_marked_as_editor: (editor) ->
#
# If I'm next in queue to join room as editor
# and I'm not previous editor
#
ApplicationCollaboratorsAccessManager.does_im_current_editor() &&
window.pusher_last_editor_id != editor.id
editor = window.current_channel_members.split("/").find((el) => el.includes("EDITOR")).split(":")
editor_info = {
id: editor[0],
tab_ident: editor[1],
email: editor[2],
name: editor[3]
}

editor_info

track_current_editor: (editor_id) ->
window.last_editor_id = editor_id
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
window.ApplicationCollaboratorsConnectionManager =

init: (form_id, p_host, p_port, p_key, rails_env, timestamp) ->
init: (form_id, user_id, rails_env) ->

#
# Checking if browser supports WebSockets technology
Expand All @@ -9,79 +9,27 @@ window.ApplicationCollaboratorsConnectionManager =

window.form_id = form_id

window.pusher_host = p_host
window.pusher_port = p_port

window.pusher_key = p_key
window.rails_env = rails_env
window.user_id = user_id

if rails_env == "staging" || rails_env == "production"
window.pusher_encrypted = true
else
window.pusher_encrypted = false
window.tab_ident = ApplicationCollaboratorsConnectionManager.get_tab_ident()

window.pusher_section = $(".js-step-link.step-current").attr('data-step')
window.form_section = $(".js-step-link.step-current").attr('data-step')

ApplicationCollaboratorsConnectionManager.init_pusher(timestamp)
ApplicationCollaboratorsConnectionManager.init_room()
ApplicationCollaboratorsGeneralRoomTracking.login()

init_pusher: (timestamp) ->
CollaboratorsLog.log("[PUSHER INIT] form_id: " + window.form_id + ", host: " + window.pusher_host + ", port: " + window.pusher_port + ", key: " + window.pusher_key + ", enc: " + window.pusher_encrypted + ", section: " + window.pusher_section)

# Init Pusher to use own Poxa server
pusher_ops = {
wsHost: window.pusher_host,
wsPort: window.pusher_port,
authTransport: 'jsonp',
authEndpoint: "/users/form_answers/" + window.form_id + "/collaborator_access/auth/" + window.pusher_section + "/" + timestamp
}

# Use encryption on live and staging
# as they are using HTTPS
#
if window.pusher_encrypted == "true"
pusher_ops["encrypted"] = true
CollaboratorsLog.log("[PUSHER OPS] encryption turned on!")
else
CollaboratorsLog.log("[PUSHER OPS] encryption turned off!")

window.pusher = new Pusher(window.pusher_key, pusher_ops)

# Check connection status
connection_status = pusher.connection.state
CollaboratorsLog.log("PUSHER STATUS: " + connection_status)


init_room: () ->
# Introduce new channel
channel_name = 'presence-chat-' + window.rails_env + "-" + window.form_id + '-sep-' + window.pusher_section

CollaboratorsLog.log("[PUSHER INIT ROOM] ------------------------ channel_name: " + channel_name)

window.pusher_current_channel = window.pusher.subscribe(channel_name)

# Check if subscription was successful
window.pusher_current_channel.bind 'pusher:subscription_succeeded', (members) ->
CollaboratorsLog.log('[subscription_succeeded] Count ' + members.count)

ApplicationCollaboratorsAccessManager.set_access_mode()
members.each (member) =>
ApplicationCollaboratorsAccessManager.register_member(member)

return

# Handle member removed
window.pusher_current_channel.bind 'pusher:member_removed', (member) ->
CollaboratorsLog.log('[member_removed] Count ' + window.pusher_current_channel.members.count)
channel_name = 'presence-chat-' + window.rails_env + "-" + window.form_id + '-sep-' + window.form_section

ApplicationCollaboratorsAccessManager.try_mark_as_editor()
CollaboratorsLog.log("[INIT ROOM] ------------------------ channel_name: " + channel_name)

return
window.App.collaborators = App.cable.subscriptions.create { channel: "CollaboratorsChannel", channel_name: channel_name, user_id: window.user_id, current_tab: window.tab_ident },
received: (data) ->
window.current_channel_members = data.collaborators
ApplicationCollaboratorsAccessManager.set_access_mode()

# Handle member added
window.pusher_current_channel.bind 'pusher:member_added', (member) ->
CollaboratorsLog.log('[member_added] Count ' + window.pusher_current_channel.members.count)
ApplicationCollaboratorsAccessManager.register_member(member)
get_tab_ident: () ->
document.cookie.split('; ').find((c) => c.split("=")[0] == 'public_tab_ident').split('=')[1]

return
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ window.ApplicationCollaboratorsEditorBar =

render_collaborators_bar: () ->
editor = ApplicationCollaboratorsAccessManager.current_editor()
currentEditorName = editor.info.name + " (" + editor.info.email + ")"
currentEditorName = editor.name + " (" + editor.email + ")"

members = window.pusher_current_channel.members
me = members.me
me = window.user_id

if me.info.email == editor.info.email
if me == editor.id
header = "You cannot edit this section unless you close it elsewhere first."
message = "It looks like you have already opened this section in another tab or window. To avoid data-saving issues, you can only have it open in one tab or window at a time. Please close the other tabs or windows to continue editing."
else
Expand Down
Loading
Loading