Compare commits

..

10 Commits

Author SHA1 Message Date
Bilal
5147806483 add specs and use i18n 2020-08-19 15:47:01 +03:00
Bilal
f5628c47df allow selecting conference option 2020-08-19 13:05:16 +03:00
Bilal
27fade2d09 remove obsolete require 2020-08-18 20:46:38 +03:00
Bilal
be5261037e Complete teams meeting creation and token refresh 2020-08-18 20:43:33 +03:00
Bilal
f25a72004e create graph api client to handle requests 2020-08-12 16:36:47 +02:00
Bilal
3352217a03 update azure_ad strategy 2020-08-12 14:44:22 +02:00
Bilal
0cc3b2bd62 Get code and token from Azure AD and store it to the DB 2020-08-12 13:52:22 +02:00
Senad Uka
eb7f8f1a43 Initial teams commit 2020-08-06 16:57:39 +00:00
Senad Uka
190ff2854b Upstream sync 2020-08-06 16:56:40 +00:00
Senad Uka
8214ba9e67 Changes 2020-08-03 21:52:04 +00:00
42 changed files with 554 additions and 827 deletions

View File

@@ -16,9 +16,10 @@ REDIS_URL=
# Required for Zoom.us integration
ZOOM_API_KEY=
ZOOM_API_SECRET=
ZOOM_ACCOUNT_NUMBER=
ZOOM_PRO_USERS_LIMIT= # defaults to 3
ZOOM_USER_TYPE= # 'pro' / 'basic'
ZOOM_ENABLE_RECORDINGS=0 # 0 / 1
ZOOM_ENABLE_RECORDINGS= # true / false (default: false)
# Token for webhooks authorization
ZOOM_VERIFICATION_TOKEN=
@@ -27,3 +28,11 @@ MUX_TOKEN_ID=
MUX_TOKEN_SECRET=
MUX_BROADCAST_SERVER_URL=rtmp://global-live.mux.com:5222/app
MUX_TEST_MODE_DISABLED=
# Required for Microsoft Azure AD Auth
AZURE_CLIENT_ID = Client App ID
AZURE_CLIENT_SECRET = Client App Secret
AZURE_TENANT_ID = Client App Tenant ID
AZURE_REDIRECT_URI = where microsoft will redirect after login, eg. http://localhost:3000/auth/azure_ad/callback
AZURE_SCOPES = Scopes required for Application, eg. 'openid email profile User.Read offline_access OnlineMeetings.ReadWrite'

View File

@@ -139,6 +139,15 @@ gem 'rack-cors'
# Ruby wrappers for the HubSpot REST API
gem "hubspot-ruby"
# OAuth
gem 'omniauth-oauth2', '~> 1.6'
# OmniAuth CSRF protection
gem 'omniauth-rails_csrf_protection', '~> 0.1.2'
# authenticate via Microsoft
# gem 'omniauth-microsoft_graph', git: 'https://github.com/m4c3/omniauth-microsoft_graph'
# gem 'omniauth-microsoft_graph'
group :development, :test, :review do
# Call "byebug" anywhere in the code to stop execution and get a debugger console
gem "byebug", "~> 11.0.1", platforms: [:mri, :mingw, :x64_mingw]

View File

@@ -220,6 +220,7 @@ GEM
activesupport (>= 4.2.0)
hashdiff (1.0.1)
hashery (2.1.2)
hashie (4.1.0)
hexapdf (0.9.3)
cmdparse (~> 3.0, >= 3.0.3)
geom2d (~> 0.2)
@@ -297,6 +298,7 @@ GEM
money (~> 6.13.2)
railties (>= 3.0)
msgpack (1.3.1)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
nio4r (2.5.2)
@@ -308,6 +310,21 @@ GEM
warden
oath-generators (1.0.1)
oath (>= 0.0.12)
oauth2 (1.4.4)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-oauth2 (1.6.0)
oauth2 (~> 1.1)
omniauth (~> 1.9)
omniauth-rails_csrf_protection (0.1.2)
actionpack (>= 4.2)
omniauth (>= 1.3.1)
parallel (1.19.1)
parity (3.2.0)
parser (2.6.5.0)
@@ -552,6 +569,8 @@ DEPENDENCIES
mux_ruby!
oath (~> 1.1.0)
oath-generators (~> 1.0.1)
omniauth-oauth2 (~> 1.6)
omniauth-rails_csrf_protection (~> 0.1.2)
parity (~> 3.2.0)
pdf-reader (~> 2.1.0)
pdfkit (~> 0.8.2)

View File

@@ -1,9 +0,0 @@
$(document).on("turbolinks:load", function() {
$("#upload_directory_files").on('click', function(e){
const newFilesCount = $('input[name = "directory[files][]"][type = "hidden"]').length;
if (newFilesCount === 0){
e.preventDefault();
}
});
});

View File

@@ -26,7 +26,7 @@ class BroadcastsController < ApplicationController
end
def show
@conference_url = url_for [@broadcast.project, @broadcast, :zoom_meeting]
@conference_url = url_for [@broadcast.project, @broadcast, :conference_meeting]
@recordings = @broadcast.broadcast_recordings.order_by_recent.paginate(page: params[:page])
@files = @broadcast.files.order("created_at DESC").paginate(page: params[:files_page])
render layout: 'application'
@@ -72,7 +72,7 @@ class BroadcastsController < ApplicationController
end
def broadcast_params
params.require(:broadcast).permit(:name, :shoot_location_time_zone, files: [])
params.require(:broadcast).permit(:name, :shoot_location_time_zone, :conference_option, files: [])
end
def set_project

View File

@@ -0,0 +1,22 @@
class CallbacksController < ApplicationController
skip_before_action :require_login
skip_after_action :verify_authorized, except: :index
skip_after_action :verify_policy_scoped, only: :index
skip_before_action :verify_authenticity_token
def create
uid = request.env['omniauth.auth'][:uid]
token_data = request.env['omniauth.auth'][:credentials]
current_user&.tap do |user|
user.microsoft_user_id = uid
user.microsoft_access_token = token_data.token
user.microsoft_refresh_token = token_data.refresh_token
user.microsoft_token_expires_at = token_data.expires_at # Expiration time is returned in seconds
user.save
end
redirect_to profile_path
end
end

View File

@@ -0,0 +1,45 @@
class ConferenceMeetingsController < ApplicationController
require 'microsoft_graph'
def show
authorize broadcast = Broadcast.find(params[:broadcast_id])
case broadcast.conference_option
when 'zoom'
redirect_to broadcast.zoom_meeting_url
when 'ms_teams'
if broadcast.conference_join_url.nil?
begin
graph_api = MicrosoftGraph.new(
current_user,
ENV['AZURE_CLIENT_ID'],
ENV['AZURE_CLIENT_SECRET'],
ENV['AZURE_TENANT_ID'],
ENV['AZURE_SCOPES']
)
subject = "#{broadcast.name} Online Meeting"
teams_meeting = graph_api.create_teams_meeting(subject)
join_url = teams_meeting['joinUrl']
if join_url.present?
broadcast.conference_join_url = join_url
broadcast.save
else
raise StandardError, 'Failed to read teams meeting join URL'
end
rescue ActionController::InvalidAuthenticityToken => e
Rails.logger.error(e.message)
redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.not_authenticated')
return
rescue StandardError => e
Rails.logger.error(e.message)
redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.failed_to_join')
return
end
end
redirect_to broadcast.conference_join_url
else
redirect_to project_broadcast_url(broadcast.project, broadcast), alert: t('.alerts.unknown_conference_option')
end
end
end

View File

