• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MarkUsProject / Markus / 10928630900

18 Sep 2024 07:13PM UTC coverage: 51.86% (-39.7%) from 91.541%
10928630900

push

github

web-flow
Enable cron-based automatic syncing of LTI rosters (#7178)

574 of 1298 branches covered (44.22%)

Branch coverage included in aggregate %.

2 of 20 new or added lines in 4 files covered. (10.0%)

1886 existing lines in 60 files now uncovered.

3818 of 7171 relevant lines covered (53.24%)

76.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

21.95
/app/controllers/courses_controller.rb
1
# Manages actions relating to editing and modifying
2
# courses.
3
class CoursesController < ApplicationController
1✔
4
  before_action { authorize! }
3✔
5

6
  respond_to :html
1✔
7
  layout 'assignment_content'
1✔
8

9
  def index
1✔
10
    # Force browsers not to cache the index page
11
    # to prevent attempting to render course_list
12
    # with cached HTML instead of requesting the json
13
    response.set_header('Cache-Control', 'no-store, must-revalidate')
2✔
14
    respond_to do |format|
2✔
15
      format.html { render :index }
3✔
16
      format.json do
2✔
17
        courses = current_user.visible_courses
1✔
18
                              .order('courses.name')
19
                              .pluck_to_hash('courses.id', 'courses.name',
20
                                             'courses.display_name', 'roles.type')
21
        render json: { data: courses }
1✔
22
      end
23
    end
24
  end
25

26
  def edit
1✔
UNCOV
27
    @lti_deployments = @current_course.lti_deployments
×
28
  end
29

30
  def update
1✔
UNCOV
31
    @current_course.update(course_params)
×
UNCOV
32
    update_autotest_url if allowed_to?(:edit?, with: Admin::CoursePolicy)
×
UNCOV
33
    respond_with @current_course, location: -> { edit_course_path(@current_course) }
×
34
  end
35

36
  def show
1✔
UNCOV
37
    if current_role.student? || current_role.ta?
×
UNCOV
38
      redirect_to course_assignments_path(@current_course.id)
×
UNCOV
39
    elsif current_role.instructor?
×
UNCOV
40
      @assignments = @current_course.assignments
×
UNCOV
41
      @grade_entry_forms = @current_course.grade_entry_forms
×
UNCOV
42
      @current_assignment = @current_course.get_current_assignment
×
UNCOV
43
      respond_with(@current_course)
×
44
    end
45
  end
46

47
  # Sets current_user to nil, which clears a role switch session (see role_switch)
48
  def clear_role_switch_session
1✔
UNCOV
49
    MarkusLogger.instance.log("Instructor '#{session[:real_user_name]}' logged out from '#{session[:user_name]}'.")
×
UNCOV
50
    session[:user_name] = nil
×
UNCOV
51
    session[:role_switch_course_id] = nil
×
UNCOV
52
    redirect_to action: 'show'
×
53
  end
54

55
  # Set the current_user. This allows an instructor to view their course from
56
  # the perspective of another (non-instructor) user.
57
  def switch_role
1✔
UNCOV
58
    if params[:effective_user_login].blank?
×
UNCOV
59
      render partial: 'role_switch_handler',
×
60
             formats: [:js], handlers: [:erb],
61
             locals: { error: I18n.t('main.username_not_blank') },
62
             status: :not_found
UNCOV
63
      return
×
64
    end
65

UNCOV
66
    found_user = User.find_by(user_name: params[:effective_user_login])
×
UNCOV
67
    found_role = Role.find_by(user: found_user, course: current_course)
×
68

UNCOV
69
    if found_role.nil?
×
UNCOV
70
      render partial: 'role_switch_handler',
×
71
             formats: [:js], handlers: [:erb],
72
             locals: { error: Settings.validate_user_not_allowed_message || I18n.t('main.login_failed') },
73
             status: :not_found
UNCOV
74
      return
×
75
    end
76

77
    # Check if the current instructor is trying to role switch as themselves
UNCOV
78
    if found_user.user_name == session[:real_user_name]
×
UNCOV
79
      render partial: 'role_switch_handler',
×
80
             formats: [:js], handlers: [:erb],
81
             locals: { error: I18n.t('main.cannot_role_switch_to_self') },
82
             status: :unprocessable_entity
UNCOV
83
      return
×
84
    end
85

86
    # Otherwise, check if the current instructor is trying to role switch as other instructors
UNCOV
87
    if found_role.admin_role? || (found_role.instructor? && !real_user.admin_user?)
×
UNCOV
88
      render partial: 'role_switch_handler',
×
89
             formats: [:js], handlers: [:erb],
90
             locals: { error: I18n.t('main.cannot_role_switch') },
91
             status: :unprocessable_entity
UNCOV
92
      return
×
93
    end
94

UNCOV
95
    log_role_switch found_user
×
UNCOV
96
    self.current_user = found_user
×
UNCOV
97
    session[:role_switch_course_id] = current_course.id
×
98

UNCOV
99
    session[:redirect_uri] = nil
×
UNCOV
100
    refresh_timeout
×
101
    # All good, redirect to the main page of the viewer, discard
102
    # role switch modal
UNCOV
103
    render partial: 'role_switch_handler',
×
104
           formats: [:js], handlers: [:erb],
105
           locals: { error: nil }
106
  end
107

108
  def role_switch
1✔
109
    # dummy action for remote rjs calls
110
    # triggered by clicking on the "Switch role" link
111
    # please keep.
112
  end
113

114
  def download_assignments
1✔
UNCOV
115
    format = params[:format]
×
UNCOV
116
    case format
×
117
    when 'csv'
UNCOV
118
      output = current_course.get_assignment_list(format)
×
UNCOV
119
      send_data(output,
×
120
                filename: 'assignments.csv',
121
                type: 'text/csv',
122
                disposition: 'attachment')
123
    when 'yml'
UNCOV
124
      output = current_course.get_assignment_list(format)
×
UNCOV
125
      send_data(output,
×
126
                filename: 'assignments.yml',
127
                type: 'text/yml',
128
                disposition: 'attachment')
129
    else
130
      flash[:error] = t('download_errors.unrecognized_format', format: format)
×
131
      redirect_back(fallback_location: course_assignments_path(current_course))
×
132
    end
133
  end
134

135
  def upload_assignments
1✔
136
    begin
UNCOV
137
      data = process_file_upload
×
138
    rescue Psych::SyntaxError => e
UNCOV
139
      flash_message(:error, t('upload_errors.syntax_error', error: e.to_s))
×
140
    rescue StandardError => e
UNCOV
141
      flash_message(:error, e.message)
×
142
    else
UNCOV
143
      if data[:type] == '.csv'
×
UNCOV
144
        result = current_course.upload_assignment_list('csv', data[:contents])
×
UNCOV
145
        flash_csv_result(result)
×
UNCOV
146
      elsif data[:type] == '.yml'
×
UNCOV
147
        result = current_course.upload_assignment_list('yml', data[:contents])
×
UNCOV
148
        if result.is_a?(StandardError)
×
149
          flash_message(:error, result.message)
×
150
        end
151
      end
152
    end
UNCOV
153
    redirect_back(fallback_location: course_assignments_path(current_course))
×
154
  end
155

156
  def destroy_lti_deployment
1✔
UNCOV
157
    deployment = LtiDeployment.find(params[:lti_deployment_id])
×
UNCOV
158
    deployment.destroy!
×
UNCOV
159
    redirect_to edit_course_path(@current_course), status: :see_other
×
160
  end
161

162
  def sync_roster
1✔
163
    deployment = LtiDeployment.find(params[:lti_deployment_id])
×
164
    roles = []
×
165
    if params[:include_tas] == 'true'
×
166
      roles.append(LtiDeployment::LTI_ROLES[:ta])
×
167
    end
168
    if params[:include_students] == 'true'
×
169
      roles.append(LtiDeployment::LTI_ROLES[:learner])
×
170
    end
171
    if params[:include_instructors] == 'true'
×
172
      roles.append(LtiDeployment::LTI_ROLES[:instructor])
×
173
    end
NEW
174
    job_args = {}
×
NEW
175
    job_args[:deployment_id] = deployment.id
×
NEW
176
    job_args[:role_types] = roles
×
NEW
177
    job_args[:can_create_users] = allowed_to?(:lti_manage?, with: UserPolicy)
×
NEW
178
    job_args[:can_create_roles] = allowed_to?(:manage?, with: RolePolicy)
×
NEW
179
    name = "LtiRosterSync_#{deployment.id}_#{root_path.tr!('/', '')}"
×
NEW
180
    if params[:automatic_sync] == 'true'
×
NEW
181
      config = {}
×
NEW
182
      config[:class] = 'ActiveJob::QueueAdapters::ResqueAdapter::JobWrapper'
×
NEW
183
      config[:args] = { job_class: 'LtiRosterSyncJob', arguments: [job_args] }
×
NEW
184
      config[:cron] = Settings.lti.sync_schedule
×
NEW
185
      config[:persist] = true
×
NEW
186
      config[:queue] = LtiRosterSyncJob.queue_name
×
NEW
187
      Resque.set_schedule(name, config)
×
188
    else
NEW
189
      Resque.remove_schedule(name)
×
190
    end
NEW
191
    @current_job = LtiRosterSyncJob.perform_later(job_args)
×
192
    session[:job_id] = @current_job.job_id
×
193
    redirect_to edit_course_path(@current_course)
×
194
  end
195

196
  def lti_deployments
1✔
UNCOV
197
    render json: @current_course.lti_deployments.to_json(include: :lti_client)
×
198
  end
199

200
  private
1✔
201

202
  def log_role_switch(found_user)
1✔
203
    # Log the date that the role switch occurred
UNCOV
204
    m_logger = MarkusLogger.instance
×
UNCOV
205
    if current_user != real_user
×
206
      # Log that the instructor dropped role of another user
207
      m_logger.log("Instructor '#{real_user.user_name}' logged out from '#{current_user.user_name}'.")
×
208
    end
209

UNCOV
210
    if found_user != real_user
×
211
      # Log that the instructor assumed role of another user
UNCOV
212
      m_logger.log("Instructor '#{real_user.user_name}' logged in as '#{found_user.user_name}'.")
×
213
    end
214
  end
215

216
  def course_params
1✔
UNCOV
217
    fields = [:is_hidden, :display_name]
×
UNCOV
218
    fields << :max_file_size if allowed_to?(:edit?, with: Admin::CoursePolicy)
×
UNCOV
219
    params.require(:course).permit(*fields)
×
220
  end
221

222
  def update_autotest_url
1✔
UNCOV
223
    url = params.require(:course).permit(:autotest_url)[:autotest_url]
×
UNCOV
224
    @current_job = AutotestResetUrlJob.perform_later(current_course, url, request.protocol + request.host_with_port)
×
UNCOV
225
    session[:job_id] = @current_job.job_id if @current_job
×
226
  end
227

228
  def flash_interpolation_options
1✔
UNCOV
229
    { resource_name: @current_course.name.presence || @current_course.model_name.human,
×
230
      errors: @current_course.errors.full_messages.join('; ') }
231
  end
232
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc