Skip to content

Commit

Permalink
Merge branch 'dev' into 207_sort_and_filter_component
Browse files Browse the repository at this point in the history
  • Loading branch information
stepchud authored Dec 18, 2024
2 parents 1c1b1dd + e1ce5be commit d7bef8a
Show file tree
Hide file tree
Showing 36 changed files with 711 additions and 92 deletions.
2 changes: 1 addition & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ exclude_patterns:
- vendor/
- "**/vendor/**/*"
- app/assets/images/
- spec/**/*
- spec/**/*
10 changes: 10 additions & 0 deletions .env_login → .env_dev
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@ export LOGOUT_REDIRECT_EVAL_URL=http://localhost:3000/
export PHOENIX_URI="http://localhost:4000"
export LOGIN_SECRET="f4d3c40a00a8e6ed72fae5204d9ddacd40f087865d40803a6fcfb935591a271838533f06081067dac24c0085c74123e7e1c8b3e0ab562c6645b17eb769854d0d"
export JWT_SECRET="fc28c5738ca45162f61126e770a8fbdbd938d0fedcfe8fbb9f851b855b0264866364a9130e96aca8b1977e9f58edf064f1aa435ceccf415ff22fd3c24adba320"

# static pages on localhost
# export PAGES_DOMAIN="localhost:4001"
# export PAGES_HOST="http://localhost:4001"
# export PAGES_BASE_URL="/"

# static pages on eval-dev branch
export PAGES_DOMAIN="federalist-2c628203-05c2-48ab-8f87-3eda79380559.sites.pages.cloud.gov"
export PAGES_HOST="https://federalist-2c628203-05c2-48ab-8f87-3eda79380559.sites.pages.cloud.gov"
export PAGES_BASE_URL="/preview/gsa/challenges-and-prizes/eval-dev"
2 changes: 1 addition & 1 deletion .envrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ mkdir -p .nix-bundler
export BUNDLE_PATH=./.nix-bundler

# Login Env Vars
source .env_login
source .env_dev
1 change: 1 addition & 0 deletions .simplecov
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ SimpleCov.start 'rails' do
add_filter '/app/jobs/application_job.rb'
add_filter '/app/mailers/application_mailer.rb'
add_filter '/app/models/application_record.rb'
add_filter '/app/controllers/pages_controller.rb'

add_filter '/app/controllers/sandbox_controller.rb'

Expand Down
2 changes: 1 addition & 1 deletion DEVCONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Once direnv is installed and your shell is restarted, clone the project and `cd`
```
./bin/dev
```
> _NOTE for login.gov configuration_ -- if you are **not** using direnv/nix to eval `.envrc`, you can run `source .env_login` in your terminal before starting the server or add the env vars in that file to your local environment directly.
> _NOTE for login.gov configuration_ -- if you are **not** using direnv/nix to eval `.envrc`, you can run `source .env_dev` in your terminal before starting the server or add the env vars in that file to your local environment directly.
Now you can visit [`localhost:3000`](http://localhost:3000) from your browser.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,5 @@ end
gem "factory_bot", "~> 6.5"

gem "faker", "~> 3.4"

gem "rails-reverse-proxy"
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails-reverse-proxy (0.13.0)
actionpack
addressable
railties (7.2.2)
actionpack (= 7.2.2)
activesupport (= 7.2.2)
Expand Down Expand Up @@ -398,6 +401,7 @@ DEPENDENCIES
puma (>= 6.4.3)
rails (~> 7.2.1)
rails-controller-testing
rails-reverse-proxy
rspec-rails
rspec_junit_formatter
rubocop (>= 1.66.0)
Expand Down
36 changes: 35 additions & 1 deletion app/controllers/evaluation_forms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class EvaluationFormsController < ApplicationController
before_action -> { authorize_user('challenge_manager') }
before_action :set_evaluation_form, only: %i[show edit update destroy]
before_action :set_evaluation_forms, only: %i[index]
before_action :set_available_phases, only: %i[new create edit update]

# GET /evaluation_forms or /evaluation_forms.json
def index; end
Expand Down Expand Up @@ -80,6 +81,20 @@ def set_evaluation_forms
includes([:challenge, :phase])
end

def set_available_phases
current_phase_id = @evaluation_form&.phase_id

@available_phases =
current_user.challenge_manager_challenges.includes(:phases).map do |challenge|
{
challenge:,
phases: challenge.phases.reject do |phase|
current_phase_id != phase.id && EvaluationForm.exists?(phase_id: phase.id)
end
}
end
end

# Only allow a list of trusted parameters through.
def evaluation_form_params
permitted = params.require(:evaluation_form).
Expand All @@ -91,7 +106,15 @@ def evaluation_form_params
{ option_labels: {} }
])
closing_date = parse_closing_date(permitted[:closing_date])
closing_date ? permitted.merge({ closing_date: }) : permitted
permitted = permitted.merge({ closing_date: }) if closing_date