@@ -50,58 +50,11 @@ class MaterialReleasesController < ApplicationController
private
def person_params
%i[
person_first_name
person_last_name
person_phone
person_company
person_email
person_title
person_address_street1
person_address_street2
person_address_city
person_address_state
person_address_zip
person_address_country
]
end
def guardian_params
%i[
guardian_first_name
guardian_last_name
guardian_phone
guardian_email
guardian_photo
guardian_address_street1
guardian_address_street2
guardian_address_city
guardian_address_state
guardian_address_zip
guardian_address_country
]
end
def second_guardian_params
%i[
guardian_2_first_name
guardian_2_last_name
guardian_2_phone
guardian_2_email
guardian_2_photo
guardian_2_address_street1
guardian_2_address_street2
guardian_2_address_city
guardian_2_address_state
guardian_2_address_zip
guardian_2_address_country
]
end
def material_release_params
params.require(:material_release).permit(person_params, guardian_params, second_guardian_params, :minor,
params.require(:material_release).permit(
:name, :address_street1, :address_street2, :address_city, :address_state, :address_zip, :address_country,
:person_first_name, :person_last_name, :person_title, :person_company, :person_phone, :person_email,
:person_address_street1, :person_address_street2, :person_address_city, :person_address_state, :person_address_zip, :person_address_country,
:applicable_medium_id, :applicable_medium_text,
:territory_id, :territory_text,
:term_id, :term_text,

View File

@@ -39,59 +39,11 @@ class Public::MaterialReleasesController < Public::BaseController
authorize material_releases.build(params)
end
def person_params
%i[
person_first_name
person_last_name
person_phone
person_email
person_title
person_company
person_address_street1
person_address_street2
person_address_city
person_address_state
person_address_zip
person_address_country
]
end
def guardian_params
%i[
guardian_first_name
guardian_last_name
guardian_phone
guardian_email
guardian_photo
guardian_address_street1
guardian_address_street2
guardian_address_city
guardian_address_state
guardian_address_zip
guardian_address_country
]
end
def second_guardian_params
%i[
guardian_2_first_name
guardian_2_last_name
guardian_2_phone
guardian_2_email
guardian_2_photo
guardian_2_address_street1
guardian_2_address_street2
guardian_2_address_city
guardian_2_address_state
guardian_2_address_zip
guardian_2_address_country
]
end
def material_release_params
params.require(:material_release).permit(person_params, guardian_params, second_guardian_params, :minor,
params.require(:material_release).permit(
:name, :address_street1, :address_street2, :address_city, :address_state, :address_zip, :address_country,
:person_first_name, :person_last_name, :person_title, :person_company, :person_phone, :person_email,
:person_address_street1, :person_address_street2, :person_address_city, :person_address_state, :person_address_zip, :person_address_country,
:signature_base64,
:locale, :contract_template, :description, photos: []
)

View File

@@ -0,0 +1,17 @@
module BroadcastConferencesHelper
def options_for_conference_select
[
['Zoom', 'zoom'],
['MS Teams', 'ms_teams']
]
end
def conference_option_name_from_key(key)
option = options_for_conference_select.find { |option| option[1] == key }
if option.present?
option.first
else
'Unknown conference option'
end
end
end

View File

@@ -15,7 +15,7 @@ class AppHost
end
def domain_with_port
[domain, port].compact.join(":")
[domain, port].reject(&:blank?).compact.join(":")
end
def protocol

View File

@@ -12,11 +12,6 @@ class MaterialRelease < ApplicationRecord
include PersonName
include CsvExportable
include Approvable
include GuardianPhotoable
include SecondGuardianPhotoable
include GuardianName
include SecondGuardianName
class << self
def custom_csv_exportable_headers
@@ -35,39 +30,6 @@ class MaterialRelease < ApplicationRecord
%w(person_address_country country)
]
composed_of :guardian_address,
class_name: 'Address',
mapping: [
%w[guardian_address_street1 street1],
%w[guardian_address_street2 street2],
%w[guardian_address_city city],
%w[guardian_address_state state],
%w[guardian_address_zip zip],
%w[guardian_address_country country]
]
composed_of :guardian_2_address,
class_name: 'Address',
mapping: [
%w[guardian_2_address_street1 street1],
%w[guardian_2_address_street2 street2],
%w[guardian_2_address_city city],
%w[guardian_2_address_state state],
%w[guardian_2_address_zip zip],
%w[guardian_2_address_country country]
]
# We don't care for the argument but method WILL receive option name
# when called from inside with_option block, hence * argument
def self.face_photo_acceptable_content_types(*)
['image/png', 'image/jpeg']
end
def self.acceptable_import_file_extensions
['.png', '.jpeg', '.jpg', '.pdf']
end
validates :name, presence: true
validates :person_email, email: true, allow_blank: true
@@ -85,8 +47,8 @@ class MaterialRelease < ApplicationRecord
@contact_person ||= Contact.new(person_name, person_address, person_email, person_phone)
end
def second_guardian_present?
guardian_2_first_name.present?
def minor?
false
end
def uses_edl?

View File

@@ -2,6 +2,7 @@
<%= bootstrap_form_with model: model, local: true do |form| %>
<%= form.text_field :name %>
<%= form.select :conference_option, options_for_conference_select, { label: t('.labels.conference_option') }, class: "form-control custom-select" %>
<%= form.time_zone_select(:shoot_location_time_zone, nil, label: "Time zone of shoot location") %>
<div class="row align-items-center text-center mt-4">

View File

@@ -118,8 +118,8 @@
</div>
<hr>
<% end %>
<p class="card-text">If you want to join the ZOOM meeting dedicated to this broadcast, follow the link below.</p>
<%= link_to 'Video Conference', @conference_url, class: 'btn btn-primary btn-block', target: '_blank' %>
<p class="card-text"><%= "If you want to join the #{conference_option_name_from_key(@broadcast.conference_option)} meeting dedicated to this broadcast, follow the link below." %></p>
<%= link_to 'Video Conference', @conference_url, class: "btn btn-primary btn-block", target: '_blank' %>
</div>
<div class="<%= class_string("tab-pane fade show", "active" => params[:active_tab] == 'recordings') %>" id="recordings">
<div id="broadcast_recordings">

View File

@@ -2,7 +2,7 @@
<%= field_set_tag content_tag(:span, t(".release_info.heading"), class: "h6 text-muted text-uppercase") do %>
<div class="form-row">
<%= form.text_field :name, wrapper_class: "col-sm-6" %>
<%= form.select :release_type, options_for_release_type_select(project, @release_type), { wrapper_class: "col-sm-6" }, data: { toggle: "collapse-select", target_show_values_mapping: { "#guardian_clause": %w(acquired_media appearance talent material misc medical), "#fee_field": %w(appearance talent location material acquired_media), "#exploitable_rights_fields": %w(appearance talent location material acquired_media), "#custom_fields": %w(medical misc), "#amendment_clause": %w(location) } }, class: "form-control custom-select" %>
<%= form.select :release_type, options_for_release_type_select(project, @release_type), { wrapper_class: "col-sm-6" }, data: { toggle: "collapse-select", target_show_values_mapping: { "#guardian_clause": %w(acquired_media appearance talent misc medical), "#fee_field": %w(appearance talent location material acquired_media), "#exploitable_rights_fields": %w(appearance talent location material acquired_media), "#custom_fields": %w(medical misc), "#amendment_clause": %w(location) } }, class: "form-control custom-select" %>
</div>
<div class="form-row mb-3">
<%= form.radio_button :accessibility, :public_template, label: "Public", wrapper_class: "mr-3" %>

View File

@@ -3,6 +3,6 @@
<%= render "shared/files_dropzone_fields", form: form, directory: directory %>
<% end %>
<div class="pt-3">
<%= form.submit t(".submit"), id: "upload_directory_files", class: "btn btn-block btn-success", data: { disable_with: t("shared.disable_with") } %>
<%= form.submit t(".submit"), class: "btn btn-block btn-success", data: { disable_with: t("shared.disable_with") } %>
</div>
<% end %>

View File

@@ -33,7 +33,7 @@
<th><%= t(".table_headers.notes") %></th>
<th><%= t(".table_headers.tags") %></th>
<th><%= t(".table_headers.signed_at") %></th>
<th><%= t(".table_headers.amendment_signed") %></th>
<th></th>
<th></th>
</tr>
</thead>

View File

@@ -12,9 +12,6 @@
<hr>
<%= field_set_tag content_tag(:span, t(".signer_details.heading"), class: "h6 text-muted text-uppercase") do %>
<%= form.form_group :minor do %>
<%= form.check_box :minor, label: t("helpers.label.material_release.minor"), data: { target: "[data-ujs-target=guardian-fields]", toggle: "collapse" } %>
<% end %>
<div class="form-row">
<%= form.text_field :person_first_name, wrapper_class: "col-sm-6" %>
<%= form.text_field :person_last_name, wrapper_class: "col-sm-6" %>
@@ -24,28 +21,6 @@
<%= form.text_field :person_title, wrapper_class: "col-sm-6" %>
</div>
<%= render "shared/address_fields", form: form, subject: "person" %>
<div class="<%= class_string("collapse" => !material_release.minor?) %>" data-ujs-target="guardian-fields">
<%= card_field_set_tag t(".guardian_info.heading") do %>
<div class="form-row">
<%= form.text_field :guardian_first_name, required: material_release.minor?, wrapper_class: "col-sm-3" %>
<%= form.text_field :guardian_last_name, required: material_release.minor?, wrapper_class: "col-sm-3" %>
<%= form.phone_field :guardian_phone, wrapper_class: "col-sm-6" %>
<%= form.text_field :guardian_email, wrapper_class: "col-sm-6" %>
</div>
<%= render "shared/address_fields", form: form, subject: "guardian" %>
<% end %>
<%= card_field_set_tag t(".guardian_2_info.heading") do %>
<div class="form-row">
<%= form.text_field :guardian_2_first_name, wrapper_class: "col-sm-3" %>
<%= form.text_field :guardian_2_last_name, wrapper_class: "col-sm-3" %>
<%= form.phone_field :guardian_2_phone, wrapper_class: "col-sm-6" %>
<%= form.text_field :guardian_2_email, wrapper_class: "col-sm-6" %>
</div>
<%= render "shared/address_fields", form: form, subject: "guardian_2" %>
<% end %>
</div>
<% end %>
<hr>
@@ -59,44 +34,6 @@
<%= field_set_tag content_tag(:span, t(".photos.heading"), class: "h6 text-muted text-uppercase") do %>
<%= render "shared/photos_dropzone_fields", form: form, release: material_release %>
<div class="<%= class_string("collapse" => !material_release.minor?) %>" data-ujs-target="guardian-fields">
<br>
<div class="text-left">
<p><%= t(".photos.guardian_photo.heading") %></p>
<div id="guardian-photo-preview" class="d-inline-block mb-2" data-behavior="guardian-photo-preview" data-file-input="[data-ujs-target=guardian-photo-input]">
<div class="align-items-center d-flex photo-preview img-thumbnail justify-content-center">
<span>No photo yet</span>
</div>
</div>
<% if material_release.guardian_photo.attached? %>
<%= javascript_tag nonce: true do %>
App.PhotoPreview.set("#guardian-photo-preview", "<%= url_for(material_release.guardian_photo.variant(auto_orient: true, resize: '200x200')) %>");
<% end %>
<% end %>
<div class="d-inline-block">
<%= form.hidden_field :guardian_photo, value: form.object.guardian_photo.signed_id if material_release.guardian_photo.attached?%>
<%= form.file_field :guardian_photo, hide_label: true, data: { ujs_target: "guardian-photo-input" }, help: "PNG or JPG only", accept: material_release.class.face_photo_acceptable_content_types.join(",") %>
</div>
<p><%= t(".photos.guardian_2_photo.heading") %></p>
<div id="guardian-2-photo-preview" class="d-inline-block mb-2" data-behavior="guardian-photo-preview" data-file-input="[data-ujs-target=guardian-2-photo-input]">
<div class="align-items-center d-flex photo-preview img-thumbnail justify-content-center">
<span>No photo yet</span>
</div>
</div>
<% if material_release.guardian_2_photo.attached? %>
<%= javascript_tag nonce: true do %>
App.PhotoPreview.set("#guardian-2-photo-preview", "<%= url_for(material_release.guardian_2_photo.variant(auto_orient: true, resize: '200x200')) %>");
<% end %>
<% end %>
<div class="d-inline-block">
<%= form.hidden_field :guardian_2_photo, value: form.object.guardian_2_photo.signed_id if material_release.guardian_2_photo.attached?%>
<%= form.file_field :guardian_2_photo, hide_label: true, data: { ujs_target: "guardian-2-photo-input" }, help: "PNG or JPG only", accept: material_release.class.face_photo_acceptable_content_types.join(",") %>
</div>
</div>
<hr>
</div>
<% end %>
<div class="row align-items-center text-center mt-4">

View File

@@ -17,6 +17,7 @@
<%= @user.role_for(Current.account).to_s.titleize %>
<% end %>
</p>
<%= link_to 'Auth to Microsoft', '/auth/azure_ad', method: :post, class: "btn btn-primary" %>
</div>
<div class="mt-3">

View File

@@ -13,17 +13,6 @@
<hr>
<% unless @contract_template.guardian_clause.blank? %>
<%= form.form_group :minor do %>
<%= form.check_box :minor, label: t("helpers.label.appearance_release.minor"), data: { behavior: "update-required-status", target: "[data-ujs-target=guardian-fields]", toggle: "collapse" } %>
<% end %>
<%= card_field_set_tag t(".guardian_clause.heading") do %>
<p><%= @contract_template.guardian_clause %></p>
<% end %>
<hr>
<% end %>
<%= card_field_set_tag t(".release_info.heading") do %>
<div class="form-row">
<%= form.text_field :name, required: true, wrapper_class: "col-12" %>
@@ -51,91 +40,6 @@
<hr>
<% unless @contract_template.guardian_clause.blank? %>
<div class="<%= class_string("collapse" => !@material_release.minor?) %>" data-ujs-target="guardian-fields">
<%= card_field_set_tag t(".guardian_info.heading") do %>
<div class="form-row">
<%= form.text_field :guardian_first_name, required: @material_release.minor?, wrapper_class: "col-sm-3", data: { required_tag: "guardian" } %>
<%= form.text_field :guardian_last_name, required: @material_release.minor?, wrapper_class: "col-sm-3", data: { required_tag: "guardian" } %>
<%= form.phone_field :guardian_phone, required: @material_release.minor?, wrapper_class: "col-sm-6", data: { required_tag: "guardian" } %>
<%= form.text_field :guardian_email, required: @material_release.minor?, wrapper_class: "col-sm-6", data: { required_tag: "guardian" } %>
</div>
<%= render "shared/address_fields", form: form, subject: "guardian", required: @material_release.minor?, data: { required_tag: "guardian" } %>
<% end %>
<hr>
<%= card_field_set_tag t(".guardian_photo.heading") do %>
<div class="alert alert-warning font-weight-bold"><%= t ".guardian_photo.instructions" %></div>
<div class="text-center">
<div class="d-inline-block mb-2" data-behavior="guardian-photo-preview" data-file-input="[data-ujs-target=guardian-photo-input]">
<div class="align-items-center d-flex photo-preview img-thumbnail justify-content-center">
<span><%= t ".photo.no_photo" %></span>
</div>
</div>
<div class="d-inline-block text-left">
<% if @material_release.guardian_photo.attached? %>
<%= javascript_tag nonce: true do %>
App.PhotoPreview.set("[data-behavior=guardian-photo-preview]", "<%= url_for(@material_release.guardian_photo.variant(auto_orient: true, resize: '200x200')) %>");
<% end %>
<% end %>
<div class="hidden-file-input">
<%= form.hidden_field :guardian_photo, value: form.object.guardian_photo.signed_id if @material_release.guardian_photo.attached? %>
<%= form.file_field :guardian_photo, required: @material_release.minor?, hide_label: true, data: { ujs_target: "guardian-photo-input" }, accept: @material_release.class.face_photo_acceptable_content_types.join(","), direct_upload: true %>
</div>
<%= button_tag t(".photo.take_photo"), type: "button", class: "btn btn-lg btn-primary take-photo-button", data: { behavior: "trigger-click", target: "[data-ujs-target=guardian-photo-input]" } %>
</div>
<p class="p-2 font-weight-bold">
<small class="text-muted"><%= t ".photo.warning" %></small>
</p>
</div>
<% end %>
<hr>
<%= card_field_set_tag t(".guardian_2_info.heading") do %>
<div class="form-row">
<%= form.text_field :guardian_2_first_name, wrapper_class: "col-sm-3" %>
<%= form.text_field :guardian_2_last_name, wrapper_class: "col-sm-3" %>
<%= form.phone_field :guardian_2_phone, wrapper_class: "col-sm-6" %>
<%= form.text_field :guardian_2_email, wrapper_class: "col-sm-6" %>
</div>
<%= render "shared/address_fields", form: form, subject: "guardian_2" %>
<% end %>
<hr>
<%= card_field_set_tag t(".guardian_2_photo.heading") do %>
<div class="alert alert-warning font-weight-bold"><%= t ".guardian_2_photo.instructions" %></div>
<div class="text-center">
<div class="d-inline-block mb-2" data-behavior="guardian-photo-preview" data-file-input="[data-ujs-target=guardian-2-photo-input]">
<div class="align-items-center d-flex photo-preview img-thumbnail justify-content-center">
<span><%= t ".photo.no_photo" %></span>
</div>
</div>
<div class="d-inline-block text-left">
<% if @material_release.guardian_2_photo.attached? %>
<%= javascript_tag nonce: true do %>
App.PhotoPreview.set("[data-behavior=guardian-photo-preview]", "<%= url_for(@material_release.guardian_2_photo.variant(auto_orient: true, resize: '200x200')) %>");
<% end %>
<% end %>
<div class="hidden-file-input">
<%= form.hidden_field :guardian_2_photo, value: form.object.guardian_2_photo.signed_id if @material_release.guardian_2_photo.attached? %>
<%= form.file_field :guardian_2_photo, hide_label: true, data: { ujs_target: "guardian-2-photo-input" }, accept: @material_release.class.face_photo_acceptable_content_types.join(","), direct_upload: true %>
</div>
<%= button_tag t(".photo.take_photo"), type: "button", class: "btn btn-lg btn-primary take-photo-button", data: { behavior: "trigger-click", target: "[data-ujs-target=guardian-2-photo-input]" } %>
</div>
<p class="p-2 font-weight-bold">
<small class="text-muted"><%= t ".photo.warning" %></small>
</p>
</div>
<% end %>
<hr>
</div>
<% end %>
<%= card_field_set_tag t(".signature.heading") do %>
<%= render "shared/signature_fields", form: form, instruction: 'For Owner or Authorized Signatory', signature_legal_text: @contract_template.signature_legal_text %>
<% end %>

View File

@@ -0,0 +1,17 @@
require 'azure_ad'
Rails.application.config.middleware.use OmniAuth::Builder do
provider :azure_ad,
client_id: ENV['AZURE_CLIENT_ID'],
client_secret: ENV['AZURE_CLIENT_SECRET'],
redirect_uri: ENV['AZURE_REDIRECT_URI'],
client_options: {
token_url: "#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/token",
authorize_url: "#{ENV['AZURE_TENANT_ID']}/oauth2/v2.0/authorize"
},
scope: ENV['AZURE_SCOPES']
end
# Rails.application.config.middleware.use OmniAuth::Builder do
# provider :microsoft_graph, ENV['AZURE_CLIENT_ID'], ENV['AZURE_CLIENT_SECRET'], scope: 'openid email profile User.Read'
# end

View File

@@ -262,6 +262,9 @@ en:
stream_multiple_cameras: Stream multiple cameras at one time
update:
reset_notice: The Share URL has been reset, and the previous URL will no longer work. Please click "Copy URL" and share it again with those who you want to have access to this live stream
form:
labels:
conference_option: Conference Option
bulk_taggings:
new_bulk_tag_modal:
submit: Add
@@ -511,8 +514,6 @@ en:
person_last_name: Last name
person_name: Name
person_phone: Phone number
contract_template:
amendment_clause: Additional Contract Clause
location_release:
address_city: City
address_country: Country
@@ -533,27 +534,6 @@ en:
person_last_name: Owner last name
material_release:
description: Description of licensed material
guardian_2_address_city: Guardian 2 city
guardian_2_address_country: Guardian 2 country
guardian_2_address_state: Guardian 2 state
guardian_2_address_street1: Guardian 2 address
guardian_2_address_street2: Guardian 2 address (Line 2)
guardian_2_address_zip: Guardian 2 zip code
guardian_2_email: Guardian 2 email
guardian_2_first_name: Guardian 2 first name
guardian_2_last_name: Guardian 2 last name
guardian_2_phone: Guardian 2 phone
guardian_address_city: Guardian city
guardian_address_country: Guardian country
guardian_address_state: Guardian state
guardian_address_street1: Guardian address
guardian_address_street2: Guardian address (Line 2)
guardian_address_zip: Guardian zip code
guardian_email: Guardian email
guardian_first_name: Guardian first name
guardian_last_name: Guardian last name
guardian_phone: Guardian phone
minor: Is the person a minor?
name: Name of licensed material
person_address: Address
person_address_city: City
@@ -844,7 +824,6 @@ en:
empty: Location Releases will appear here
table_headers:
address: Address
amendment_signed: Additional Clause
approved: Approved
name: Location Name
notes: Notes
@@ -854,7 +833,7 @@ en:
actions:
manage: Manage
review: Review
sign_amendment: Sign Additional Clause
sign_amendment: Sign Amendment
messages:
amendment_not_signed_tooltip: Amendment not yet signed
amendment_signed_tooltip: Amendment Signed
@@ -874,18 +853,10 @@ en:
form:
contract_and_rights:
heading: 3 of 4 Contract & Exploitable Rights
guardian_2_info:
heading: Second Guardian Information (if company requires)
guardian_info:
heading: Guardian Information
material_details:
heading: 1 of 3 Material Details
photos:
dropzone_label: Tap to take a photo of Licensed Material (optional)
guardian_2_photo:
heading: Second Guardian Photo
guardian_photo:
heading: Guardian Photo
heading: 4 of 4 Photos
signer_details:
heading: 2 of 4 Licensor/Owner Details
@@ -1136,7 +1107,7 @@ en:
amendment_signed_message: Release amendment signed successfully! Thank you
new:
amendment:
heading: Additional Clause
heading: Amendment
copy_url: Copy sign amendment URL
signature:
heading: Signature
@@ -1213,27 +1184,10 @@ en:
cancel: Cancel
contact_info:
heading: Licensor/Owner Contact Information
guardian_2_info:
heading: Second Guardian Information (if company requires)
guardian_2_photo:
heading: Second Guardian Photo
instructions: >
Lastly, it's time for second guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese!
guardian_clause:
heading: Guardian Clause
guardian_info:
heading: Guardian Information
guardian_photo:
heading: Guardian Photo
instructions: >
Lastly, it's time for guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese!
legal:
heading: Legal
photo:
heading: Photos
no_photo: No photo yet
take_photo: Take Photo
warning: If your photo appears sideways, it will be autocorrected when you submit your release.
release_info:
heading: Release Information
signature:
@@ -1603,3 +1557,9 @@ en:
edit: Edit
report: Report
generating: Generating...
conference_meetings:
show:
alerts:
not_authenticated: You are not authenticated via Microsoft, please authenticate and try again
failed_to_join: Failed to join conference
unknown_conference_option: Unknown conference option

View File

@@ -120,6 +120,9 @@ es:
stream_multiple_cameras: Stream multiple cameras at one time
update:
reset_notice: The Share URL has been reset, and the previous URL will no longer work. Please click "Copy URL" and share it again with those who you want to have access to this live stream
form:
labels:
conference_option: Conference Option (ES)
contract_templates:
blank_contracts:
create:
@@ -285,31 +288,6 @@ es:
person_email: Dirección de correo electrónico
person_name: Nómbre
person_phone: Número de teléfono
contract_template:
amendment_clause: Additional Contract Clause (ES)
material_release:
guardian_2_address_city: Guardian 2 city (ES)
guardian_2_address_country: Guardian 2 country (ES)
guardian_2_address_state: Guardian 2 state (ES)
guardian_2_address_street1: Guardian 2 address (ES)
guardian_2_address_street2: Guardian 2 address (Line 2) (ES)
guardian_2_address_zip: Guardian 2 zip code (ES)
guardian_2_email: Guardian 2 email (ES)
guardian_2_first_name: Guardian 2 first name (ES)
guardian_2_last_name: Guardian 2 last name (ES)
guardian_2_phone: Guardian 2 phone (ES)
guardian_address_city: Guardian city (ES)
guardian_address_country: Guardian country (ES)
guardian_address_state: Guardian state (ES)
guardian_address_street1: Dirección del tutor legal
guardian_address_street2: Dirección del tutor legal (Línea 2)
guardian_address_zip: Guardian zip code (ES)
guardian_email: Guardian email (ES)
guardian_first_name: Guardian first name (ES)
guardian_last_name: Guardian last name (ES)
guardian_name: Nómbre del tutor legal
guardian_phone: Número de teléfono del tutor legal
minor: El firmante es un menor
medical_release:
guardian_2_address_city: Guardian 2 city (ES)
guardian_2_address_country: Guardian 2 country (ES)
@@ -398,8 +376,6 @@ es:
contract_template:
update: Save changes (ES)
create: 'Crear %{model}'
material_release:
create: Import Release (ES)
medical_release:
update: Approve (ES)
update: 'Actualizar %{model}'
@@ -410,30 +386,19 @@ es:
index:
table_headers:
address: Address (ES)
amendment_signed: Additional Clause (ES)
notes: Notes (ES)
signed_at: Date Signed (ES)
tags: Tags (ES)
location_release:
actions:
sign_amendment: Sign Additional Clause (ES)
sign_amendment: Sign Amendment (ES)
messages:
amendment_not_signed_tooltip: Amendment not yet signed (ES)
amendment_signed_tooltip: Amendment Signed (ES)
material_releases:
create:
notice: The acquired media release has been created (ES)
form:
guardian_2_info:
heading: Guardian Information (if company requires) [ES]
guardian_info:
heading: Guardian Information (ES)
photos:
dropzone_label: Tap to take a photo of Licensed Material (optional) (ES)
guardian_2_photo:
heading: Second Guardian Photo
guardian_photo:
heading: Guardian Photo
index:
table_headers:
name: Name (ES)
@@ -502,7 +467,7 @@ es:
amendment_signed_message: Release amendment signed successfully! Thank you (ES)
new:
amendment:
heading: Additional Clause (ES)
heading: Amendment
copy_url: Copy sign amendment URL (ES)
signature:
heading: Signature (ES)
@@ -545,25 +510,8 @@ es:
heading: Photos (ES)
material_releases:
new:
guardian_2_info:
heading: Second Guardian Information (if company requires) [ES]
guardian_2_photo:
heading: Second Guardian Photo (ES)
instructions: >
(ES) Lastly, it's time for second guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! (ES)
guardian_clause:
heading: Guardian Clause (ES)
guardian_info:
heading: Guardian Information (ES)
guardian_photo:
heading: Guardian Photo (ES)
instructions: >
(ES) Lastly, it's time for guardian to take a selfie photo! Please remove your hat and sunglasses (regular eyewear is ok), make sure that you are the only person in the photo, look straight into the camera, and say Cheese! (ES)
photo:
heading: Photos (ES)
no_photo: No photo yet (ES)
take_photo: Take Photo (ES)
warning: If your photo appears sideways, it will be autocorrected when you submit your release. (ES)
medical_releases:
new:
guardian_2_info:
@@ -685,3 +633,11 @@ es:
production_elements_logs: Production Elements Logs, and more (ES)
reduces_labor_cost: Reduces labor costs (ES)
simplifies_cue_sheets: Simplifies Music Cue Sheets, Graphic Cue Sheets (ES)
conference_meetings:
show:
alert:
not_authenticated: ""
alerts:
not_authenticated: You are not authenticated via Microsoft, please authenticate and try again (ES)
failed_to_join: Failed to join conference (ES)
unknown_conference_option: Unknown conference option (ES)

View File

@@ -4,6 +4,8 @@ require 'sidekiq/web'
Rails.application.routes.draw do
AVAILABLE_LOCALES_REGEX = /#{I18n.available_locales.join("|")}/.freeze
get 'auth/azure_ad/callback', to: 'callbacks#create'
concern :confirmable do
resources :video_release_confirmations, only: [:new, :create, :destroy]
end
@@ -99,7 +101,7 @@ Rails.application.routes.draw do
member do
delete :destroy_file
end
resource :zoom_meeting, only: [:show]
resource :conference_meeting, only: [:show]
end
resources :directories, except: [:index] do
member do

View File

@@ -1,25 +0,0 @@
class AddGuardiansFieldsToMaterialReleases < ActiveRecord::Migration[6.0]
def change
add_column :material_releases, :minor, :boolean, default: false
add_column :material_releases, :guardian_first_name, :string
add_column :material_releases, :guardian_last_name, :string
add_column :material_releases, :guardian_email, :string
add_column :material_releases, :guardian_phone, :string
add_column :material_releases, :guardian_address_street1, :string
add_column :material_releases, :guardian_address_street2, :string
add_column :material_releases, :guardian_address_city, :string
add_column :material_releases, :guardian_address_state, :string
add_column :material_releases, :guardian_address_zip, :string
add_column :material_releases, :guardian_address_country, :string
add_column :material_releases, :guardian_2_first_name, :string
add_column :material_releases, :guardian_2_last_name, :string
add_column :material_releases, :guardian_2_email, :string
add_column :material_releases, :guardian_2_phone, :string
add_column :material_releases, :guardian_2_address_street1, :string
add_column :material_releases, :guardian_2_address_street2, :string
add_column :material_releases, :guardian_2_address_city, :string
add_column :material_releases, :guardian_2_address_state, :string
add_column :material_releases, :guardian_2_address_zip, :string
add_column :material_releases, :guardian_2_address_country, :string
end
end

View File

@@ -0,0 +1,7 @@
class AddMicrosoftTokensToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :microsoft_access_token, :string
add_column :users, :microsoft_refresh_token, :string
add_column :users, :microsoft_token_expires_at, :integer
end
end

View File

@@ -0,0 +1,5 @@
class AddMicrosoftUserIdToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :microsoft_user_id, :string
end
end

View File

@@ -0,0 +1,6 @@
class AddConferenceDetailsToBroadcasts < ActiveRecord::Migration[6.0]
def change
add_column :broadcasts, :conference_option, :string
add_column :broadcasts, :conference_join_url, :string
end
end

View File

@@ -562,7 +562,9 @@ CREATE TABLE public.broadcasts (
token character varying,
streamer_status integer DEFAULT 0,
shoot_location_time_zone character varying DEFAULT 'UTC'::character varying,
full_live_stream_playback_uid character varying
full_live_stream_playback_uid character varying,
conference_option character varying,
conference_join_url character varying
);
@@ -1002,28 +1004,7 @@ CREATE TABLE public.material_releases (
person_last_name character varying,
approved_by_user_name text,
approved_by_user_email text,
approved_at timestamp without time zone,
minor boolean DEFAULT false,
guardian_first_name character varying,
guardian_last_name character varying,
guardian_email character varying,
guardian_phone character varying,
guardian_address_street1 character varying,
guardian_address_street2 character varying,
guardian_address_city character varying,
guardian_address_state character varying,
guardian_address_zip character varying,
guardian_address_country character varying,
guardian_2_first_name character varying,
guardian_2_last_name character varying,
guardian_2_email character varying,
guardian_2_phone character varying,
guardian_2_address_street1 character varying,
guardian_2_address_street2 character varying,
guardian_2_address_city character varying,
guardian_2_address_state character varying,
guardian_2_address_zip character varying,
guardian_2_address_country character varying
approved_at timestamp without time zone
);
@@ -1776,7 +1757,11 @@ CREATE TABLE public.users (
remember_created_at timestamp without time zone,
first_name character varying,
last_name character varying,
time_zone character varying DEFAULT 'UTC'::character varying NOT NULL
time_zone character varying DEFAULT 'UTC'::character varying NOT NULL,
microsoft_access_token character varying,
microsoft_refresh_token character varying,
microsoft_token_expires_at integer,
microsoft_user_id character varying
);
@@ -3988,6 +3973,8 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200720131309'),
('20200721140821'),
('20200725231419'),
('20200727143209');
('20200810140331'),
('20200812161119'),
('20200817233053');

124
lib/azure_ad.rb Normal file
View File

@@ -0,0 +1,124 @@
require 'omniauth-oauth2'
# This file is from omniauth-microsoft_graph lib (not installed)
# It is modified to make auth work
module OmniAuth
module Strategies
class AzureAd < OmniAuth::Strategies::OAuth2
BASE_SCOPE_URL = 'https://graph.microsoft.com/'
BASE_SCOPES = %w[offline_access openid email profile].freeze
DEFAULT_SCOPE = 'offline_access openid email profile User.Read'.freeze
option :name, :azure_ad
option :client_options,
site: 'https://login.microsoftonline.com/'
option :authorize_options, %i[state callback_url scope response_mode]
option :token_params, {}
option :scope, DEFAULT_SCOPE
option :authorized_client_ids, []
uid { raw_info["id"] }
info do
{
# 'email' => raw_info["mail"],
# 'first_name' => raw_info["givenName"],
# 'last_name' => raw_info["surname"],
# 'name' => [raw_info["givenName"], raw_info["surname"]].join(' '),
# 'nickname' => raw_info["displayName"],
}
end
extra do
{
# 'raw_info' => raw_info,
# 'params' => access_token.params,
# 'aud' => options.client_id
}
end
def authorize_params
super.tap do |params|
options[:authorize_options].each do |k|
params[k] = request.params[k.to_s] unless [nil, ''].include?(request.params[k.to_s])
end
params[:scope] = get_scope(params)
session['omniauth.state'] = params[:state] if params[:state]
end
end
def raw_info
@raw_info ||= access_token.get('https://graph.microsoft.com/v1.0/me').parsed
end
def callback_url
options[:callback_url] || full_host + script_name + callback_path
end
def custom_build_access_token
token_response = get_access_token(request)
session[:microsoft_graph_api_token] = token_response.token
token_response
end
alias build_access_token custom_build_access_token
private
def get_access_token(request)
verifier = request.params['code']
redirect_uri = request.params['redirect_uri'] || request.params['callback_url']
if verifier && request.xhr?
client_get_token(verifier, redirect_uri || '/auth/azure_ad/callback')
elsif verifier
client_get_token(verifier, redirect_uri || callback_url)
elsif verify_token(request.params['access_token'])
::OAuth2::AccessToken.from_hash(client, request.params.dup)
elsif request.content_type =~ /json/i
begin
body = JSON.parse(request.body.read)
request.body.rewind # rewind request body for downstream middlewares
verifier = body && body['code']
client_get_token(verifier, '/auth/azure_ad/callback') if verifier
rescue JSON::ParserError => e
warn "[omniauth google-oauth2] JSON parse error=#{e}"
end
end
end
def client_get_token(verifier, redirect_uri)
client.auth_code.get_token(verifier, get_token_options(redirect_uri), get_token_params)
end
def get_token_params
deep_symbolize(options.auth_token_params || {})
end
def get_token_options(redirect_uri = '')
{ redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true))
end
def get_scope(params)
raw_scope = params[:scope] || DEFAULT_SCOPE
scope_list = raw_scope.split(' ').map { |item| item.split(',') }.flatten
scope_list.map! { |s| s =~ %r{^https?://} || BASE_SCOPES.include?(s) ? s : "#{BASE_SCOPE_URL}#{s}" }
scope_list.join(' ')
end
def verify_token(access_token)
return false unless access_token
# access_token.get('https://graph.microsoft.com/v1.0/me').parsed
raw_response = client.request(:get, 'https://graph.microsoft.com/v1.0/me',
params: { access_token: access_token }).parsed
(raw_response['aud'] == options.client_id) || options.authorized_client_ids.include?(raw_response['aud'])
end
end
end
end

107
lib/microsoft_graph.rb Normal file
View File

@@ -0,0 +1,107 @@
require 'httparty'
class MicrosoftGraph
BASE_URL = 'https://graph.microsoft.com/v1.0'.freeze
def initialize(current_user, client_id, client_secret, tenant_id, scopes)
@current_user = current_user
@uid = current_user.microsoft_user_id
@token = current_user.microsoft_access_token
@refresh_token = current_user.microsoft_refresh_token
@token_expires_at = current_user.microsoft_token_expires_at
@client_id = client_id
@client_secret = client_secret
@tenant_id = tenant_id
@scopes = scopes
end
def create_teams_meeting(subject)
if @refresh_token.nil? || @token_expires_at.nil?
raise ActionController::InvalidAuthenticityToken, 'Missing refresh token / token expiration'
return
end
# Obtain new token if token is expired or will expire in less than 5 minutes
if 5.minutes.from_now.to_i > @token_expires_at.seconds
refresh_access_token
end
if @token.nil?
raise ActionController::InvalidAuthenticityToken, 'Missing access token'
return
end
response = HTTParty.post(
"#{BASE_URL}/me/onlineMeetings",
body: {
subject: subject,
participants: {
organizer: {
identity: {
user: {
id: @uid
}
}
}
}
}.to_json,
headers: {
Authorization: "Bearer #{@token}",
'Content-Type': 'application/json'
}
)
raise StandardError, 'Authenticated user does not have a permission to create Teams Online Meeting' if response.code == 403
if response.code != 201
Rails.logger.error('[Microsoft Graph Error]')
Rails.logger.error(response.inspect)
raise StandardError, "Failed to create teams meeting [#{response.code}]"
else
JSON.parse(response.body)
end
end
private
def refresh_token_url
"https://login.microsoftonline.com/#{@tenant_id}/oauth2/v2.0/token"
end
def refresh_access_token
response = HTTParty.post(refresh_token_url,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
client_id: @client_id,
client_secret: @client_secret,
refresh_token: @refresh_token,
grant_type: 'refresh_token',
scope: @scopes
})
if response.code != 200
Rails.logger.error '[Microsoft Graph Error] Failed to obtain new access token using refresh token'
Rails.logger.error(response.inspect)
raise StandardError, 'Failed to obtain new access token'
end
parsed_response = JSON.parse(response.body)
new_access_token = parsed_response['access_token']
new_refresh_token = parsed_response['refresh_token']
token_expires_in = parsed_response['expires_in'] # For how long access token is valid (in seconds)
token_new_expiration_time = Time.now.to_i + token_expires_in
@current_user.microsoft_access_token = new_access_token
@current_user.microsoft_refresh_token = new_refresh_token
@current_user.microsoft_token_expires_at = token_new_expiration_time
@current_user.save!
@token = new_access_token
@refresh_token = new_refresh_token
@token_expires_at = token_new_expiration_time
end
end

View File

@@ -94,7 +94,7 @@ RSpec.describe Api::SyncController, type: :controller do
expect(appearance_releases.first).to include('guardian_photo')
expect(talent_releases.first).to include('guardian_photo')
expect(location_releases.first).not_to include('guardian_photo')
expect(material_releases.first).to include('guardian_photo')
expect(material_releases.first).not_to include('guardian_photo')
end
it 'guardian photo has same format as other photos' do

View File

@@ -131,16 +131,25 @@ RSpec.describe BroadcastsController, type: :controller do
expect(response.body).to have_xpath "//input[@readonly][@value='#{broadcast_url(broadcast.token)}']"
end
it "displays zoom meeting button" do
it "displays zoom meeting button for zoom conference option" do
get :show, params: { project_id: project.id, id: broadcast.id }
expect(response.body).to have_link("Video Conference", href: project_broadcast_zoom_meeting_url(project, broadcast))
expect(response.body).to have_content 'Zoom'
expect(response.body).to have_link("Video Conference", href: project_broadcast_conference_meeting_url(project, broadcast))
end
it "displays microsoft teams meeting button for MS Teams conference option" do
ms_teams_broadcast = create(:broadcast, :ms_teams_conference, project: project )
get :show, params: { project_id: project.id, id: ms_teams_broadcast.id }
expect(response.body).to have_content 'MS Teams'
expect(response.body).to have_link 'Video Conference', href: project_broadcast_conference_meeting_url(project, ms_teams_broadcast)
end
it "assigns required variables" do
get :show, params: { project_id: project.id, id: broadcast.id }
expect(assigns(:conference_url)).to eq project_broadcast_zoom_meeting_url(project, broadcast)
expect(assigns(:conference_url)).to eq project_broadcast_conference_meeting_url(project, broadcast)
expect(assigns(:broadcast)).to eq broadcast
end

View File

@@ -0,0 +1,76 @@
require 'rails_helper'
RSpec.describe ConferenceMeetingsController, type: :controller do
let(:user) { create(:user) }
let(:account) { user.primary_account }
let(:project) { create(:project, account: user.primary_account) }
let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) }
let(:ms_teams_broadcast) { create(:broadcast, :ms_teams_conference, project: project) }
let(:unknown_option_broadcast) { create(:broadcast, project: project, conference_option: 'google') }
let(:meeting_start_url) { "http://meeting_start_url" }
let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) }
let(:user_create_response) { {"id" => "new_host_id"} }
let(:roles_assign_response) { {"ids" => ["new_host_id"]} }
let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} }
before :each do
allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash)
allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id")
allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id")
allow(MuxLiveStream).to receive(:new).and_return OpenStruct.new(id: 'id', key: 'key', playback_id: 'playback_id')
end
describe "#show" do
before { sign_in user }
it "redirects to meeting start url with Zoom conference option" do
get :show, params: { project_id: project.id, broadcast_id: broadcast.id }
expect(response).to redirect_to(meeting_start_url)
end
it "redirects to the broadcast show page with alert if user is not authenticated via microsoft and tries to create MS Teams meeting" do
get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
expect(response).to redirect_to project_broadcast_path(project, ms_teams_broadcast)
expect(flash.alert).to eq not_authenticated_alert
end
it "redirects to the broadcast show page with alert if user is authenticated via microsoft and tries to create MS Teams meeting but Graph API fails to create meeting" do
allow_any_instance_of(MicrosoftGraph).to receive(:create_teams_meeting).and_return(nil)
get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
expect(response).to redirect_to project_broadcast_path(project, ms_teams_broadcast)
expect(flash.alert).to eq failed_to_join_alert
end
it "redirects to the broadcast show page with alert if conference option is not reckognized" do
get :show, params: { project_id: project.id, broadcast_id: unknown_option_broadcast.id }
expect(response).to redirect_to project_broadcast_path(project, unknown_option_broadcast)
expect(flash.alert).to eq unknown_conference_option_alert
end
it "redirects to meeting start url with MS Teams conference option" do
new_ms_teams_meeting = JSON.parse({
joinUrl: meeting_start_url
}.to_json)
allow_any_instance_of(MicrosoftGraph).to receive(:create_teams_meeting).and_return(new_ms_teams_meeting)
get :show, params: { project_id: project.id, broadcast_id: ms_teams_broadcast.id }
expect(response).to redirect_to(meeting_start_url)
end
end
private
def not_authenticated_alert
t 'conference_meetings.show.alerts.not_authenticated'
end
def failed_to_join_alert
t 'conference_meetings.show.alerts.failed_to_join'
end
def unknown_conference_option_alert
t 'conference_meetings.show.alerts.unknown_conference_option'
end
end

