Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds an upsertAssignment GraphQL mutation #75

Merged
merged 2 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions app/graphql/mutations/upsert_assignment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module Mutations
class UpsertAssignment < BaseMutation
description "Create or update an assignment."

# arguments passed to the `resolve` method
argument :id, ID, required: false, description: "The ID of the assignment to update."
argument :project_id, ID, required: true, description: "The ID of the project this assignment is being created for."
argument :user_id, ID, required: true, description: "The ID of the user being assigned to the project."
argument :status, String, required: true, description: "The status of the assignment."
argument :starts_on, GraphQL::Types::ISO8601Date, required: false, description: "The date this assignment starts."
argument :ends_on, GraphQL::Types::ISO8601Date, required: false, description: "The date this assignment ends."

# return type from the mutation
type Types::StaffPlan::AssignmentType

def resolve(id: nil, project_id:, user_id:, status:, starts_on: nil, ends_on: nil)
current_company = context[:current_company]

# try and find the assignment
assignment = if id.present?
current_company.assignments.find(id)
end

if assignment
assignment.assign_attributes(project_id:, user_id:, status:)
else
project = current_company.projects.find(project_id)
assignment = project.assignments.new(user_id:, status:)
end

assignment.assign_attributes(starts_on: starts_on) if starts_on
assignment.assign_attributes(ends_on: ends_on) if ends_on

if assignment.valid?
assignment.save!
else
assignment.errors.group_by_attribute.each do |attribute, errors|
errors.each do |error|
context.add_error(
GraphQL::ExecutionError.new(
error.full_message,
extensions: {
attribute: attribute.to_s,
}
)
)
end
end
end

assignment
end
end
end
35 changes: 35 additions & 0 deletions app/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,41 @@ type Mutation {
companyId: ID!
): Company!

"""
Create or update an assignment.
"""
upsertAssignment(
"""
The date this assignment ends.
"""
endsOn: ISO8601Date

"""
The ID of the assignment to update.
"""
id: ID

"""
The ID of the project this assignment is being created for.
"""
projectId: ID!

"""
The date this assignment starts.
"""
startsOn: ISO8601Date

"""
The status of the assignment.
"""
status: String!

"""
The ID of the user being assigned to the project.
"""
userId: ID!
): Assignment!

"""
Create or update a work week record for a StaffPlan user.
"""
Expand Down
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module Types
class MutationType < Types::BaseObject
field :set_current_company, mutation: Mutations::SetCurrentCompany
field :upsert_work_week, mutation: Mutations::UpsertWorkWeek
field :upsert_assignment, mutation: Mutations::UpsertAssignment
end
end
8 changes: 8 additions & 0 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@ class Assignment < ApplicationRecord
validates :project_id, presence: true, uniqueness: { scope: :user_id }
validates :status, presence: true, inclusion: { in: VALID_STATUSES }
validate :starts_and_ends_on_rules
validate :project_and_user_belong_to_same_company

scope :for_user, ->(user) { where(user: user) }

private

def project_and_user_belong_to_same_company
project_company_users = project.company.active_users
return if project_company_users.include?(user)

errors.add(:project, "and user must belong to the same company")
end

def starts_and_ends_on_rules
if starts_on.blank? && ends_on.present?
errors.add(:starts_on, "is required if an end date is set")
Expand Down
4 changes: 4 additions & 0 deletions app/models/company.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ class Company < ApplicationRecord

validates :name, presence: true, uniqueness: { case_sensitive: false }

def active_users
users.joins(:memberships).where(memberships: { status: Membership::ACTIVE })
end

def subscription
@_subscription if defined?(@_subscription)

Expand Down
177 changes: 177 additions & 0 deletions spec/graphql/mutations/upsert_assignment_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Mutations::UpsertAssignment do

context "resolve" do
it "creates a new assignment with valid params" do
query_string = <<-GRAPHQL
mutation($projectId: ID!, $userId: ID!, $status: String!) {
upsertAssignment(projectId: $projectId, userId: $userId, status: $status) {
id
project {
id
}
user {
id
}
status
startsOn
endsOn
}
}
GRAPHQL

user = create(:user)
project = create(:project, company: user.current_company)

result = StaffplanReduxSchema.execute(
query_string,
context: {
current_user: user,
current_company: user.current_company
},
variables: {
projectId: project.id,
userId: user.id,
status: Assignment::PROPOSED
}
)

post_result = result["data"]["upsertAssignment"]
expect(result["errors"]).to be_nil
expect(post_result["project"]["id"]).to eq(project.id.to_s)
expect(post_result["user"]["id"]).to eq(user.id.to_s)
expect(post_result["status"]).to eq(Assignment::PROPOSED)
expect(post_result["startsOn"]).to be_nil
expect(post_result["endsOn"]).to be_nil
end

