Allow recordings to be 'unlisted'

Unlisted is a state between published and unpublished. They are still
published in the server, but will not appear to anyone other than the
user that created the recording.
It is done using a metadata attribute and required several changes in how
the application handles publishing and unpublishing.
This commit is contained in:
Leonardo Crauss Daronco 2016-12-06 12:03:53 -02:00
parent 40cbc8a575
commit b518458622
12 changed files with 111 additions and 53 deletions

View File

@ -23,17 +23,21 @@
}, },
{ {
received: function(data) { received: function(data) {
var recordings = Recordings.getInstance(); var recordings = Recordings.getInstance();
var table = recordings.table.api() var table = recordings.table.api();
var row = table.row("#"+data.record_id); var row = table.row("#"+data.record_id);
if (data.action === 'update') { if (data.action === 'update') {
var rowData = row.data(); var rowData = row.data();
rowData.published = data.published
rowData.published = data.published;
rowData.listed = data.listed;
table.row("#"+data.record_id).data(rowData); table.row("#"+data.record_id).data(rowData);
recordings.draw(); recordings.draw();
var published = (data.published) ? 'published' : 'unpublished'; var status = data.published ? (data.listed ? 'published' : 'unlisted') : 'unpublished';
showAlert(I18n['recording_'+published], 4000); showAlert(I18n['recording_'+status], 4000);
} else if (data.action === 'delete') { } else if (data.action === 'delete') {
row.remove(); row.remove();
recordings.draw(); recordings.draw();

View File

@ -100,7 +100,8 @@
.tooltip('fixTitle'); .tooltip('fixTitle');
}); });
$('.center-panel-wrapper').on ('click', '.meeting-invite', function (event) { // button used to send invitations to the meeting (i.e. "mailto:" link)
$('.center-panel-wrapper').on('click', '.meeting-invite', function (event) {
var meetingURL = Meeting.getInstance().getURL(); var meetingURL = Meeting.getInstance().getURL();
var subject = $(this).data("invite-subject"); var subject = $(this).data("invite-subject");
var body = $(this).data("invite-body").replace("&&URL&&", meetingURL); var body = $(this).data("invite-body").replace("&&URL&&", meetingURL);

View File

@ -37,7 +37,7 @@ class @Recordings
{ data: "previews", orderable: false }, { data: "previews", orderable: false },
{ data: "duration", orderable: false }, { data: "duration", orderable: false },
{ data: "playbacks", orderable: false }, { data: "playbacks", orderable: false },
{ data: "published", visible: false }, { data: "listed", visible: false },
{ data: "id", orderable: false } { data: "id", orderable: false }
], ],
columnDefs: [ columnDefs: [
@ -78,18 +78,18 @@ class @Recordings
render: (data, type, row) -> render: (data, type, row) ->
if type == 'display' if type == 'display'
roomName = Meeting.getInstance().getId() roomName = Meeting.getInstance().getId()
published = row.published
publishText = if published then 'unpublish' else 'publish'
recordingActions = $('.hidden-elements').find('.recording-actions') recordingActions = $('.hidden-elements').find('.recording-actions')
recordingActions.find('.recording-update > i.default') classes = ['recording-inaccessible', 'recording-unlisted', 'recording-published']
.removeClass(PUBLISHED_CLASSES.join(' ')) if row.published
.addClass(getPublishClass(published)) if row.listed
recordingActions.find('.recording-update > i.hover') cls = classes[2]
.removeClass(PUBLISHED_CLASSES.join(' ')) else
.addClass(getPublishClass(!published)) cls = classes[1]
recordingActions.find('.recording-update') else
.attr('data-published', published) cls = classes[0]
.attr('title', I18n[publishText+'_recording']) trigger = recordingActions.find('.recording-update-trigger')
trigger.removeClass(classes.join(' '))
trigger.addClass(cls)
return recordingActions.html() return recordingActions.html()
return data return data
} }
@ -111,9 +111,10 @@ class @Recordings
html: true, html: true,
trigger: 'focus', trigger: 'focus',
title: -> title: ->
return I18n.are_you_sure; return $(this).data("popover-title");
content: -> content: ->
return $(".delete-popover-body").html() bodySelector = $(this).data("popover-body")
return $(bodySelector).html()
} }
$('#recordings').popover(options) $('#recordings').popover(options)
@ -153,17 +154,25 @@ class @Recordings
# setup click handlers for the action buttons # setup click handlers for the action buttons
setupActionHandlers: -> setupActionHandlers: ->
table_api = this.table.api() table_api = this.table.api()
@getTable().on 'click', '.recording-update', (event) -> @getTable().on 'click', '.recording-update', (event) ->
btn = $(this) btn = $(this)
row = table_api.row($(this).closest('tr')).data() row = table_api.row($(this).closest('tr')).data()
url = $('.meeting-url').val() url = $('.meeting-url').val()
id = row.id id = row.id
published = btn.data('published')
published = btn.data('visibility') == "unlisted" ||
btn.data('visibility') == "published"
listed = btn.data('visibility') == "published"
btn.prop('disabled', true) btn.prop('disabled', true)
$.ajax({ $.ajax({
method: 'PATCH', method: 'PATCH',
url: url+'/recordings/'+id, url: url+'/recordings/'+id,
data: {published: (!published).toString()} data: {
published: published.toString(),
"meta_greenlight-listed": listed.toString()
}
}).done((data) -> }).done((data) ->
).fail((data) -> ).fail((data) ->

View File

@ -20,12 +20,6 @@ $.ajaxSetup({
} }
}); });
var PUBLISHED_CLASSES = ['fa-eye-slash', 'fa-eye']
var getPublishClass = function(published) {
return PUBLISHED_CLASSES[+published];
}
var loopJoin = function() { var loopJoin = function() {
var jqxhr = Meeting.getInstance().getJoinMeetingResponse(); var jqxhr = Meeting.getInstance().getJoinMeetingResponse();
jqxhr.done(function(data) { jqxhr.done(function(data) {
@ -57,4 +51,4 @@ var showAlert = function(html, timeout_delay) {
var displayRoomURL = function() { var displayRoomURL = function() {
$('.meeting-url').val(Meeting.getInstance().getURL()); $('.meeting-url').val(Meeting.getInstance().getURL());
} };

View File

@ -86,3 +86,17 @@
} }
} }
} }
.recording-update-trigger {
&.recording-inaccessible {
color: red;
}
&.recording-unlisted {
color: #e3a91e;
}
&.recording-published {
color: green;
}
}

View File

@ -106,3 +106,7 @@ html, body {
text-align: center; text-align: center;
} }
} }
.popover {
max-width: none;
}

