Skip to content

Commit

Permalink
Merge pull request #255 from goinvo/fermion/allow-unassigned-assignments
Browse files Browse the repository at this point in the history
Allow unassigned assignments
  • Loading branch information
fermion authored Aug 2, 2024
2 parents acc36cb + b8b9fd0 commit 94700f5
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 26 deletions.
23 changes: 15 additions & 8 deletions app/graphql/mutations/upsert_assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ 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 :estimated_weekly_hours, Integer, required: false, description: "The estimated weekly hours for this 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."
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: false,
description: "The ID of the user being assigned to the project. If omitted, the assignment status cannot be 'active'."
argument :status, String, required: true,
description: "The status of the assignment."
argument :estimated_weekly_hours, Integer, required: false,
description: "The estimated weekly hours for this 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:, estimated_weekly_hours: nil, starts_on: nil, ends_on: nil)
def resolve(id: nil, project_id:, user_id: nil, status:, estimated_weekly_hours: nil, starts_on: nil, ends_on: nil)
current_company = context[:current_company]

# try and find the assignment
Expand Down
6 changes: 3 additions & 3 deletions app/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ type Assignment {
"""
The user assigned to this assignment
"""
assignedUser: User!
assignedUser: User
createdAt: ISO8601DateTime!

"""
Expand Down Expand Up @@ -147,9 +147,9 @@ type Mutation {
status: String!

"""
The ID of the user being assigned to the project.
The ID of the user being assigned to the project. If omitted, the assignment status cannot be 'active'.
"""
userId: ID!
userId: ID
): Assignment!

"""
Expand Down
2 changes: 1 addition & 1 deletion app/graphql/types/staff_plan/assignment_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module StaffPlan
class AssignmentType < Types::BaseObject
field :id, ID, null: false

field :assigned_user, Types::StaffPlan::UserType, null: false, description: 'The user assigned to this assignment'
field :assigned_user, Types::StaffPlan::UserType, null: true, description: 'The user assigned to this assignment'
def assigned_user
object.user
end
Expand Down
6 changes: 3 additions & 3 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Assignment < ApplicationRecord
belongs_to :user
belongs_to :user, optional: true
belongs_to :project
has_one :company, through: :user, source: :current_company
has_many :work_weeks, dependent: :destroy
Expand All @@ -12,8 +12,8 @@ class Assignment < ApplicationRecord
COMPLETED = "completed".freeze
VALID_STATUSES = [PROPOSED, ACTIVE, ARCHIVED, COMPLETED].freeze

validates :user_id, presence: true, uniqueness: { scope: :project_id }
validates :project_id, presence: true, uniqueness: { scope: :user_id }
validates :user_id, presence: true, uniqueness: { scope: :project_id }, if: ->(assignment) { assignment.status != PROPOSED }
validates :project_id, presence: true, uniqueness: { scope: :user_id, allow_nil: true }, if: ->(assignment) { assignment.user_id.present? }
validates :status, presence: true, inclusion: { in: VALID_STATUSES }
validate :starts_and_ends_on_rules
validate :project_and_user_belong_to_same_company
Expand Down
2 changes: 1 addition & 1 deletion config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ development:
<<: *default
database: staffplan_redux_development
host: <%= ENV["DB_HOST"] %>
password: <%= ENV["DB_PASSWORD"] %>
password: <%= ENV["POSTGRES_PASSWORD"] %>
username: <%= ENV["DB_USERNAME"] %>

# The specified database role being used to connect to PostgreSQL.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class MakeAssignmentsUserIdNullable < ActiveRecord::Migration[7.1]
def change
change_column_null :assignments, :user_id, true
end
end
4 changes: 2 additions & 2 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion spec/factories.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@
if assignment.project.blank?
assignment.project = create(:project, client: create(:client, company: assignment.user.current_company))
end
end

foo = 'bar'
trait :unassigned do
user { nil }
end
end

Expand Down
50 changes: 46 additions & 4 deletions spec/graphql/mutations/upsert_assignment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,51 @@
RSpec.describe Mutations::UpsertAssignment do

context "resolve" do
it "creates a new assignment proposed unassigned assignment" do
query_string = <<-GRAPHQL
mutation($projectId: ID!, $userId: ID, $status: String!) {
upsertAssignment(projectId: $projectId, userId: $userId, status: $status) {
id
project {
id
}
assignedUser {
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,
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["assignedUser"]).to be_nil
expect(post_result["status"]).to eq(Assignment::PROPOSED)
expect(post_result["startsOn"]).to be_nil
expect(post_result["endsOn"]).to be_nil
end

it "creates a new assignment with valid params" do
query_string = <<-GRAPHQL
mutation($projectId: ID!, $userId: ID!, $status: String!) {
mutation($projectId: ID!, $userId: ID, $status: String!) {
upsertAssignment(projectId: $projectId, userId: $userId, status: $status) {
id
project {
Expand Down Expand Up @@ -50,7 +92,7 @@

it "updates the assignment with valid params" do
query_string = <<-GRAPHQL
mutation($id: ID, $projectId: ID!, $userId: ID!, $status: String!, $estimatedWeeklyHours: Int, $startsOn: ISO8601Date, $endsOn: ISO8601Date) {
mutation($id: ID, $projectId: ID!, $userId: ID, $status: String!, $estimatedWeeklyHours: Int, $startsOn: ISO8601Date, $endsOn: ISO8601Date) {
upsertAssignment(id: $id, projectId: $projectId, userId: $userId, status: $status, estimatedWeeklyHours: $estimatedWeeklyHours, startsOn: $startsOn, endsOn: $endsOn) {
id
project {
Expand Down Expand Up @@ -97,7 +139,7 @@

it "renders validation errors" do
query_string = <<-GRAPHQL
mutation($id: ID, $projectId: ID!, $userId: ID!, $status: String!) {
mutation($id: ID, $projectId: ID!, $userId: ID, $status: String!) {
upsertAssignment(id: $id, projectId: $projectId, userId: $userId, status: $status) {
id
project {
Expand Down Expand Up @@ -137,7 +179,7 @@

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!) {
mutation($id: ID, $projectId: ID!, $userId: ID, $status: String!) {
upsertAssignment(id: $id, projectId: $projectId, userId: $userId, status: $status) {
id
project {
Expand Down
38 changes: 35 additions & 3 deletions spec/models/assignment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,48 @@
context "validations" do
subject { create(:assignment) }

it { should validate_presence_of(:user_id) }
it { should validate_uniqueness_of(:user_id).scoped_to(:project_id) }
it { should validate_presence_of(:project_id) }
it { should validate_uniqueness_of(:project_id).scoped_to(:user_id) }
it { should validate_presence_of(:status) }
it { should validate_inclusion_of(:status).in_array(%w(proposed active archived completed)) }

it "does not validates user / project uniqueness if the status is not 'active'" do
first_assignment = build(:assignment, status: Assignment::PROPOSED)
second_assignment = build(:assignment, project: first_assignment.project, status: Assignment::PROPOSED)
first_assignment.update!(user_id: nil)
second_assignment.update!(user_id: nil)

expect(first_assignment).to be_valid
expect(second_assignment).to be_valid
end

it "requires a user if the status is active" do
assignment = create(:assignment, status: Assignment::ACTIVE)
assignment.assign_attributes(user_id: nil)

expect(assignment).to_not be_valid
expect(assignment.save).to be_falsey
assignment.reload
expect(assignment.user).to_not be_nil
end

it 'disallows saving non-proposed assignments with no assignee' do
assignment = Assignment.new(
project: create(:project),
status: Assignment::ACTIVE,
user: nil
)
expect(assignment).to_not be_valid
expect(assignment.save).to be_falsey
end

it "does not require a user if the status is not 'active'" do
assignment = build(:assignment, status: Assignment::PROPOSED, user: nil)
expect(assignment).to be_valid
end
end

context "associations" do
it { should belong_to(:user) }
it { should belong_to(:project) }
it { should have_many(:work_weeks) }
end
Expand Down

0 comments on commit 94700f5

Please sign in to comment.