Creating view to show confirmed matches, fixing bugs in permission logic and making proposal status updates ajax requests

This commit is contained in:
Absurdon 2017-10-07 12:28:06 +02:00
parent 548b15489c
commit 62bc78274a
7 changed files with 88 additions and 33 deletions

View file

@ -1,5 +1,5 @@
class ContestsController < ApplicationController
before_filter :get_contest, only: [:show, :edit, :update, :destroy, :del_map, :scores, :recalc]
before_filter :get_contest, only: [:show, :edit, :update, :destroy, :del_map, :scores, :recalc, :confirmed_matches]
def index
# @contests = Contest.all
@ -102,6 +102,10 @@ class ContestsController < ApplicationController
redirect_to contests_url
end
def confirmed_matches
@match_props = MatchProposal.confirmed_for_contest(@contest)
end
private
def get_contest

View file

@ -1,6 +1,7 @@
class MatchProposalsController < ApplicationController
before_filter :get_match
def index
raise AccessError unless @match.user_in_match?(cuser)
end
def new
@ -25,25 +26,35 @@ class MatchProposalsController < ApplicationController
end
def update
@proposal = MatchProposal.find(params[:id])
raise AccessError unless @proposal.can_update?(cuser, params[:match_proposal])
@proposal.status = params[:match_proposal][:status]
if @proposal.save
# TODO: rework messages
# TODO: make it so only one proposal can be confirmed for a match at any given time
action = case @proposal.status
when MatchProposal::STATUS_CONFIRMED
"Confirmed Proposal for #{Time.use_zone(view_context.timezone_offset) { @proposal.proposed_time.strftime('%d %B %y %H:%M %Z') }}"
when MatchProposal::STATUS_REJECTED
"Rejected Proposal for #{Time.use_zone(view_context.timezone_offset) { @proposal.proposed_time.strftime('%d %B %y %H:%M %Z') }}"
else
"Smthn went wrong"
end
flash[:notice] = action
else
flash[:notice] = "Error"
raise AccessError unless request.xhr? # Only respond to ajax requests
rjson = {}
proposal = MatchProposal.find(params[:id])
unless proposal
rjson[:error] = {
code: 404,
message: "No proposal with id #{params[:id]}"
}
render(json: rjson, status: :not_found) && return
end
unless proposal.can_update?(cuser, params[:match_proposal])
rjson[:error] = {
code: 403,
message: "You are not allowed to update the state to #{MatchProposal.status_strings[params[:match_proposal][:status].to_i]}"
}
render(json: rjson, status: :forbidden) && return
end
proposal.status = params[:match_proposal][:status]
if proposal.save
rjson[:status] = MatchProposal.status_strings[proposal.status]
rjson[:message] = "Successfully updated status to #{MatchProposal.status_strings[proposal.status]}"
render(json: rjson, status: :success)
else
rjson[:error] = {
code: 500,
message: 'Something went wrong! Please try again.'
}
render(json: rjson, status: 500)
end
redirect_to(match_proposals_path(@match))
end
private

View file