View File

@ -76,9 +76,11 @@ class BbbController < ApplicationController
# PATCH /rooms/:id/recordings/:record_id # PATCH /rooms/:id/recordings/:record_id
def update_recordings def update_recordings
bbb_res = bbb_update_recordings(params[:record_id], params[:published] == 'true') published = params[:published] == 'true'
metadata = params.select{ |k, v| k.match(/^meta_/) }
bbb_res = bbb_update_recordings(params[:record_id], published, metadata)
if bbb_res[:returncode] if bbb_res[:returncode]
RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id], bbb_res[:published]) RecordingUpdatesJob.perform_later(@user.encrypted_id, params[:record_id])
end end
render_bbb_response bbb_res render_bbb_response bbb_res
end end

View File

@ -19,22 +19,13 @@ class RecordingUpdatesJob < ApplicationJob
queue_as :default queue_as :default
def perform(room, record_id, published) def perform(room, record_id)
tries = 0
sleep_time = 2
while tries < 4
bbb_res = bbb_get_recordings(nil, record_id) bbb_res = bbb_get_recordings(nil, record_id)
if bbb_res[:recordings].first[:published].to_s == published recording = bbb_res[:recordings].first
ActionCable.server.broadcast "#{room}_recording_updates_channel", ActionCable.server.broadcast "#{room}_recording_updates_channel",
action: 'update', action: 'update',
record_id: record_id, record_id: record_id,
published: bbb_res[:recordings].first[:published] published: recording[:published],
break listed: bbb_is_recording_listed(recording)
end
sleep sleep_time
sleep_time = sleep_time * 2
tries += 1
end
end end
end end

View File

