Skip to content

Commit 8cfc3a8

Browse files
authored
Merge pull request #131 from lsa-mis/staging
staging into main 20250521
2 parents 8aef334 + beee551 commit 8cfc3a8

36 files changed

Lines changed: 628 additions & 327 deletions

.github/workflows/brakeman.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ on:
88
schedule:
99
- cron: '0 0 * * 0' # Run weekly on Sunday
1010

11+
permissions:
12+
contents: read
13+
actions: write
14+
1115
jobs:
1216
security:
1317
runs-on: ubuntu-latest

app/controllers/bulk_contest_instances_controller.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def create_contest_instances
7171
new_instance.date_closed = params[:bulk_contest_instance_form][:date_closed]
7272
new_instance.created_by = current_user.email
7373
new_instance.active = false
74-
new_instance.archived = false
7574

7675
# Copy relationships if they exist
7776
if last_instance

app/controllers/contest_descriptions_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def set_contest_description
8787
end
8888

8989
def contest_description_params
90-
params.require(:contest_description).permit(:created_by, :active, :archived,
90+
params.require(:contest_description).permit(:created_by, :active,
9191
:eligibility_rules, :name, :notes,
9292
:short_name,
9393
:container_id)

app/controllers/contest_instances_controller.rb

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,42 @@ def export_entries
177177
end
178178
end
179179

180+
def export_round_results
181+
begin
182+
@contest_instance = @contest_description.contest_instances.find(params[:id])
183+
authorize @contest_instance, :export_entries?
184+
185+
round_id = params[:round_id]
186+
judging_round = @contest_instance.judging_rounds.find_by(id: round_id)
187+
188+
if judging_round.nil?
189+
redirect_to container_contest_description_contest_instance_path(@container, @contest_description, @contest_instance),
190+
alert: 'Judging round not found.'
191+
return
192+
end
193+
194+
@entries = judging_round.entries.distinct.includes(
195+
:profile, :category,
196+
entry_rankings: [:user]
197+
)
198+
199+
respond_to do |format|
200+
format.csv do
201+
filename = "#{@contest_description.name.parameterize}-round-#{judging_round.round_number}-results-#{Time.zone.today}.csv"
202+
203+
csv_data = generate_round_results_csv(@entries, @contest_description, @contest_instance, judging_round)
204+
205+
send_data csv_data,
206+
type: 'text/csv; charset=utf-8; header=present',
207+
disposition: "attachment; filename=#{filename}"
208+
end
209+
end
210+
rescue Pundit::NotAuthorizedError
211+
flash[:alert] = 'Not authorized to access this contest instance'
212+
redirect_to root_path
213+
end
214+
end
215+
180216
private
181217

182218
def authorize_container_access
@@ -202,7 +238,7 @@ def redirect_to_contest_instance_path
202238

203239
def contest_instance_params
204240
params.require(:contest_instance).permit(
205-
:active, :archived, :contest_description_id, :date_open, :date_closed,
241+
:active, :contest_description_id, :date_open, :date_closed,
206242
:notes, :judging_open, :judge_evaluations_complete,
207243
:maximum_number_entries_per_applicant, :require_pen_name,
208244
:require_campus_employment_info, :require_finaid_info, :created_by,
@@ -255,4 +291,66 @@ def generate_entries_csv(entries, contest_description, contest_instance)
255291
end
256292
end
257293
end
294+
295+
def generate_round_results_csv(entries, contest_description, contest_instance, judging_round)
296+
require 'csv'
297+
298+
CSV.generate do |csv|
299+
# Header section
300+
contest_info = "#{contest_description.name} - Round #{judging_round.round_number} Results"
301+
header_row1 = [contest_info] + Array.new(15, '')
302+
csv << header_row1
303+
csv << Array.new(16, '') # Empty row as separator
304+
305+
# Column headers
306+
headers = [
307+
'Title', 'Category',
308+
'Pen Name', 'First Name', 'Last Name', 'UMID', 'Uniqname',
309+
'Class Level', 'Campus', 'Entry ID', 'Selected for Next Round',
310+
'Judge Name', 'Score', 'Judge Comments [External]', 'Judge Comments [Internal]'
311+
]
312+
csv << headers
313+
314+
# Entry data
315+
entries.each do |entry|
316+
profile = entry.profile
317+
rankings = entry.entry_rankings.where(judging_round: judging_round)
318+
selected = rankings.exists?(selected_for_next_round: true)
319+
320+
# Base entry data
321+
base_data = [
322+
entry.title,
323+
entry.category&.kind,
324+
entry.pen_name,
325+
profile&.user&.first_name,
326+
profile&.user&.last_name,
327+
profile&.umid,
328+
profile&.user&.uniqname,
329+
profile&.class_level&.name,
330+
profile&.campus&.campus_descr,
331+
entry.id,
332+
selected ? 'Yes' : 'No'
333+
]
334+
335+
# If there are rankings, create a row for each judge's ranking
336+
if rankings.any?
337+
rankings.each do |ranking|
338+
score = ranking.rank
339+
external_comments = ranking.external_comments.presence || 'No comment entered'
340+
internal_comments = ranking.internal_comments.presence || 'No comment entered'
341+
342+
csv << base_data + [
343+
"#{ranking.user.display_name_or_first_name_last_name} (#{ranking.user.uid})",
344+
score,
345+
external_comments,
346+
internal_comments
347+
]
348+
end
349+
else
350+
# If no rankings, just output the base data with empty judge fields
351+
csv << base_data + [ '', '' ]
352+
end
353+
end
354+
end
355+
end
258356
end

app/controllers/entries_controller.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class EntriesController < ApplicationController
22
include AvailableContestsConcern
3-
before_action :set_entry, only: %i[ show edit update destroy soft_delete toggle_disqualified ]
3+
before_action :set_entry, only: %i[ show edit update destroy soft_delete toggle_disqualified modal_details ]
44
before_action :set_entry_for_profile, only: %i[ applicant_profile ]
55
before_action :authorize_entry, only: %i[show edit update destroy]
66
before_action :authorize_index, only: [ :index ]
@@ -16,6 +16,12 @@ def show
1616
authorize @entry
1717
end
1818

19+
# GET /entries/1/modal_details
20+
def modal_details
21+
authorize @entry, :show?
22+
render layout: false
23+
end
24+
1925
# GET /entries/new
2026
def new
2127
contest_instance_id = params[:contest_instance_id]

app/helpers/entries_helper.rb

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,4 @@ def disqualified_icon(entry)
66
''
77
end
88
end
9-
10-
def archived_icon(entry)
11-
if entry.archived
12-
content_tag(:i, '', class: 'bi bi-eye', style: 'font-size: 1.5rem;', aria: { hidden: 'true' })
13-
else
14-
''
15-
end
16-
end
179
end

app/models/container.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def active_entries
8080
Entry.joins(contest_instance: { contest_description: :container })
8181
.where(containers: { id: id })
8282
.where(deleted: false)
83-
.where(contest_instances: { active: true, archived: false })
84-
.where(contest_descriptions: { active: true, archived: false })
83+
.where(contest_instances: { active: true })
84+
.where(contest_descriptions: { active: true })
8585
end
8686
end

app/models/contest_description.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,4 @@ class ContestDescription < ApplicationRecord
3131
validates :name, presence: true, uniqueness: true
3232

3333
scope :active, -> { where(active: true) }
34-
scope :archived, -> { where(archived: true) }
3534
end

app/models/contest_instance.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class ContestInstance < ApplicationRecord
5959

6060
# Scopes
6161
scope :active_and_open, -> {
62-
where(active: true, archived: false)
62+
where(active: true)
6363
.where('date_open <= ? AND date_closed >= ?', Time.zone.now, Time.zone.now)
6464
}
6565

@@ -89,7 +89,7 @@ class ContestInstance < ApplicationRecord
8989
}
9090

9191
def open?
92-
active && !archived && Time.current.between?(date_open, date_closed)
92+
active && Time.current.between?(date_open, date_closed)
9393
end
9494

9595
def judging_open?(user = nil)

app/policies/entry_policy.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@ def toggle_disqualified?
1414
def view_applicant_profile?
1515
container = record.contest_instance.contest_description.container
1616
record.profile.user == user ||
17-
user&.has_container_role?(container, ['Collection Administrator', 'Collection Manager']) ||
17+
user&.has_container_role?(container, [ 'Collection Administrator', 'Collection Manager' ]) ||
18+
axis_mundi?
19+
end
20+
21+
def show?
22+
# Allow users to see their own entries
23+
return true if record.profile.user == user
24+
25+
# Allow collection admins/managers to see entries from their containers
26+
container = record.contest_instance.contest_description.container
27+
return true if user&.has_container_role?(container)
28+
29+
# Allow judges to see entries they've been assigned to judge
30+
return true if user&.judging_assignments&.pluck(:contest_instance_id)&.include?(record.contest_instance_id)
31+
32+
# Fall back to axis_mundi check
1833
axis_mundi?
1934
end
2035

0 commit comments

Comments
 (0)