View File

@@ -1,30 +0,0 @@
require 'rails_helper'
RSpec.describe ZoomMeetingsController, type: :controller do
let(:user) { create(:user) }
let(:account) { user.primary_account }
let(:project) { create(:project, account: user.primary_account) }
let(:broadcast) { create(:broadcast, name: "Broadcast", project: project) }
let(:meeting_start_url) { "http://meeting_start_url" }
let(:meeting_hash) { HashWithIndifferentAccess.new(start_url: meeting_start_url) }
let(:user_create_response) { {"id" => "new_host_id"} }
let(:roles_assign_response) { {"ids" => ["new_host_id"]} }
let(:roles_list_response) { {"roles" => [{"name" => "directme-host"}]} }
before :each do
allow_any_instance_of(ZoomGateway).to receive(:find_meeting).and_return(meeting_hash)
allow_any_instance_of(ZoomGateway).to receive(:create_meeting).and_return("meeting_id")
allow_any_instance_of(ZoomGateway).to receive(:create_host).and_return("host_id")
allow(MuxLiveStream).to receive(:new).and_return OpenStruct.new(id: 'id', key: 'key', playback_id: 'playback_id')
end
describe "#show" do
before { sign_in user }
it "redirects to meeting start url" do
get :show, params: { project_id: project.id, broadcast_id: broadcast.id }
expect(response).to redirect_to(meeting_start_url)
end
end
end

View File

@@ -2,11 +2,16 @@ FactoryBot.define do
factory :broadcast do
association :project
name "My Live Stream"
conference_option "zoom"
transient do
skip_create_callback false
end
trait :ms_teams_conference do
conference_option "ms_teams"
end
trait :with_stream do
stream_uid "mux_stream"
stream_key "mux_key"

View File

@@ -15,16 +15,6 @@ FactoryBot.define do
end
end
trait :minor do
minor true
guardian_first_name "Guardian1"
guardian_last_name "First"
guardian_2_first_name "Guardian2"
guardian_2_last_name "Second"
guardian_phone "1111"
guardian_2_phone "2222"
end
trait :with_photo do
photos do
path = Rails.root.join("spec", "fixtures", "files", "material_photo.png")

View File

@@ -178,7 +178,7 @@ RSpec.feature 'User manages contract templates', type: :feature do
expect(content_disposition).to include('inline')
expect(pdf_body).to have_content('PREVIEW ONLY')
expect(pdf_body).to have_content('Material Release')
expect(pdf_body).to have_content('Guardian')
expect(pdf_body).not_to have_content('Guardian')
end
context 'preventing creation of release template with wrong fee value' do

View File

@@ -51,13 +51,6 @@ RSpec.feature "User manages project custom folders", type: :feature do
expect(page).to have_content("UPLOAD NEW FILES")
expect do
click_button "Upload Files"
end.not_to raise_exception
expect(page).to have_content("UPLOAD NEW FILES")
expect(page).not_to have_content("The folder has been updated")
drop_file Rails.root.join(file_fixture("person_photo.png")), type: :dropzone
click_button "Upload Files"