@ -15,6 +15,8 @@
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. # with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
module BbbApi module BbbApi
META_LISTED = "greenlight-listed"
def bbb_endpoint def bbb_endpoint
Rails.configuration.bigbluebutton_endpoint || '' Rails.configuration.bigbluebutton_endpoint || ''
end end
@ -65,7 +67,14 @@ module BbbApi
logout_url = options[:meeting_logout_url] || "#{request.base_url}" logout_url = options[:meeting_logout_url] || "#{request.base_url}"
moderator_password = random_password(12) moderator_password = random_password(12)
viewer_password = random_password(12) viewer_password = random_password(12)
meeting_options = {record: options[:meeting_recorded].to_s, logoutURL: logout_url, moderatorPW: moderator_password, attendeePW: viewer_password} meeting_options = {
record: options[:meeting_recorded].to_s,
logoutURL: logout_url,
moderatorPW: moderator_password,
attendeePW: viewer_password,
"meta_#{BbbApi::META_LISTED}": false
}
# Create the meeting # Create the meeting
bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options) bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options)
@ -148,6 +157,8 @@ module BbbApi
else else
[] []
end end
recording[:listed] = bbb_is_recording_listed(recording)
end end
res res
end end
@ -168,8 +179,11 @@ module BbbApi
response_data = bbb_exception_res exc response_data = bbb_exception_res exc
end end
def bbb_update_recordings(id, published) def bbb_update_recordings(id, published, metadata)
bbb_safe_execute :publish_recordings, id, published bbb_safe_execute :publish_recordings, id, published
params = { recordID: id }.merge(metadata)
bbb_safe_execute :send_api_request, "updateRecordings", params
end end
def bbb_delete_recordings(id) def bbb_delete_recordings(id)
@ -191,6 +205,11 @@ module BbbApi
response_data response_data
end end
def bbb_is_recording_listed(recording)
!recording[:metadata].blank? &&
recording[:metadata][BbbApi::META_LISTED.to_sym] == "true"
end
def success_join_res(join_url) def success_join_res(join_url)
{ {
returncode: true, returncode: true,

View File

@ -24,6 +24,7 @@ json.recordings do
json.end_time recording[:endTime] json.end_time recording[:endTime]
json.published recording[:published] json.published recording[:published]
json.length recording[:length] json.length recording[:length]
json.listed recording[:listed]
json.previews do json.previews do
json.array!(recording[:previews]) do |preview| json.array!(recording[:previews]) do |preview|
json.partial! 'preview', preview: preview json.partial! 'preview', preview: preview

View File

@ -50,6 +50,17 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<%= t('no') %> <%= t('no') %>
</button> </button>
</div> </div>
<div class="recording-visibility-popover">
<button type="button" class="btn btn-default btn-danger recording-update" data-visibility="inaccessible">
<%= t('innaccessible') %>
</button>
<button type="button" class="btn btn-default btn-warning recording-update" data-visibility="unlisted">
<%= t('unlisted') %>
</button>
<button type="button" class="btn btn-default btn-success recording-update" data-visibility="published">
<%= t('published') %>
</button>
</div>
<div class="alert-template"> <div class="alert-template">
<div class="alert alert-success alert-dismissible fade in" role="alert"> <div class="alert alert-success alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
@ -59,12 +70,14 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</div> </div>
</div> </div>
<div class="recording-actions"> <div class="recording-actions">
<button type="button" class="btn btn-default recording-update bottom-tooltip" data-published=""> <button type="button" class="btn btn-default has-popover recording-update-trigger"
<i class="fa default" aria-hidden="true"></i> data-placement="left" data-popover-body=".recording-visibility-popover"
<i class="fa hover" aria-hidden="true"></i> data-popover-title="<%= t('change_recording_visibility') %>">
<i class="fa fa-eye" aria-hidden="true"></i>
</button> </button>
<a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip" <a tabindex="0" role="button" class="btn btn-default has-popover delete-tooltip"
data-placement="top"> data-placement="top" data-popover-body=".delete-popover-body"
data-popover-title="<%= t('are_you_sure') %>">
<i class="fa fa-trash-o" aria-hidden="true"></i> <i class="fa fa-trash-o" aria-hidden="true"></i>
</a> </a>
</div> </div>

View File

@ -38,6 +38,8 @@
en-US: en-US:
actions: Actions actions: Actions
are_you: Are you %{name}? are_you: Are you %{name}?
are_you_sure: Are you sure?
change_recording_visibility: "Change visibility to:"
client: client:
are_you_sure: Are you sure? are_you_sure: Are you sure?
delete_recording: Delete recording delete_recording: Delete recording
@ -48,6 +50,7 @@ en-US:
publish_recording: Publish recording publish_recording: Publish recording
recording_deleted: Recording was deleted recording_deleted: Recording was deleted
recording_published: Recording was published recording_published: Recording was published
recording_unlisted: Recording was unlisted
recording_unpublished: Recording was unpublished recording_unpublished: Recording was unpublished
unpublish_recording: Hide recording unpublish_recording: Hide recording
copied: Copied copied: Copied
@ -61,6 +64,7 @@ en-US:
greet_user: Welcome, %{name} greet_user: Welcome, %{name}
greet_guest: Welcome to %{name} Meeting Space greet_guest: Welcome to %{name} Meeting Space
hi_all: Hi Everyone hi_all: Hi Everyone
innaccessible: Innaccessible
join: Join join: Join
join_session: Join the current session join_session: Join the current session
join_session_id: Join %{id} join_session_id: Join %{id}
@ -82,11 +86,13 @@ en-US:
footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}" footer_html: "%{greenlight_link} build %{version}, Powered by %{bbb_link}"
presentation: Presentation presentation: Presentation
previously_joined_meetings: Previously Joined Sessions previously_joined_meetings: Previously Joined Sessions
published: Published
return_to_room: Return to your personal room return_to_room: Return to your personal room
session_url_explanation: The session will be taking place using the following URL session_url_explanation: The session will be taking place using the following URL
start: Start start: Start
your_personal_room: Your Personal Room your_personal_room: Your Personal Room
thumbnails: Thumbnails thumbnails: Thumbnails
unlisted: Unlisted
url_copy_explanation: Copy this URL to invite others to the meeting url_copy_explanation: Copy this URL to invite others to the meeting
url_refresh_hint: Generate a new meeting URL url_refresh_hint: Generate a new meeting URL
video: Video video: Video