Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

API=PATCH:WorkItem.2 (follow jsonapi spec) #482

Merged
merged 23 commits into from Dec 1, 2016

Conversation

pranavgore09
Copy link
Contributor

@pranavgore09 pranavgore09 commented Nov 11, 2016

Side effect fixes #466 (How to use - please see ui#348 )

WorkItem2 :- Patch the work item. (Allow partial updates & do not change WIT)
Follows jsonapi spec to define input payload for PATCH call.


{
   "data": {
      "attributes": {
         "system.state": "new",
         "system.title": "Example story",
         "version": "1"
      },
      "id": "42",
      "relationships": {
         "assignee": {
            "data": {
               "id": "6c5610be-30b2-4880-9fec-81e4f8e4fd76",
               "type": "identities"
            }
         },
         "baseType": {
            "data": {
               "id": "system.userstory",
               "type": "workitemtypes"
            }
         }
      },
      "type": "workitems"
   }
}

Following attributes are mandatory for every update request with correct value.

  1. data.id
  2. data.type
  3. data.attributes.version

This payload is constructed for updating WI.
Above structure will update work item(if exists) with id 42 and set it's assignee to given UUID if it exists in system.

@aslakknutsen
Copy link
Contributor

aslakknutsen commented Nov 11, 2016

I believe fields == attributes

"data": {
  "attributes": {
     "system.creator": "user-ref",
     "system.state": "new",
     "system.title": "Example story",
     "version": 5
  },
  "id": "42",
}

@pranavgore09
Copy link
Contributor Author

@aslakknutsen , That is possible, but then WIT type and Version will be on the same level of title,description etc, state, etc. That differs from model definition. I understand jsonapi spec do not say to follow model definition but...

// WorkItem represents a work item as it is stored in the database
type WorkItem struct {
    gormsupport.Lifecycle
    ID uint64 `gorm:"primary_key"`
    // Id of the type of this work item
    Type string
    // Version for optimistic concurrency control
    Version int
    // the field values
    Fields Fields `sql:"type:jsonb"`
}

@codecov-io
Copy link

codecov-io commented Nov 12, 2016

Current coverage is 67.52% (diff: 84.29%)

Merging #482 into master will increase coverage by 0.79%

@@             master       #482   diff @@
==========================================
  Files            62         63     +1   
  Lines          3286       3403   +117   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           2193       2298   +105   
- Misses          857        862     +5   
- Partials        236        243     +7   

Powered by Codecov. Last update 9bd5c82...519dbaf

@aslakknutsen
Copy link
Contributor

@pranavgore09 Sure, Model != REST.

@pranavgore09
Copy link
Contributor Author

@aslakknutsen @tsmaeder I have addressed above comments except versioning. I have not tried etag or another similar in this commit. So the new payload looks like -

{
   "data": {
      "attributes": {
         "system.state": "new",
         "system.title": "Example story",
         "type": "system.userstory",
         "version": "1"
      },
      "id": "42",
      "relationships": {
         "assignee": {
            "data": {
               "id": "6c5610be-30b2-4880-9fec-81e4f8e4fd76",
               "type": "identities"
            }
         }
      },
      "type": "workitems"
   }
}

As of now. attributes can accept anything. But version and type are mandatory and validated via our code.

Should we rename type to wiType in REST payload ? considering jsonapi spec is already having type as reserved word kind of.

@aslakknutsen
Copy link
Contributor

Type should be a relationship.

On Wed, Nov 16, 2016, 08:48 Pranav Gore notifications@github.com wrote:

@aslakknutsen https://github.com/aslakknutsen @tsmaeder
https://github.com/tsmaeder I have addressed above comments except
versioning. I have not tried etag or another similar in this commit. So
the new payload looks like -

{
"data": {
"attributes": {
"system.state": "new",
"system.title": "Example story",
"type": "system.userstory",
"version": "1"
},
"id": "42",
"relationships": {
"assignee": {
"data": {
"id": "6c5610be-30b2-4880-9fec-81e4f8e4fd76",
"type": "identities"
}
}
},
"type": "workitems"
}
}

As of now. attributes can accept anything. But version and type are
mandatory and validated via our code.

Should we rename type to wiType in REST payload ? considering jsonapi spec
is already having type as reserved word kind of.


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#482 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAIEPvwqLVXl7GvLG2VXGiEFBygAKP9jks5q-sNhgaJpZM4Kvta0
.

@pranavgore09
Copy link
Contributor Author

  • type is taken out of fields and made it as a relationship
{
   "data": {
      "attributes": {
         "system.state": "new",
         "system.title": "Example story",
         "version": "1"
      },
      "id": "42",
      "relationships": {
         "assignee": {
            "data": {
               "id": "6c5610be-30b2-4880-9fec-81e4f8e4fd76",
               "type": "identities"
            }
         },
         "baseType": {
            "data": {
               "id": "system.userstory",
               "type": "workitemtypes"
            }
         }
      },
      "type": "workitems"
   }
}

