diff --git a/app/components/user_mentee_applications/list_item_component.html.erb b/app/components/user_mentee_applications/list_item_component.html.erb index 284b1f5a..bad0087c 100644 --- a/app/components/user_mentee_applications/list_item_component.html.erb +++ b/app/components/user_mentee_applications/list_item_component.html.erb @@ -1,5 +1,7 @@
  • - <%= link_to link_text, mentee_application, class: 'font-bold text-lg text-secondary underline hover:no-underline' %> + <%= link_to link_text, + user_mentee_application_mentee_application_state_path(user_mentee_application_id: mentee_application.id, status_name: mentee_application.current_state), + class: 'font-bold text-lg text-secondary underline hover:no-underline' %>

    Status: <%= mentee_application.status.humanize %>

    Submitted: <%= mentee_application.created_at.to_fs(:slash_format) %>

  • diff --git a/app/controllers/user_mentee_applications/mentee_application_states_controller.rb b/app/controllers/user_mentee_applications/mentee_application_states_controller.rb index db9a880d..eb9cda9b 100644 --- a/app/controllers/user_mentee_applications/mentee_application_states_controller.rb +++ b/app/controllers/user_mentee_applications/mentee_application_states_controller.rb @@ -1,42 +1,16 @@ class UserMenteeApplications::MenteeApplicationStatesController < ApplicationController before_action :load_user_mentee_application - before_action -> { authorize :user_only, :application_reviewer? } + skip_before_action :only_authorize_agent - def new; end - - # rubocop:disable Metrics/AbcSize - def create - respond_to do |format| - MenteeApplicationTransitionService.call( - application: @user_mentee_application, - reviewer: current_user, - action: application_state_params[:reviewer_action].to_sym, - note: application_state_params[:note] - ) - @user_mentee_application.reload - format.html { redirect_to user_mentee_applications_path } - format.turbo_stream do - flash.now[:notice] = "Application updated to #{@user_mentee_application.current_state.status.humanize.downcase}" - end - rescue MenteeApplicationTransitionService::InvalidTransitionError => e - format.html do - redirect_to new_user_mentee_applications_mentee_application_state_path(@user_mentee_application), - alert: e.message - end - format.turbo_stream { flash.now[:alert] = e.message } - end + def show + @state = @user_mentee_application.mentee_application_states.find_by!(status: params[:status_name]) + @previous_state = @state.previous_state + @next_state = @state.next_state end - # rubocop:enable Metrics/AbcSize private def load_user_mentee_application - @user_mentee_application = UserMenteeApplication.find(params[:user_mentee_application_id]) - end - - def application_state_params - params - .permit(:reviewer_action, :note) - .merge(status_changed_id: current_user.id) + @user_mentee_application = current_user.mentee_applications.find(params[:user_mentee_application_id]) end end diff --git a/app/models/mentee_application_state.rb b/app/models/mentee_application_state.rb index 630b1cf1..ae4a5349 100644 --- a/app/models/mentee_application_state.rb +++ b/app/models/mentee_application_state.rb @@ -28,7 +28,7 @@ class MenteeApplicationState < ApplicationRecord enum status: { application_received: 0, - coding_challenge_sent: 1, + coding_challenge: 1, coding_challenge_received: 2, coding_challenge_approved: 3, phone_screen_scheduled: 4, @@ -41,4 +41,20 @@ class MenteeApplicationState < ApplicationRecord def valid_transitions MenteeApplicationTransitionService.valid_transitions(status:) end + + def future_state + MenteeApplicationTransitionService.future_state(status:) + end + + def next_state + user_mentee_application.mentee_application_states.where('created_at > ?', created_at).order(:created_at).first + end + + def previous_state + user_mentee_application.mentee_application_states.where('created_at < ?', created_at).order(:created_at).last + end + + def to_param + status + end end diff --git a/app/services/mentee_application_transition_service.rb b/app/services/mentee_application_transition_service.rb index 5ab42b5d..89b1f2e4 100644 --- a/app/services/mentee_application_transition_service.rb +++ b/app/services/mentee_application_transition_service.rb @@ -6,9 +6,9 @@ class InvalidStatusError < StandardError; end STATUS_TRANSITION_MAPPING = { application_received: { valid_transitions: %i[promote reject withdrawn], - promote_transition: :coding_challenge_sent + promote_transition: :coding_challenge }, - coding_challenge_sent: { + coding_challenge: { valid_transitions: %i[promote withdrawn], promote_transition: :coding_challenge_received }, @@ -53,7 +53,7 @@ def call(application:, reviewer:, action:, note: nil) application.mentee_application_states.create!(status: transition_status, reviewer:, note:) # handle side effects case transition_status - when :coding_challenge_sent then code_challenge_sent_side_effects(application) + when :coding_challenge then code_challenge_sent_side_effects(application) when :coding_challenge_approved then coding_challenge_approved_side_effects(application) when :accepted then accepted_side_effects(application) when :rejected then rejected_side_effects(application) @@ -69,6 +69,11 @@ def valid_transitions(status:) STATUS_TRANSITION_MAPPING[status][:valid_transitions] end + def future_state(status:) + status = status.to_sym + STATUS_TRANSITION_MAPPING[status][:promote_transition] + end + private def code_challenge_sent_side_effects(application) diff --git a/app/views/user_mentee_applications/mentee_application_states/_accepted.html.erb b/app/views/user_mentee_applications/mentee_application_states/_accepted.html.erb new file mode 100644 index 00000000..ab6d94e3 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_accepted.html.erb @@ -0,0 +1,3 @@ +

    Accepted!

    + +

    Congratulations, your application to join the Agency of Learning has been accepted. We are finishing up interviews with others. After this process is complete, we will contact you with an invite to join our discord.

    diff --git a/app/views/user_mentee_applications/mentee_application_states/_application_received.html.erb b/app/views/user_mentee_applications/mentee_application_states/_application_received.html.erb new file mode 100644 index 00000000..7feac988 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_application_received.html.erb @@ -0,0 +1,17 @@ +

    What is our Mission?

    +

    At AOL, we bridge the gap between learning and doing. We team up junior developers with seasoned mentors to tackle real-world projects, ensuring that every member not only gains invaluable experience but becomes an integral part of a community that champions each other's growth and success in the tech industry.

    + +

    Who would be a good fit?

    +
      +
    1. Aspiring Developers: Individuals who are passionate about coding and technology, looking for real-world experience. They could be recent graduates, self-taught programmers, or career switchers aiming to break into the tech industry.
    2. +
    3. Continuous Learners: Those who have a genuine curiosity and drive to continuously upskill, learn from others, and share their knowledge.
    4. +
    5. Team Players: Given the collaborative nature of projects, individuals who communicate well, are open to feedback, and can work effectively in a team would thrive in AOL.
    6. +
    7. Solution Seekers: Members who are proactive, ready to tackle challenges head-on, and are always looking for innovative solutions.
    8. +
    9. Community-Focused Individuals: Those who believe in giving back. This includes experienced developers who've landed jobs and are keen on mentoring, guiding, and sharing their experiences with newcomers.
    10. +
    11. Adaptable and Resilient: The tech world is ever-evolving, and projects can be unpredictable. Members who are adaptable to new technologies, methodologies, and can handle setbacks with a positive attitude would be ideal.
    12. +
    13. Ethical and Respectful: Given the diverse nature of most tech communities. it's crucial for members to uphold a standard of respect, understanding, and integrity, ensuring a safe and inclusive environment for all.
    14. +
    +

    In essence, AOL would be perfect for those who are keen on growing both technically and personally, while valuing the strength of a supportive community.

    + +

    What happens if you don't get accepted?

    +

    If you are not accepted, we will try to give you feedback over email. Our goal is your success. It's common for people to re-apply later and be accepted.

    diff --git a/app/views/user_mentee_applications/mentee_application_states/_coding_challenge.html.erb b/app/views/user_mentee_applications/mentee_application_states/_coding_challenge.html.erb new file mode 100644 index 00000000..d8643215 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_coding_challenge.html.erb @@ -0,0 +1,5 @@ +

    Coding Challenge

    + +

    You have been sent an email with a coding challenge. This should take you no more than 2-3 hours to complete.

    + + diff --git a/app/views/user_mentee_applications/mentee_application_states/_coding_challenge_approved.html.erb b/app/views/user_mentee_applications/mentee_application_states/_coding_challenge_approved.html.erb new file mode 100644 index 00000000..72e59362 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_coding_challenge_approved.html.erb @@ -0,0 +1,3 @@ +

    Coding Challenge Approved

    + + diff --git a/app/views/user_mentee_applications/mentee_application_states/_coding_challenge_received.html.erb b/app/views/user_mentee_applications/mentee_application_states/_coding_challenge_received.html.erb new file mode 100644 index 00000000..6ef80080 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_coding_challenge_received.html.erb @@ -0,0 +1,5 @@ +

    Coding Challenge Received

    + +

    You have submitted your solution to our coding challenge. We will contact you after our team has reviewed your work.

    + + diff --git a/app/views/user_mentee_applications/mentee_application_states/_phone_screen_completed.html.erb b/app/views/user_mentee_applications/mentee_application_states/_phone_screen_completed.html.erb new file mode 100644 index 00000000..1796d692 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_phone_screen_completed.html.erb @@ -0,0 +1,3 @@ +

    Phone Screen Completed

    + + diff --git a/app/views/user_mentee_applications/mentee_application_states/_phone_screen_scheduled.html.erb b/app/views/user_mentee_applications/mentee_application_states/_phone_screen_scheduled.html.erb new file mode 100644 index 00000000..151a3d65 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_phone_screen_scheduled.html.erb @@ -0,0 +1,5 @@ +

    Phone Screen

    + +

    We would like to schedule a zoom call with you. This call is a quick interview with one of our senior members. It will last about 30 minutes. We will have read your application and reviewed your code. The ideal call is an informal conversation about your goals and your coding journey. There will also be an opportunity for you to ask us questions as well.

    + +

    Visit this calendly link to book a time: <%= link_to "Schedule Link", "https://savvycal.com/davepaola/f70795af", target: "_blank", rel: "nofollow", class: "link link-primary" %>

    diff --git a/app/views/user_mentee_applications/mentee_application_states/_rejected.html.erb b/app/views/user_mentee_applications/mentee_application_states/_rejected.html.erb new file mode 100644 index 00000000..28675cae --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_rejected.html.erb @@ -0,0 +1,9 @@ +

    Rejected

    + +

    + Thank you for submitting your application to join us at the Agency of Learning! +

    + +

    + Unfortunately you have not been accepted into the upcoming batch. This was a tough decision. We can't make any promises about being admitted on a future application, but we hope that you reapply for a future batch. +

    diff --git a/app/views/user_mentee_applications/mentee_application_states/_withdrawn.html.erb b/app/views/user_mentee_applications/mentee_application_states/_withdrawn.html.erb new file mode 100644 index 00000000..0e369fed --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/_withdrawn.html.erb @@ -0,0 +1,6 @@ +

    Withdrawn

    + +

    You have withdrawn your application to the Agency.

    + + +

    We encourage you to reapply in the future.

    diff --git a/app/views/user_mentee_applications/mentee_application_states/show.html.erb b/app/views/user_mentee_applications/mentee_application_states/show.html.erb new file mode 100644 index 00000000..712be163 --- /dev/null +++ b/app/views/user_mentee_applications/mentee_application_states/show.html.erb @@ -0,0 +1,22 @@ +
    +

    <%= current_user.full_name %>

    +

    Application Status

    +
    + +
    + <% if @previous_state.present? %> +

    The previous state is <%= link_to @previous_state.status.humanize, user_mentee_application_mentee_application_state_path(user_mentee_application_id: @user_mentee_application.id, status_name: @previous_state) %>

    + <% end %> + +

    Current State: <%= @state.status.humanize %>

    + + <% if @next_state.present? %> +

    The next state is <%= link_to @next_state.status.humanize, user_mentee_application_mentee_application_state_path(user_mentee_application_id: @user_mentee_application.id, status_name: @next_state) %>

    + <% else %> +

    The next state is <%= @state.future_state.to_s.humanize %>

    + <% end %> +
    + +
    + <%= render @state.status %> +
    diff --git a/config/locales/views/user_mentee_applications/mentee_application_states/en.yml b/config/locales/views/user_mentee_applications/mentee_application_states/en.yml index aec152fd..54ac8d11 100644 --- a/config/locales/views/user_mentee_applications/mentee_application_states/en.yml +++ b/config/locales/views/user_mentee_applications/mentee_application_states/en.yml @@ -6,7 +6,7 @@ en:

    - send an email to the applicant with a link to the code challenge


    If you choose to promote, please include why you think the applicant is a good fit for AOL in the note.

    " - coding_challenge_sent: + coding_challenge: status_description: "We sent out the code challenge and are awaiting the applicant's response (the typeform submission)." acceptance_criteria: "In the note, please include the link for the applicant's response (i.e. typeform url) to the code challenge." coding_challenge_received: diff --git a/config/routes.rb b/config/routes.rb index 232a112d..017304e4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -64,7 +64,11 @@ end end - resources :user_mentee_applications, only: %i[index show new create edit update] + resources :user_mentee_applications, only: %i[index show new create edit update] do + scope module: :user_mentee_applications do + resources :mentee_application_states, only: %i[show], param: :status_name + end + end scope controller: :static do get :faq diff --git a/config/tailwind.config.js b/config/tailwind.config.js index 0bbbb06c..abbab4e1 100644 --- a/config/tailwind.config.js +++ b/config/tailwind.config.js @@ -7,6 +7,7 @@ module.exports = { "./app/javascript/**/*.js", "./app/views/**/*.{erb,haml,html,slim}", "./app/components/**/*.{rb,erb,html}", + "./config/locales/**/*.yml" ], theme: { extend: { diff --git a/spec/factories/mentee_application_states.rb b/spec/factories/mentee_application_states.rb index 8fad8c00..a744adfa 100644 --- a/spec/factories/mentee_application_states.rb +++ b/spec/factories/mentee_application_states.rb @@ -30,8 +30,8 @@ trait :application_received do status { :application_received } end - trait :coding_challenge_sent do - status { :coding_challenge_sent } + trait :coding_challenge do + status { :coding_challenge } end trait :coding_challenge_received do status { :coding_challenge_received } diff --git a/spec/factories/user_mentee_applications.rb b/spec/factories/user_mentee_applications.rb index 3bed94ca..ee762c82 100644 --- a/spec/factories/user_mentee_applications.rb +++ b/spec/factories/user_mentee_applications.rb @@ -58,9 +58,9 @@ end end - trait :coding_challenge_sent do + trait :coding_challenge do after(:create) do |user_mentee_application| - create(:mentee_application_state, :coding_challenge_sent, user_mentee_application:) + create(:mentee_application_state, :coding_challenge, user_mentee_application:) end end diff --git a/spec/models/mentee_application_state_spec.rb b/spec/models/mentee_application_state_spec.rb new file mode 100644 index 00000000..121e5cc5 --- /dev/null +++ b/spec/models/mentee_application_state_spec.rb @@ -0,0 +1,80 @@ +# == Schema Information +# +# Table name: mentee_application_states +# +# id :bigint not null, primary key +# note :text +# status :integer default("application_received"), not null +# created_at :datetime not null +# updated_at :datetime not null +# status_changed_id :bigint +# user_mentee_application_id :bigint not null +# +# Indexes +# +# index_mentee_application_states_on_status_changed_id (status_changed_id) +# index_mentee_application_states_on_user_mentee_application_id (user_mentee_application_id) +# +# Foreign Keys +# +# fk_rails_... (status_changed_id => users.id) +# fk_rails_... (user_mentee_application_id => user_mentee_applications.id) +require 'rails_helper' + +RSpec.describe MenteeApplicationState do + let(:user) { create(:user) } + let(:mentee_application) { create(:user_mentee_application) } + let(:mentee_application_state) { create(:mentee_application_state, user_mentee_application: mentee_application) } + + context 'when status is application received' do + describe 'future_state' do + it 'returns the future state as coding challenge sent' do + expect(mentee_application_state.future_state).to eq(:coding_challenge) + end + end + + describe 'next_state' do + it 'returns the next state as nil' do + expect(mentee_application_state.next_state).to be_nil + end + end + + describe 'previous_state' do + it 'returns the current state' do + expect(mentee_application_state.previous_state.status.to_sym).to eq(:application_received) + end + end + end + + context 'when status is coding challenge sent' do + let(:mentee_application_state) do + create(:mentee_application_state, :coding_challenge, user_mentee_application: mentee_application) + end + + describe 'future_state' do + it 'returns the future state as coding_challenge_received' do + expect(mentee_application_state.future_state).to eq(:coding_challenge_received) + end + end + + describe 'next_state' do + it 'returns the next state as coding_challenge_completed' do + expect(mentee_application_state.next_state).to be_nil + end + + context 'when the user is on application_received state' do + let(:previous_mentee_application_state) { mentee_application_state.previous_state } + + it 'returns the next state as coding_challenge' do + expect(previous_mentee_application_state.next_state.status.to_sym).to eq(:coding_challenge) + end + end + end + + describe 'previous_state' do + it 'returns the previous state as application_received' do + expect(mentee_application_state.previous_state.status.to_sym).to eq(:application_received) + end + end + end +end diff --git a/spec/models/user_mentee_application_spec.rb b/spec/models/user_mentee_application_spec.rb index 3a1c074c..2a5fcedb 100644 --- a/spec/models/user_mentee_application_spec.rb +++ b/spec/models/user_mentee_application_spec.rb @@ -109,7 +109,7 @@ end context "when the status is 'in_review" do - let(:status) { :coding_challenge_sent } + let(:status) { :coding_challenge } it 'is available' do expect(mentee_application.action_available?).to be true diff --git a/spec/services/mentee_application_transition_service_spec.rb b/spec/services/mentee_application_transition_service_spec.rb index 22cf93c2..6691cc7c 100644 --- a/spec/services/mentee_application_transition_service_spec.rb +++ b/spec/services/mentee_application_transition_service_spec.rb @@ -6,7 +6,7 @@ let(:reviewer) { create(:user) } let(:user) { create(:user, :applicant) } let(:application_received) { create(:user_mentee_application, :application_received, user:) } - let(:coding_challenge_sent) { create(:user_mentee_application, :coding_challenge_sent, user:) } + let(:coding_challenge) { create(:user_mentee_application, :coding_challenge, user:) } let(:coding_challenge_received) { create(:user_mentee_application, :coding_challenge_received, user:) } let(:coding_challenge_approved) { create(:user_mentee_application, :coding_challenge_approved, user:) } let(:phone_screen_scheduled) { create(:user_mentee_application, :phone_screen_scheduled, user:) } @@ -25,7 +25,7 @@ described_class.call(application: application_received, reviewer:, action:) }.to change { application_received.reload.current_status - }.from('application_received').to('coding_challenge_sent') + }.from('application_received').to('coding_challenge') end it 'enqueues an accepted mailer' do @@ -38,10 +38,10 @@ context 'when the coding challenge has been sent' do it 'promotes the application to the next status' do expect { - described_class.call(application: coding_challenge_sent, reviewer:, action:) + described_class.call(application: coding_challenge, reviewer:, action:) }.to change { - coding_challenge_sent.reload.current_status - }.from('coding_challenge_sent').to('coding_challenge_received') + coding_challenge.reload.current_status + }.from('coding_challenge').to('coding_challenge_received') end end @@ -147,7 +147,7 @@ context 'when the coding challenge has been sent' do it 'raises an invalid transition error' do expect { - described_class.call(application: coding_challenge_sent, reviewer:, action:) + described_class.call(application: coding_challenge, reviewer:, action:) }.to raise_error MenteeApplicationTransitionService::InvalidTransitionError end end @@ -239,15 +239,15 @@ context 'when the coding challenge has been sent' do it 'withdraws the application' do expect { - described_class.call(application: coding_challenge_sent, reviewer:, action:) + described_class.call(application: coding_challenge, reviewer:, action:) }.to change { - coding_challenge_sent.reload.current_status - }.from('coding_challenge_sent').to('withdrawn') + coding_challenge.reload.current_status + }.from('coding_challenge').to('withdrawn') end it 'enqueues a withdrawal mailer' do expect { - described_class.call(application: coding_challenge_sent, reviewer:, action:) + described_class.call(application: coding_challenge, reviewer:, action:) }.to have_enqueued_mail(MenteeApplicationMailer, :notify_applicant_of_withdrawal) end end