@ -19,8 +19,10 @@ class MatchProposal < ActiveRecord::Base
scope :of_match, ->(match) { where('match_id = ?', match.id) }
scope :confirmed_for_match, ->(match) { where('match_id = ? AND status = ?', match.id, STATUS_CONFIRMED) }
scope :confirmed_upcoming, ->{ where('status = ? AND proposed_time > UTC_TIMESTAMP()', STATUS_CONFIRMED) }
scope :confirmed_for_contest,
->(contest){ includes(:match).where(matches:{contest_id: contest.id}, status: STATUS_CONFIRMED).all}
def status_strings
def self.status_strings
{STATUS_PENDING => 'Pending',
STATUS_REVOKED => 'Revoked',
STATUS_REJECTED => 'Rejected',
@ -47,7 +49,11 @@ class MatchProposal < ActiveRecord::Base
cuser && cuser.admin?
end
private
def state_immutable?
status == STATUS_REJECTED ||
status == STATUS_DELAYED ||
status == STATUS_REVOKED
end
def status_change_allowed?(cuser, new_status)
case new_status
@ -58,17 +64,20 @@ class MatchProposal < ActiveRecord::Base
# only confirmed matches can be set to delayed
# only admins can set matches to delayed and only if they are not playing in that match
# matches can only be delayed if they are not to far in the future
return false unless self.status == STATUS_CONFIRMED && cuser.admin? &&
return self.status == STATUS_CONFIRMED && cuser.admin? &&
!self.match.user_in_match?(cuser) && self.proposed_time <= CONFIRMATION_LIMIT.minutes.from_now
when STATUS_REVOKED
# unconfirmed can only be revoked by team making the proposal
# confirmed can only be revoked if soon enough before match time
return false unless self.status == STATUS_PENDING && self.team == cuser.team ||
return self.status == STATUS_PENDING && self.team == cuser.team ||
self.status == STATUS_CONFIRMED && self.proposed_time > CONFIRMATION_LIMIT.minutes.from_now
when STATUS_CONFIRMED, STATUS_REJECTED
# only team proposed to can reject or confirm and only if soon enough before match time
return false unless self.status == STATUS_PENDING && self.team != cuser.team &&
self.proposed_time < CONFIRMATION_LIMIT.minutes.from_now
status_ok = self.status == STATUS_PENDING
team_ok = self.team != cuser.team
time_ok = CONFIRMATION_LIMIT.minutes.from_now < self.proposed_time
return status_ok && team_ok && time_ok
else
# invalid status
return false

View file

@ -0,0 +1,13 @@
<h1 class="title">Confirmed Matches for <%= @contest.name %></h1>
<table class="striped">
<tr>
<th>Match</th>
<th>Scheduled for</th>
</tr>
<% @match_props && @match_props.each do |mp| %>
<tr>
<th><%= link_to mp.match.contester1, contester_path(mp.match.contester1) %> VS <%= link_to mp.match.contester2, contester_path(mp.match.contester2) %></th>
<th><%= longertime(mp.proposed_time) %></th>
</tr>
<% end %>
</table>

View file

@ -1,7 +1,8 @@
<h1>Proposals</h1>
<h2><%= @match.contester1 %> VS <%= @match.contester2 %></h2>
<%= link_to 'Back', match_path(@match), class: 'button' %>
<% if @match.match_proposals.empty? %>
<h4>There are no proposals yet</h4>
<h4 style="clear: both;">There are no proposals yet</h4>
<% else %>
<table id="proposals" class="striped">
<tr>
@ -16,13 +17,19 @@
<tr class="<%=cycle('even', 'odd') %>">
<td><%= proposal.team.name %></td>
<td><%= longtime proposal.proposed_time %> </td>
<td><%= proposal.status_strings[proposal.status] %></td>
<td><%= MatchProposal.status_strings[proposal.status] %></td>
<% if @match.can_make_proposal?(cuser) %>
<td>
<%= form_for proposal, url: match_proposal_path(@match, proposal) do |f| %>
<%= f.hidden_field :status, value: 0 %>
<%= link_to_function icon('check'), "proposalStateSubmit(#{MatchProposal::STATUS_CONFIRMED},#{proposal.id})" %>
<%= link_to_function icon('times'), "proposalStateSubmit(#{MatchProposal::STATUS_REJECTED},#{proposal.id})" %>
<% unless proposal.state_immutable? %>
<%= form_for proposal, url: match_proposal_path(@match, proposal) do |f| %>
<%= f.hidden_field :status, value: 0 %>
<%= link_to_function icon('check'), "proposalStateSubmit(#{MatchProposal::STATUS_CONFIRMED},#{proposal.id})", title: 'Confirm' %>
<%= link_to_function icon('times'), "proposalStateSubmit(#{MatchProposal::STATUS_REJECTED},#{proposal.id})", title: 'Reject' %>
<%= link_to_function icon('eraser'), "proposalStateSubmit(#{MatchProposal::STATUS_REVOKED},#{proposal.id})", title: 'Revoke' %>
<% if proposal.status_change_allowed?(cuser, MatchProposal::STATUS_DELAYED) %>
<%= link_to_function icon('hourglass'), "proposalStateSubmit(#{MatchProposal::STATUS_DELAYED},#{proposal.id})", title: 'Delay' %>
<% end %>
<% end %>
<% end %>
</td>
<% end %>
@ -33,7 +40,16 @@
function proposalStateSubmit(newState, formID) {
var form = $('#edit_match_proposal_' + formID);
form.children("input[type='hidden']").val(newState);
form.submit();
$.post(form.attr('action'),form.serialize(), function(data) {
datajson = data.responseText;
tr = form.closest('tr');
tr.children('td').eq(1).html(datajson.status);
alert(datajson.message);
}, 'json')
.error(function (err) {
errjson = JSON.parse(err.responseText);
alert(errjson.error.message);
});
}
</script>
<% end %>

View file

@ -116,7 +116,7 @@
<p><%= @match.report.html_safe %></p>
</div>
<% end %>
<% if cuser and (cuser.admin? or @match.of_user cuser) %>
<% if cuser and (cuser.admin? or @match.user_in_match? cuser) %>
<div class="referee">
<%= link_to 'Proposals', match_proposals_path(@match), class: 'button' %>
</div>

View file

@ -51,6 +51,8 @@ Ensl::Application.routes.draw do
resources :users
resources :locks
resources :contesters
get "contests/:id/confirmedmatches" => "contests#confirmed_matches", as: :confirmed_matches
resources :contests
resources :challenges
resources :servers