ToDo:

  • add tests all around patch API

@pranavgore09
Copy link
Contributor Author

pranavgore09 commented Nov 17, 2016

@aslakknutsen @tsmaeder : If I go with etag for versions, it need to have GET call, so I am keeping version as it is for now in Attributes, and this PR will do update of WI only. Else it will become big/clumsy. Once this is in master I will start off GET WI using workitem.2 jsonapi

Currently our path URL is - http://localhost:8080/api/workitems.2/1 in which I am concerned about id field in URL.
jsonapi spec says that ID should be in data along with type.
So, we can remove id in URL, but then item exists or not check will be done in our code and not from goadesign ,any suggestions on this ?
--- Just learnt that goadesign is not performing any check, so I am updating code to use payload.data..id only. So, ID will be present in URL but will not be consumed as of now.

a.Attribute("type", d.String, func() {
a.Enum("workitems")
})
a.Attribute("id", d.String, "ID of the work item which is being updated", func() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just like in https://github.com/almighty/almighty-core/blob/master/design/user_types.go#L25
we do not include the ID in the payload because we do it allow updating the ID, and that it could be retrieved from the REST URL, I am wondering if we should include it here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes @sbose78 , you are right, I have mentioned that here
But ID should be in payload as per specifications of jsonapi.org mentioned here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, Thanks. If the spec needs it, we would need to keep it.
Could we ensure that the workitem doesn't get updated with whatever ID is present in the payload? Or, are we validating that the same ID is present ( as that in the rest url ) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I checked goadesign is not performing any db check for existence of item, we are doing that in repository methods. So no need to validate if they are same, we will use only from payload.
this spec tells us that ID should be present in url as well as payload.
Thanks, I will update same. https://github.com/almighty/almighty-core/pull/482/files#diff-d2f956a730e7c36b29bc0dceedd367bdR173 should be updated.

uuidStr := assigneeData.ID
assigneeUUID, err := uuid.FromString(uuidStr)
if err != nil {
return nil, NewBadParameterError("data.relationships.assignee.data.id should be UUID", uuidStr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first argument of the NewBadParameterError should be the paramater name.
The error message would be constructed internally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, done in dbc7935

}
_, err = identityRepo.Load(ctx, assigneeUUID)
if err != nil {
return nil, NewBadParameterError("data.relationships.assignee.data.id not found", uuidStr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first argument of the NewBadParameterError should be the paramater name.
The error message would be constructed internally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, done in dbc7935

@pranavgore09
Copy link
Contributor Author

@aslakknutsen , should we allow user to update type of WI in update API ?
Use case is change WI from bug to userstory.
If yes, while updating we need to check for fields only, or any other part should get updated ?

In current scenario of PATCH, I have made type as a relationship. And it is required as of now, which I will be changing.
If provided in payload then we will update type else we will use existing type of the WI and check for fields if they are present or not as described by new type.

@pranavgore09
Copy link
Contributor Author

Response for PATCH WI -

{
    "data": {
        "attributes": {
            "system.creator": "d8958274-f741-4558-b117-37cb2ed50d09",
            "system.description": null,
            "system.remote_item_id": null,
            "system.state": "open",
            "system.title": "cool title",
            "version": 8
        },
        "id": "1",
        "relationships": {
            "assignee": {
                "data": {
                    "id": "75743e50-641c-4ca5-a10d-9ae824f752f6",
                    "type": "identities"
                }
            },
            "baseType": {
                "data": {
                    "id": "system.userstory",
                    "type": "workitemtypes"
                }
            }
        },
        "type": "workitems"
    },
    "links": {
        "self": "http://localhost:8080/api/workitems.2/1"
    }
}

@aslakknutsen
Copy link
Contributor

Doesn't have to be part of this, but system.creator is a relationship as well. And technically the relationships should have a link to the target (we don't have a single User service atm, but we do have a Type endpoint)

@aslakknutsen
Copy link
Contributor

@pranavgore09 Have you looked at this part of the spec? http://jsonapi.org/format/#crud-updating-relationships

@pranavgore09
Copy link
Contributor Author

pranavgore09 commented Nov 22, 2016

@aslakknutsen yes and that introduces new endpoint of CRUD API for assignee relation. And while doing that we have version attribute which crucial for update. I have added this comment where I mentioned that not doing etag as part of this set assignee.
We decided to use patch for modifying assignee and earlier it was part of Attributes then moved to relationship. now this PR makes update api according to jsonapi spec which is not needed to set assignee.
Should I revert all code above ?

@pranavgore09
Copy link
Contributor Author

Just now I have pushed the code for removing assignee because it was already done.
But I can revert to include new relationship APIs instead of patch call altogether.

@pranavgore09
Copy link
Contributor Author

pranavgore09 commented Nov 22, 2016

@aslakknutsen IMO, as this API is working and integrated here. I keep this codebase as it is for updating WI.
And will add relationships UPDATE and DELETE(means patch with data=nil).

May be I am missing some point, but how can I manage version while update relationship assignee.
-- I can put version in Meta top level object for PATCH.

baijum
baijum previously requested changes Nov 23, 2016
@@ -38,6 +38,71 @@ var UpdateWorkItemPayload = a.Type("UpdateWorkItemPayload", func() {
a.Required("type", "fields", "version")
})

// UpdateWorkItemJSONAPIPayload defines top level structure from jsonapi specs
// visit : http://jsonapi.org/format/#document-top-level
var UpdateWorkItemJSONAPIPayload = a.Type("UpdateWorkItemJSONAPIPayload", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to export names?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, done in 22a20be

})

// WorkItemDataForUpdate defines how an update payload will look like
var WorkItemDataForUpdate = a.Type("WorkItemDataForUpdate", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to export names?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, done in 22a20be

})

// WorkItemRelationships defines only `assignee` as of now. To be updated
var WorkItemRelationships = a.Type("WorkItemRelationships", func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to export names?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, done in 22a20be

@pranavgore09
Copy link
Contributor Author

@aslakknutsen @baijum

{"data": {"attributes": {"version": "2", "unkown_attribute": "some value"},"id": "2","type": "workitems"}}

For input like this, when unknown attributes are added in PATCH, that unknown attribute will not be added to DB or anywhere else, but version get plus one.

Currently attributes is map[string]interface{} so no control over it, no code verifies attribute names are valid or not.

Any suggestions ?

@tsmaeder tsmaeder changed the title WIP :- Set assignee while patching WokItem (follow jsonapi spec) WIP :- Set assignee while patching WorkItem (follow jsonapi spec) Nov 24, 2016
@tsmaeder
Copy link
Contributor

Mmhh...WokItems! Fixed title ;-)

@pranavgore09
Copy link
Contributor Author

Oops, it took me a while to get the difference :P thanks @tsmaeder

@pranavgore09 pranavgore09 changed the title WIP :- Set assignee while patching WorkItem (follow jsonapi spec) API=PATCH:WorkItem.2 (follow jsonapi spec) Nov 25, 2016
Now the code is not considering ID from URL to perform any action.
But we need to keep ID in URL as per specification by - http://jsonapi.org/format/#crud-updating
"relationship.BaseType" is not manadatory
Do not allow to update Type of WI as of now
Use existing type of WI to verify fields
remove testing functions of creating user
update affected test case
Added "links" in top level of response
Added new method ConvertWorkItemToJSONAPI for WIcontroller
Tests updated to absorb jsonapi response

ToDo:
Remove assignee
pass null ID for assignee relationship to remove existing assignment
Set/Unset Assignee
Invalid UUID for assignee
Invalid ID of WI
Update only title
Update only description
Use require instead of assert where needed.
@pranavgore09
Copy link
Contributor Author

pranavgore09 commented Nov 30, 2016

@tsmaeder hmm yes, okay but UI is already using new List method.
If this work is added to workitem as a patch action then workitem resource will have mixture of non-jsonpai and jsonapi actions.
Hence IMHO I feel we should keep all jsonapi based apis in workitem.2 and we will rename it to workitem once UI completely shifts to new APIs. I understand UI needs to re-work on URLs but I think it should be easily configurable. Also putting jsonapi based changes in workitem.2 makes the resource consistent. Also we will have create/delete actions based on jsonapi spec which will go in workitem.2 only.

Copy link
Collaborator

@kwk kwk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are looking much better and cleaned up now. Please address the other minor issues.

@@ -457,3 +522,10 @@ var RelationWorkItemData = a.Type("RelationWorkItemData", func() {
})
a.Required("type", "id")
})