View File

@@ -24,6 +24,7 @@ feature 'User managing broadcasts' do
by 'filling out the form' do
fill_in broadcast_name_field, with: 'My Broadcast'
select_conference_option('Zoom')
select_time_zone("New Delhi")
end
@@ -267,6 +268,12 @@ feature 'User managing broadcasts' do
end
end
def select_conference_option(value)
if value.present?
select value, from: "broadcast[conference_option]"
end
end
def click_checkboxes
all('input[type="checkbox"]')[0].click
all('input[type="checkbox"]')[1].click

View File

@@ -87,7 +87,7 @@ feature "User managing location releases" do
visit new_account_project_contract_template_location_release_amendment_path(project.account, project, contract_template, release)
expect(page).to have_content amendments_heading.upcase
expect(page).to have_content amendments_heading
fill_in amendment_signer_name_field, with: 'Big Signer'
draw_signature file_fixture("signature.png"), amendment_signature_field
@@ -127,14 +127,6 @@ feature "User managing location releases" do
sign_in current_user
end
scenario "listing all releases, table have correct headers", js:true do
ct = create(:contract_template, :with_amendment_clause, project: project)
create(:location_release, :native, project: project, contract_template: ct)
visit project_location_releases_path(project)
table_headers.each { |s| expect(page).to have_content s }
end
scenario "creating a release", js: true do
visit new_project_location_release_path(project)
@@ -250,7 +242,7 @@ feature "User managing location releases" do
new_window = window_opened_by { click_link sign_amendment_link }
within_window new_window do
expect(page).to have_content amendments_heading.upcase
expect(page).to have_content amendments_heading
fill_in amendment_signer_name_field, with: 'Big Signer'
draw_signature file_fixture("signature.png"), amendment_signature_field
@@ -737,16 +729,4 @@ feature "User managing location releases" do
def amendment_signature_label
t 'contracts.amendment_page.description_labels.amendment_signature'
end
def table_headers
[
t('location_releases.index.table_headers.approved'),
t('location_releases.index.table_headers.name'),
t('location_releases.index.table_headers.address'),
t('location_releases.index.table_headers.notes'),
t('location_releases.index.table_headers.tags'),
t('location_releases.index.table_headers.signed_at'),
t('location_releases.index.table_headers.amendment_signed')
]
end
end