if action_name == "update"
# Update action may only allow closing_date depending on phase.end_date
handle_upate_permitted_params(permitted)
else
# Create action always allows all params
permitted
end
end

def parse_closing_date(input_date)
Expand All @@ -104,4 +127,15 @@ def parse_closing_date(input_date)
input_date
end
end

def handle_upate_permitted_params(permitted)
evaluation_form = EvaluationForm.find(params[:id])
phase = evaluation_form.phase

if phase&.end_date&.past?
permitted.slice(:closing_date)
else
permitted
end
end
end
64 changes: 64 additions & 0 deletions app/controllers/pages_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

# Proxy Cloud.gov pages content at the root of the application
class PagesController < ApplicationController
include ReverseProxy::Controller
# We must remove this for proxy of JS assets to be loaded by the browser
protect_from_forgery except: :assets

# TODO: When launched, the cloud.gov pages need to move off the www.challenge.gov domain
DOMAIN = Rails.configuration.static_site_interop.fetch(:domain)
HOST = Rails.configuration.static_site_interop.fetch(:host)
BASE_URL = Rails.configuration.static_site_interop.fetch(:base_url)

def index
path = "#{BASE_URL}/#{params[:path]}/"
reverse_proxy(HOST, path:, reset_accept_encoding: true, headers: { host: DOMAIN }) do |config|
config.on_missing do |_code, _response|
redirect_to "/dashboard"
return true
end

config.on_response do |_code, response|
response.body = rewrite_links(response.body)
end
end
end

def assets
if params[:ext] == "min"
path = "#{HOST}#{BASE_URL}/assets/#{params[:path]}.#{params[:ext]}.js"
response = Faraday.get(path)
send_data(response.body, type: 'application/javascript')
else
path = "#{BASE_URL}/assets/#{params[:path]}.#{params[:ext]}"
reverse_proxy(HOST, path:, reset_accept_encoding: true, headers: { host: DOMAIN })
end
end

def root
path = "#{BASE_URL}/"
reverse_proxy(HOST, path:, reset_accept_encoding: true, headers: { host: DOMAIN }) do |config|
config.on_response do |_code, response|
if response.body.present?
response.body = rewrite_links(response.body)
end
end
end
end

private

def rewrite_links(html)
parsed_html = html.gsub(HOST, "/")
if BASE_URL.length > 1
parsed_html = parsed_html.gsub(BASE_URL, "")
end
# delete the data-public-url attribute from the react app element to send requests through rails proxy
parsed_html = parsed_html.sub(/(<div id="challenge-gov-react-app".+)(data-public-url=[^ ]+)/, '\1')

# rubocop:disable Rails/OutputSafety
parsed_html.html_safe
# rubocop:enable Rails/OutputSafety
end
end
7 changes: 4 additions & 3 deletions app/controllers/phases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ def index

def submissions
@submissions = @phase.submissions

@submissions_count = @submissions.count

@not_started = @submissions.where.missing(:evaluations)

@in_progress = @submissions.joins(:evaluations).
where(evaluations: { completed_at: nil })
where(evaluations: { completed_at: nil }).distinct

@completed = @submissions.joins(:evaluations).
where.not(evaluations: { completed_at: nil })
where.not(evaluations: { completed_at: nil }).distinct