// WorkItemLinks has `self` as of now according to http://jsonapi.org/format/#fetching-resources
var WorkItemLinks = a.Type("WorkItemLinks", func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name might be misleading because work item links are the once that I implemented in PR #421

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, will WorkItemResourceLinksForJSONAPI sound okay ?
done in 519dbaf#diff-aa2cec7dc60fd15e23552b59d51fdbdcR527

return nil, NewBadParameterError("version", version)
}
} else {
return nil, VersionConflictError{simpleError{"version is mandatory"}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use NewVersionConflictError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return nil, VersionConflictError{simpleError{"version is mandatory"}}
}
if res.Version != version {
return nil, VersionConflictError{simpleError{"version conflict"}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use NewVersionConflictError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


rel := wi.Relationships
// TODO
// if rel.Assignee.Data == nil then remove the relationship for WI
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So removing an assignee isn't possible at the moment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, need to remove this comment in code.
assignee is removed when ID=nil 519dbaf#diff-d778d1bb58fa049a00282e997d150670R74


if err := tx.Save(&newWi).Error; err != nil {
log.Print(err.Error())
return nil, InternalError{simpleError{err.Error()}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use NewInternalError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.Equal(s.T(), updatedWI.Data.Attributes[models.SystemDescription], modifiedDescription)
}

func (s *WorkItem2Suite) TestWI2UpdateMultipleScenarios() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not put each scenario in a distinct test method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each scenario is already covered in a separate method above.
This is just series of actions taken back to back by updating version as needed.

a.Media(workItem2)
})
//ToDo: Error responses need modifications as per https://github.com/almighty/almighty-core/pull/421
a.Response(d.BadRequest, func() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a.Response(d.BadRequest, JSONAPIErrors) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a.Response(d.BadRequest, func() {
a.Media(d.ErrorMedia)
})
a.Response(d.InternalServerError)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a.Response(d.InternalServerError, JSONAPIErrors) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a.Media(d.ErrorMedia)
})
a.Response(d.InternalServerError)
a.Response(d.NotFound)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a.Response(d.NotFound, JSONAPIErrors) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})
a.Response(d.InternalServerError)
a.Response(d.NotFound)
a.Response(d.Unauthorized)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a.Response(d.Unauthorized, JSONAPIErrors) instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@pranavgore09 pranavgore09 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kwk for another detailed review, please update me something is not as expected.
Added comments.
Please check my responses in here