it "updates the assignment with valid params" do
query_string = <<-GRAPHQL
mutation($id: ID, $projectId: ID!, $userId: ID!, $status: String!, $startsOn: ISO8601Date, $endsOn: ISO8601Date) {
upsertAssignment(id: $id, projectId: $projectId, userId: $userId, status: $status, startsOn: $startsOn, endsOn: $endsOn) {
id
project {
id
}
user {
id
}
status
startsOn
endsOn
}
}
GRAPHQL

user = create(:user)
assignment = assignment_for_user(user:, status: Assignment::PROPOSED)

result = StaffplanReduxSchema.execute(
query_string,
context: {
current_user: user,
current_company: user.current_company
},
variables: {
id: assignment.id,
projectId: assignment.project_id,
userId: assignment.user_id,
status: Assignment::ACTIVE,
startsOn: starts_on = 2.weeks.from_now.to_date.iso8601,
endsOn: ends_on = 10.weeks.from_now.to_date.iso8601
}
)

post_result = result["data"]["upsertAssignment"]
expect(result["errors"]).to be_nil
expect(post_result["status"]).to eq(Assignment::ACTIVE)
expect(post_result["startsOn"]).to eq(starts_on.to_s)
expect(post_result["endsOn"]).to eq(ends_on.to_s)
end

it "renders validation errors" do
query_string = <<-GRAPHQL
mutation($id: ID, $projectId: ID!, $userId: ID!, $status: String!) {
upsertAssignment(id: $id, projectId: $projectId, userId: $userId, status: $status) {
id
project {
id
}
user {
id
}
status
startsOn
endsOn
}
}
GRAPHQL

user = create(:user)
assignment = assignment_for_user(user:)

result = StaffplanReduxSchema.execute(
query_string,
context: {
current_user: user,
current_company: user.current_company
},
variables: {
id: assignment.id,
projectId: create(:project).id,
userId: assignment.user_id,
status: "invalid-status",
}
)

post_result = result["errors"]
expect(post_result.length).to eq(2)
expect(post_result.first["message"]).to eq("Status is not included in the list")
expect(post_result.first["extensions"]["attribute"]).to eq("status")
expect(post_result.second["message"]).to eq("Project and user must belong to the same company")
expect(post_result.second["extensions"]["attribute"]).to eq("project")
end

it "raises a 404 if given an assignment id that doesn't exist on the company" do
query_string = <<-GRAPHQL
mutation($id: ID, $projectId: ID!, $userId: ID!, $status: String!) {
upsertAssignment(id: $id, projectId: $projectId, userId: $userId, status: $status) {
id
project {
id
}
user {
id
}
status
startsOn
endsOn
}
}
GRAPHQL

user = create(:user)
assignment = assignment_for_user(user:)

result = StaffplanReduxSchema.execute(
query_string,
context: {
current_user: user,
current_company: user.current_company
},
variables: {
id: assignment_for_user(user: create(:user)).id,
projectId: assignment.project_id,
userId: assignment.user_id,
status: Assignment::ACTIVE,
}
)

post_result = result["errors"]
expect(post_result.first["message"]).to eq("Assignment not found")
end
end
end
8 changes: 0 additions & 8 deletions spec/graphql/mutations/upsert_work_week_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@

RSpec.describe Mutations::UpsertWorkWeek do

# when the :work_weeks factory is used the user's current_company
# is not the same as the one the project/assignment belongs to.
def assignment_for_user(user:)
client = create(:client, company: user.current_company)
project = create(:project, client:)
create(:assignment, user:, project:)
end

context "when updating a work week" do
it "updates the work week with valid params" do
query_string = <<-GRAPHQL
Expand Down
12 changes: 12 additions & 0 deletions spec/models/assignment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,16 @@
expect(assignment.errors[:ends_on]).to include("is required if a start date is set")
end
end

describe "#project_and_user_belong_to_same_company" do
it "validates that the user and project belong to the same company" do
user = create(:user)
project = create(:project)
assignment = build(:assignment, user: user, project: project)
expect(project.company.active_users).to_not include(user)

expect(assignment).to_not be_valid
expect(assignment.errors[:project]).to include("user must belong to the same company as the project")
end
end
end
7 changes: 7 additions & 0 deletions spec/support/helper_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# when the :work_weeks factory is used the user's current_company
# is not the same as the one the project/assignment belongs to.
def assignment_for_user(user:, status: Assignment::ACTIVE)
client = create(:client, company: user.current_company)
project = create(:project, client:)
create(:assignment, user:, project:, status:)
end
Loading