@submissions_by_status = {
not_started: @not_started.count,
Expand Down
12 changes: 12 additions & 0 deletions app/helpers/evaluation_forms_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ def challenge_phase_title(challenge, phase)
"#{challenge.title} - Phase #{phase_number(challenge, phase)}"
end

def options_for_available_phases(available_phases)
available_phases.flat_map do |entry|
challenge = entry[:challenge]
entry[:phases].map do |phase|
[
challenge_phase_title(challenge, phase).to_s,
"#{challenge.id}.#{phase.id}.#{phase.end_date.strftime('%m/%d/%Y')}"
]
end
end
end

def evaluation_period(evaluation_form)
start_date = evaluation_form.phase.end_date.strftime("%m/%d/%Y")
end_date = evaluation_form.closing_date.strftime("%m/%d/%Y")
Expand Down
4 changes: 3 additions & 1 deletion app/javascript/controllers/evaluation_form_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export default class extends Controller {

// Opens all accordions, remove existing points/weights, update max points/weights values
updateMaxPoints(e) {
const form = e.target.closest('form[data-controller="evaluation-form"]');
const form = e.target.closest(
'form[data-controller="evaluation-form modal"]'
);
const pointsWeights = form.querySelectorAll(".points-or-weight");
const weightedScale = e.target.value === "true";

Expand Down
3 changes: 3 additions & 0 deletions app/javascript/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ application.register("evaluation-form", EvaluationFormController);
import HotdogController from "./hotdog_controller";
application.register("hotdog", HotdogController);

import ModalController from "./modal_controller";
application.register("modal", ModalController);

import SortFilterMenuController from "./sort_filter_menu_controller";
application.register("sort-filter-menu", SortFilterMenuController);

Expand Down
87 changes: 87 additions & 0 deletions app/javascript/controllers/modal_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["modal"];
static values = {
modalId: String,
};

open(event) {
const modalId = event.currentTarget.dataset.modalTargetId;
const modal = this.modalTargets.find((modal) => modal.id === modalId);

this.openEvent = event;

event.preventDefault();

if (modal) {
modal.showModal();
} else {
console.warn(`Modal with ID '${modalId}' not found.`);
}
}

confirm(event) {
const modal = this._getModal(event);

if (modal) {
const confirmRedirect = modal.dataset.modalConfirmRedirect;
const confirmAction = modal.dataset.modalConfirmAction;

if (confirmRedirect) {
window.location.href = confirmRedirect;
} else if (confirmAction) {
this.invokeAction(confirmAction);
modal.close();
} else {
modal.close();
return true;
}
}
}

cancel(event) {
const modal = this._getModal(event);

if (modal) {
const cancelRedirect = modal.dataset.modalCancelRedirect;
const cancelAction = modal.dataset.modalCancelAction;

if (cancelRedirect) {
window.location.href = cancelRedirect;
} else if (cancelAction) {
this.invokeAction(cancelAction);
modal.close();
} else {
modal.close();
return false;
}
}
}

_getModal(event) {
return event.currentTarget.closest("dialog");
}

invokeAction(actionName) {
const [controllerName, action] = actionName.split("#");
const controllerElement = document.querySelector(
`[data-controller~="${controllerName}"]`
);

if (!controllerElement) {
console.warn(`Controller element for ${controllerName} not found.`);
}

const controller = this.application.getControllerForElementAndIdentifier(
controllerElement,
controllerName
);

if (controller && typeof controller[action] === "function") {
controller[action](this.openEvent);
} else {
console.warn(`Action ${actionName} not found on ${controllerName}`);
}
}
}
4 changes: 4 additions & 0 deletions app/models/evaluation_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,13 @@ class EvaluationForm < ApplicationRecord
validates :instructions, presence: true
validates :closing_date, presence: true

validates :phase_id, uniqueness: true

validate :criteria_weights_must_sum_to_one_hundred
validate :validate_unique_criteria_titles

private

def validate_unique_criteria_titles
titles = evaluation_criteria.reject(&:marked_for_destruction?).map(&:title)

Expand Down
4 changes: 3 additions & 1 deletion app/models/submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Submission < ApplicationRecord
belongs_to :manager, class_name: 'User'
has_many :evaluator_submission_assignments, dependent: :destroy
has_many :evaluators, through: :evaluator_submission_assignments, class_name: "User"
has_many :evaluations, through: :evaluator_submission_assignments
has_many :evaluations, through: :evaluator_submission_assignments, dependent: :destroy

# Fields
attribute :title, :string
Expand All @@ -61,6 +61,8 @@ class Submission < ApplicationRecord
none
end
}
scope :eligible_for_evaluation, -> { where(judging_status: [:selected, :winner]) }

def eligible_for_evaluation?
selected? or winner?
end
Expand Down
Loading

0 comments on commit d7bef8a

Please sign in to comment.