a.Media(workItem2)
})
//ToDo: Error responses need modifications as per https://github.com/almighty/almighty-core/pull/421
a.Response(d.BadRequest, func() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a.Response(d.BadRequest, func() {
a.Media(d.ErrorMedia)
})
a.Response(d.InternalServerError)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a.Media(d.ErrorMedia)
})
a.Response(d.InternalServerError)
a.Response(d.NotFound)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

})
a.Response(d.InternalServerError)
a.Response(d.NotFound)
a.Response(d.Unauthorized)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -457,3 +522,10 @@ var RelationWorkItemData = a.Type("RelationWorkItemData", func() {
})
a.Required("type", "id")
})

// WorkItemLinks has `self` as of now according to http://jsonapi.org/format/#fetching-resources
var WorkItemLinks = a.Type("WorkItemLinks", func() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, will WorkItemResourceLinksForJSONAPI sound okay ?
done in 519dbaf#diff-aa2cec7dc60fd15e23552b59d51fdbdcR527

BaseType: &app.RelationshipBaseType{
Data: &app.BaseTypeData{
ID: wi.Type,
Type: "workitemtypes",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this constant and now using it in 519dbaf#diff-d2f956a730e7c36b29bc0dceedd367bdR192

op.Data.Relationships.Assignee = &app.RelationAssignee{
Data: &app.AssigneeData{
ID: &valStr,
Type: "identities",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
wi, err := appl.WorkItems2().Save(ctx, toSave)
if err != nil {
switch err := err.(type) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big help, thanks. will consume that.
Done that in 519dbaf#diff-d2f956a730e7c36b29bc0dceedd367bdR232

assert.Equal(s.T(), updatedWI.Data.Attributes[models.SystemDescription], modifiedDescription)
}

func (s *WorkItem2Suite) TestWI2UpdateMultipleScenarios() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each scenario is already covered in a separate method above.
This is just series of actions taken back to back by updating version as needed.

return nil, NewBadParameterError("data.relationships.assignee.data.id", uuidStr)
}
wi.Attributes[SystemAssignee] = *uuidStr
// ToDO : make it a list and append
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I understand that,
But this will extend the scope of task to much extent, because it needs change in type of assignee in WI, then change in input, change in assignment logic, and while reading the WorkItem. Plus adding tests for the same. And then review on PR.
So most likely I will end up not doing it complete 100% (merge in master) by tomorrow.
And it is not blocker so....can that be done out of this PR ?
any suggestion @aslakknutsen ?

Using constant got "workitemtypes"
Using NewInternalError instead of InternalError
Using NewVersionConflictError
@@ -524,7 +524,7 @@ var RelationWorkItemData = a.Type("RelationWorkItemData", func() {
})

// WorkItemLinks has `self` as of now according to http://jsonapi.org/format/#fetching-resources
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment doesn't reflect the new name for the type.

@aslakknutsen aslakknutsen dismissed baijum’s stale review December 1, 2016 12:12

Concerns addressed

@aslakknutsen aslakknutsen merged commit 319ff70 into fabric8-services:master Dec 1, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Modify WorkItem Update api to set assignee
7 participants