View File

@@ -40,93 +40,6 @@ feature "User managing material releases" do
expect(MaterialRelease.last.photos.attached?).to eq true
end
scenario "creating a release for a minor - guardian fields are required when minor checkbox is checked", js: true do
contract_template = create(:contract_template, project: project)
visit new_account_project_contract_template_material_release_path(project.account, project, contract_template)
all('input[data-required-tag="guardian"]').each do |field|
expect(field['required']).to eq 'false'
expect(field).not_to be_visible
end
page.check person_is_minor_checkbox
all('input[data-required-tag="guardian"]').each do |field|
expect(field['required']).to eq 'true'
expect(field).to be_visible
end
end
scenario 'creating a release for a minor', js: true do
project = create(:project, members: current_user, account: current_user.primary_account)
contract_template = create(:contract_template, project: project)
visit new_account_project_contract_template_material_release_path(project.account, project, contract_template)
expect(page).not_to have_content guardian_information_heading.upcase
expect(page).not_to have_content guardian_photo_heading.upcase
page.check person_is_minor_checkbox
expect(page).to have_content guardian_information_heading.upcase
expect(page).to have_content guardian_photo_heading.upcase
expect(page).to have_content guardian_email_field.titleize
fill_all_fields
fill_in guardian_first_name_field, with: 'Guardian'
fill_in guardian_last_name_field, with: 'Name'
fill_in guardian_phone_field, with: '001101'
fill_in guardian_email_field, with: 'valid@email.com'
attach_file guardian_photo_field, file_fixture('hemsworth.jpeg'), visible: :all
fill_in_guardian_address_fields
draw_signature file_fixture("signature.png"), signature_field
click_button submit_release_button
expect(page).to have_content(successful_submission_message)
end
scenario 'creating a release for a minor with two guardians', js: true do
project = create(:project, members: current_user, account: current_user.primary_account)
contract_template = create(:contract_template, project: project)
visit new_account_project_contract_template_material_release_path(project.account, project, contract_template)
expect(page).not_to have_content guardian_2_information_heading.upcase
expect(page).not_to have_content guardian_2_photo_heading.upcase
page.check person_is_minor_checkbox
expect(page).to have_content guardian_information_heading.upcase
expect(page).to have_content guardian_photo_heading.upcase
expect(page).to have_content guardian_email_field.titleize
expect(page).to have_content guardian_2_information_heading.upcase
expect(page).to have_content guardian_2_photo_heading.upcase
expect(page).to have_content guardian_2_phone_field.titleize
fill_all_fields
fill_in guardian_first_name_field, with: 'Guardian'
fill_in guardian_last_name_field, with: 'Name'
fill_in guardian_phone_field, with: '001101'
fill_in guardian_email_field, with: 'valid@email.com'
attach_file guardian_photo_field, file_fixture('hemsworth.jpeg'), visible: :all
fill_in_guardian_address_fields
draw_signature file_fixture("signature.png"), signature_field
fill_in guardian_2_first_name_field, with: 'Second'
fill_in guardian_2_last_name_field, with: 'Guardian'
fill_in guardian_2_phone_field, with: '999'
click_button submit_release_button
expect(page).to have_content(successful_submission_message)
expect(MaterialRelease.last.guardian_2_first_name).to eq 'Second'
end
scenario "creating release is possible only after filling all fields", js: true do
contract_template = create(:contract_template, project: project)
@@ -185,7 +98,7 @@ feature "User managing material releases" do
sign_in current_user
end
scenario "creating a release for and adult", js: true do
scenario "creating a release", js: true do
visit new_project_material_release_path(project)
by "attaching only a contract" do
@@ -207,94 +120,13 @@ feature "User managing material releases" do
click_button create_release_button
expect(page).to have_content(create_release_notice)
expect(page).to have_photo("material_photo.png", visible: :all)
expect(page).to have_photo("material_photo.png")
click_on "Manage"
expect(page).to have_link("Download")
end
end
scenario "creating a release for a minor - guardian fields are required when minor checkbox is checked", js: true do
visit new_project_material_release_path(project)
all('input[data-required-tag="guardian"]').each do |field|
expect(field['required']).to eq 'false'
expect(field).not_to be_visible
end
page.check person_is_minor_checkbox
all('input[data-required-tag="guardian"]').each do |field|
expect(field['required']).to eq 'true'
expect(field).to be_visible
end
end
scenario "creating a release for a minor", js: true do
visit new_project_material_release_path(project)
expect(page).not_to have_content guardian_information_heading.upcase
expect(page).not_to have_content guardian_photo_heading
page.check person_is_minor_checkbox
expect(page).to have_content guardian_information_heading.upcase
expect(page).to have_content guardian_photo_heading
expect(page).to have_content guardian_email_field.titleize
fill_in_release_fields name: "Apple Laptop"
attach_file contract_field, Rails.root.join(file_fixture("contract.pdf")), visible: false
fill_in guardian_first_name_field, with: 'Guardian'
fill_in guardian_last_name_field, with: 'Name'
fill_in guardian_phone_field, with: '001101'
fill_in guardian_email_field, with: 'valid@email.com'
attach_file guardian_photo_field, file_fixture('hemsworth.jpeg'), visible: :all
fill_in_guardian_address_fields
click_button import_release_button
expect(page).to have_content successful_import_message
expect(MaterialRelease.last.guardian_first_name).to eq 'Guardian'
end
scenario "creating a release for a minor with two guardians", js: true do
visit new_project_material_release_path(project)
expect(page).not_to have_content guardian_2_information_heading.upcase
expect(page).not_to have_content guardian_2_photo_heading
page.check person_is_minor_checkbox
expect(page).to have_content guardian_information_heading.upcase
expect(page).to have_content guardian_photo_heading
expect(page).to have_content guardian_email_field.titleize
expect(page).to have_content guardian_2_information_heading.upcase
expect(page).to have_content guardian_2_photo_heading
expect(page).to have_content guardian_2_phone_field.titleize
fill_in_release_fields name: "Apple Laptop"
attach_file contract_field, Rails.root.join(file_fixture("contract.pdf")), visible: false
fill_in guardian_first_name_field, with: 'Guardian'
fill_in guardian_last_name_field, with: 'Name'
fill_in guardian_phone_field, with: '001101'
fill_in guardian_email_field, with: 'valid@email.com'
attach_file guardian_photo_field, file_fixture('hemsworth.jpeg'), visible: :all
fill_in_guardian_address_fields
fill_in guardian_2_first_name_field, with: 'Second'
fill_in guardian_2_last_name_field, with: 'Guardian'
fill_in guardian_2_phone_field, with: '999'
click_button import_release_button
expect(page).to have_content successful_import_message
expect(MaterialRelease.last.guardian_first_name).to eq 'Guardian'
expect(MaterialRelease.last.guardian_2_first_name).to eq 'Second'
end
scenario "updating an existing release", js: true do
material_release = create(:material_release, :non_native, project: project)
@@ -358,7 +190,7 @@ feature "User managing material releases" do
click_on "Save Changes"
expect(page).to have_content("The release has been updated")
expect(page).to have_photo("material_photo.png", visible: :all)
expect(page).to have_photo("material_photo.png")
end
scenario "viewing the contract PDF" do
@@ -587,8 +419,8 @@ feature "User managing material releases" do
"material_release[person_address_zip]"
end
def have_photo(filename, visible: true)
have_selector("img[src*='#{filename}']", visible: visible)
def have_photo(filename)
have_selector("img[src*='#{filename}']")
end
def import_material_release_link(project)
@@ -710,107 +542,4 @@ feature "User managing material releases" do
def date_issued
t 'contracts.for_office_use_only.description_labels.date_issued'
end
def person_is_minor_checkbox
'material_release_minor'
end
def guardian_2_first_name_field
t 'helpers.label.material_release.guardian_2_first_name'
end
def guardian_2_last_name_field
t 'helpers.label.material_release.guardian_2_last_name'
end
def guardian_2_phone_field
t 'helpers.label.material_release.guardian_2_phone'
end
def guardian_first_name_field
t 'helpers.label.material_release.guardian_first_name'
end
def guardian_last_name_field
t 'helpers.label.material_release.guardian_last_name'
end
def guardian_phone_field
t 'helpers.label.material_release.guardian_phone'
end
def guardian_email_field
t 'helpers.label.material_release.guardian_email'
end
def guardian_address_street1_field
t('helpers.label.material_release.guardian_address_street1')
end
def guardian_address_city_field
t('helpers.label.material_release.guardian_address_city')
end
def guardian_address_state_field
t('helpers.label.material_release.guardian_address_state')
end
def guardian_address_zip_field
t('helpers.label.material_release.guardian_address_zip')
end
def guardian_photo_field
'material_release[guardian_photo]'
end
def guardian_2_photo_field
'material_release[guardian_2_photo]'
end
def fill_in_guardian_address_fields
fill_in guardian_address_street1_field, with: "124 Test Lane"
fill_in guardian_address_city_field, with: "New York"
fill_in guardian_address_state_field, with: "NY"
fill_in guardian_address_zip_field, with: '1000'
end
def submit_release_button
'I have read and agree to the above'
end
def successful_submission_message
'Your release was successfully submitted. Thank you.'
end
def guardian_information_heading
t 'public.material_releases.new.guardian_info.heading'
end
def guardian_photo_heading
t 'public.material_releases.new.guardian_photo.heading'
end
def guardian_2_information_heading
t 'public.material_releases.new.guardian_2_info.heading'
end
def guardian_2_photo_heading
t 'public.material_releases.new.guardian_2_photo.heading'
end
def contract_field
'material_release[contract]'
end
def import_release_button
t 'helpers.submit.material_release.create'
end
def successful_import_message
t 'material_releases.create.notice'
end
def signature_field
'material_release_signature_base64'
end
end