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

Actions Board View Rule #2249

Open
wants to merge 45 commits into
base: master
Choose a base branch
from

Conversation

michaelkleinhenz
Copy link
Collaborator

@michaelkleinhenz michaelkleinhenz commented Aug 20, 2018

This contains the state to metastate mapping action rule and the necessary changes to the existing templates.

A thorough and complete description can be found here: https://docs.google.com/presentation/d/18xKQmkf3UKlGoP7qLqUnrIYiflza674n_paUZXKuepA/edit

Please note that this PR contains 2 new source files (the actual rule and the test) and minor changes to 3 existing files. The rest of the files in the PR are changes to golden files.

@michaelkleinhenz michaelkleinhenz changed the title Actions Board View Rule and Controller Integration Actions Board View Rule Aug 20, 2018
@codecov-io
Copy link

codecov-io commented Aug 20, 2018

Codecov Report

Merging #2249 into master will increase coverage by 1.73%.
The diff coverage is 66.53%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2249      +/-   ##
==========================================
+ Coverage   69.52%   71.25%   +1.73%     
==========================================
  Files         176      177       +1     
  Lines       16670    18079    +1409     
==========================================
+ Hits        11589    12882    +1293     
- Misses       3957     3994      +37     
- Partials     1124     1203      +79
Impacted Files Coverage Δ
actions/rules/action_field_set.go 59.18% <100%> (ø) ⬆️
actions/rules/action_nil.go 100% <100%> (ø) ⬆️
actions/rules/action_state_to_metastate.go 65.84% <65.84%> (ø)
actions/actions.go 61.11% <75%> (-12.23%) ⬇️
controller/user.go 34.04% <0%> (-4.99%) ⬇️
migration/migration.go 61.11% <0%> (-0.46%) ⬇️
controller/space_iterations.go 84.78% <0%> (+0.51%) ⬆️
controller/area.go 92.78% <0%> (+0.52%) ⬆️
controller/iteration.go 81.12% <0%> (+2.79%) ⬆️
... and 8 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a8fab53...e71b346. Read the comment docs.

@@ -72,14 +72,12 @@ func ExecuteActionsByChangeset(ctx context.Context, db application.DB, userID uu
Ctx: ctx,
UserID: &userID,
}, actionConfig, newContext, contextChanges, &actionChanges)
/* commented out for now until this rule is added
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please line comments. Then it is easier to follow what is affected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ok.

)

// ActionStateToMetaState implements the bidirectional mapping between state and column.
type ActionStateToMetaState struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is a little bit strange to have an action defined inside the rules package.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

because the rule is the action. actions/actions would be weird, so does action/impls.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@michaelkleinhenz what do you mean with because the rule is the action?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The only reason there is a rules directory is to structure the files and to seperate the infra from the actual actions that may go away at some point as the actions will be stored somewhere else, like in the database. That rules directory is just to keep the files clean and structures. The rules are not rules but the semantic name is action.

@@ -3,7 +3,7 @@ Package actions system is a key component for process automation in WIT. It prov
way of executing user-configurable, dynamic process steps depending on user
settings, schema settings and events in the WIT.

The idea here is to provide a simple, yet powerful "signal-slot" system that
The idea here is to provide a simple, yet powerful "publish-subscribe" system that
Copy link
Collaborator

Choose a reason for hiding this comment

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

You have almost the same comment without relating to a package or something in actions/actions.go. Please remove the latter one.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@michaelkleinhenz great. But please try to use one commit per comment so it is easy to follow as a reviewer.

// make sure the rule is implementing the interface.
var _ Action = ActionStateToMetaState{}

func (act ActionStateToMetaState) containsUUID(s []uuid.UUID, e uuid.UUID) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of implementing this function here, I encourage you to use this:

func (s Slice) Contains(v uuid.UUID) bool {
. I've implemented that function for you.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I get a []uuid.UUID from WorkItemTypeGroup.type. So I'd need to convert that into a id.Slice with an extra step, requiring more memory. So I'd suggest we update the model first to not use []uuid.UUID at all before this. I'd also say id.Slice should have something like fromSlice() or similar to make this more convenient.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, you can just assign an []uuid.UUID to an id.Slice. See this example: https://play.golang.org/p/aIsXiZJZisU

package main

import (
	"fmt"
)

type MySlice []int

func main() {
	foo := []int{1,2,3}
	var mySlice MySlice = foo
	fmt.Println(mySlice)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. 4764322

}
// removed in slice2
for _, elem := range old {
if !act.contains(new, elem) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This causes another unnecessary l loop. For id.Slice we also have implemented a Diff method that you might want to copy the algorithm from and adapt it slightly: https://github.com/fabric8-services/fabric8-wit/blob/master/id/slice.go#L14 .

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I need added and removed items seperately. So I don't think this improves on the complexity. Also, using id.Slice needs more memory here, for converting the Slices to id.Slice first and for the counter map.

if !ok {
return nil, errs.New("metastate value in value list is not of type string")
}
// this is important: we only add a new entry if there
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment is very hard to follow. Please try to simplify it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

return nil, errs.New("given field on workitemtype " + wit.ID.String() + " is not an enum field: " + fieldName)
}

func (act ActionStateToMetaState) getStateToMetastateMap(workitemTypeID uuid.UUID) (map[string]string, 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 provide a description with this function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done.

return stateToMetastateMap, nil
}

func (act ActionStateToMetaState) getMetastateToStateMap(workitemTypeID uuid.UUID) (map[string]string, 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 provide a description with this function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

return &c1
}

// OnChange executes the action rule.
Copy link
Collaborator

@kwk kwk Aug 21, 2018

Choose a reason for hiding this comment

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

Please have a comment // OnChange implements action.Action.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Member

Choose a reason for hiding this comment

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

No. It is not fixed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I get the impression we're on different branches. This was fixed. I updated the comment to include the implements reference.

for _, board := range boards {
for _, column := range board.Columns {
if columnID == column.ID.String() {
thisColumn = column
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we break after this find or do we have to continue looping?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Breaking out of nested loops is not that easy and according to https://stackoverflow.com/questions/51996175/how-to-break-out-of-nested-loops-in-golang all solutions proposed there would make the code more complicated and hard to understand. Given that we're only dealing with a small number of columns here (I'd say in 99% of the cases, less than 5), I would say let's prevail code readability in favour of having the WIT count from 3 to 5.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay. I'm fine with this.

if err != nil {
return nil, nil, err
return nil, nil, errs.Wrap(err, "error loading space: "+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.

No need to stringify the error here. Please get rid of err.Error(). You've already wrapped this error.

Copy link
Collaborator Author

@michaelkleinhenz michaelkleinhenz Aug 27, 2018

Choose a reason for hiding this comment

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

Fixed in feecf1c

Copy link
Collaborator

Choose a reason for hiding this comment

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

The fix is fine. But maybe it makes sense to specify which space could not be loaded?

Copy link
Collaborator Author

@michaelkleinhenz michaelkleinhenz Aug 28, 2018

Choose a reason for hiding this comment

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

This is contained in the wrapped error. It contains the ID that is not found in the message. I don't see a reason for repeating this.

}
groups, err := act.Db.WorkItemTypeGroups().List(act.Ctx, space.SpaceTemplateID)
if err != nil {
return nil, nil, errs.Wrap(err, "error loading type groups: "+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.

Same here, please get rid of err.Error().

Copy link
Collaborator Author

@michaelkleinhenz michaelkleinhenz Aug 27, 2018

Choose a reason for hiding this comment

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

Fixed in feecf1c

@kwk
Copy link
Collaborator

kwk commented Aug 27, 2018

This contains the state to metastate mapping action rule and the necessary changes to the existing templates.

@michaelkleinhenz can you please explain in more details what this does and what it is good for? This description is only telling what is contained but not what it does.

return false
}

func (act ActionStateToMetaState) removeElement(s []interface{}, e interface{}) []interface{} {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I cannot find a test for this function. As simple as it looks this is really dangerous. First, it can be wrong. Second, I think you're leaking memory here. See this for an explanation:

NOTE If the type of the element is a pointer or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut and Delete have a potential memory leak problem: some elements with values are still referenced by slice a and thus can not be collected.

(source https://github.com/golang/go/wiki/SliceTricks#delete-without-preserving-order)

Copy link
Collaborator Author

@michaelkleinhenz michaelkleinhenz Aug 27, 2018

Choose a reason for hiding this comment

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

Fixed in 35e6034

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've commented on the revision 35e6034. The issue is different now. The functions removeElement and contains should not be member functions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why? Sorry, but again, spilling over functionality that is only used to structure the code to package level does not makes things better but worse.

}

func (act ActionStateToMetaState) loadWorkItemByID(id uuid.UUID) (*workitem.WorkItem, error) {
wi, err := act.Db.WorkItems().LoadByID(act.Ctx, id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need to capture this function in a wrapper. The return value of both are the same.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

? Both what?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The return values of the wrapper and the wrapped function.

Copy link
Collaborator Author

@michaelkleinhenz michaelkleinhenz Aug 28, 2018

Choose a reason for hiding this comment

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

Again, this is not for re-using functionality. This is to make the main method smaller and easier to understand what it does! Why are you trying to force your mind model into my code and internal wiring? Yes, sure, I could force everything into one gigantic method, like it is done in many other places in the WIT. Does this increase robustness? Does it increase readability? I don't think so. I could glue the onStateChange() and onColumnChange() back together and put it into onChange(). Sure, all of this is possible. But does it makes sense?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is to make the main method smaller and easier to understand what it does!

I get your point you didn't get mine. Sure if I read loadWorkItem that is nice but this is an untested function and as you've mentioned in many other parts of the code we call act.Db.WorkItems().LoadByID(act.Ctx, id). That means, people know what this code does and it is not very secret or hard to read. So IMHO the mere fact that we're using it all over the code already outweighs the apparent readability which costs coverage and adds a layer over an already established function.

return metastateToStateMap, nil
}

func (act ActionStateToMetaState) addOrUpdateChange(changes change.Set, attributeName string, oldValue interface{}, newValue interface{}) change.Set {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this a member function when act is not used anywhere inside?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because there is no need to spill over purely internal functionality to package level. Encapsulation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I didn't say you should put it on the package level but how about making it part of the change.Set?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, one could argue about the reason you gave for not putting it as a function on package level. Here's you may not be polluting the package level but you're requiring to create an object, namely ActionStateToMetaState, that is not needed for the execution of the function. I consider this worse because the prerequisites to run the function are just wrong.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think you missed some of the basic ideas here. There is good reason for having Actions encapsulated as structs.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you missed some of the basic ideas here. There is good reason for having Actions encapsulated as structs.

You misunderstood me. I never said to not store actions as structs. All I asked for was to outsource helper functions as testable-building blocks. We've discussed that in today's Bluejeans meeting. As much as I don't like it but I can live with a test that does ActionStateToMetaState{}.addOrUpdateChange().

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok. Fixed in 7dafce4.

return newChanges
}

func (act ActionStateToMetaState) fuseChanges(c1 change.Set, c2 change.Set) *change.Set {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same here: Why is this a member function when act is not used anywhere inside?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same reason as above.

return &c1
}

// OnChange executes the action rule. It implements rules.Action.
Copy link
Collaborator

Choose a reason for hiding this comment

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

"executes the action rule" doesn't sound right and it is not helpful.

Copy link
Member

Choose a reason for hiding this comment

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

The same review comment was made in #2249 (comment) , which should've been fixed.

return newChanges
}

func (act ActionStateToMetaState) fuseChanges(c1 change.Set, c2 change.Set) *change.Set {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function could need an explanation. And it I think it is best implemented as a member of change.Set, no? Then you can write:

var a, b change.Set
c := a.fuse(b)

Also, why does it return a pointer to a change.Set? Below you explicitly dereference: return newContext, *actionChanges, nil.

Copy link
Member

Choose a reason for hiding this comment

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

I agree a.fuse(b) makes more sense to me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, because this implements a very specific functionality that is not generalizable. In this case, existing changes sould be updated rather than appended in order. This is very specific to this particular use case. I don't think this makes sense as part of change.Set. Also, I externalize to local functions for structuring the code and making is more understandable, not necessarily because there is a re-use case or it is generalizable functionality. It just does not make sense to write methods that have more than a few hundred lines. If it has, I segment them down into chunks that have a defined signature interface.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

And it returns *actionChanges because of exactly this reason. This is a structural method. The caller needs *actionChanges.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That is somewhat confusing to me. The consumer of the fuseChanges function is func (act ActionStateToMetaState) OnChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error). Inside of that function you assign the actionChanges like so: actionChanges = act.fuseChanges(*actionChanges, executionChanges) and then you also return the changes like so: return newContext, *actionChanges, nil.

In the return you dereference the actionChanges, so for that part you don't need fuseChanges to return a pointer.

Since you also set a pointer that was a parameter before and also return the same value in OnChange, I doubt that this is thought through. One should be enough, either set (using the parameter) or return a new value but not do both.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I understand the first point. I don't understand the second point. Anyways, I changed the return type fo fuseChanges. 5001aa2.

}
}
// and return to sender.
return resultingWorkItem, changes, nil
Copy link
Member

Choose a reason for hiding this comment

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

We would be returning an empty workitem in resultingWorkitem if there were no changes. Is this expected?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good find. Actually no, because the issue is somewhere else, but connected to this. In the case of the state change, isDirty is always true at l465. So this works, but for the wrong reasons. I'll update the method.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

973fe2d. Checked again, indeed the isDirty was redundant. I think this happened because I copied and refactored the onColumnChange() method there. Again, good find.

}

// onStateChange is executed when the state changes. It eventually updates the metastate and the boardcolumns.
func (act ActionStateToMetaState) onStateChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you please add a comment stating what this function is supposed to return?
Depending on https://github.com/fabric8-services/fabric8-wit/pull/2249/files#diff-d2486097896184ec82948b273169e638R458 , the first value returned is either the new workitem or an empty workitem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See above. It returns the updated WI. Added a comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

}

// onBoardColumnsChange is executed when the columns change. It eventually updates the metastate and the state.
func (act ActionStateToMetaState) onBoardColumnsChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you please update the comment to include what this function is supposed to return?
This function returns either a workitem object with field values or an empty workitem object based on the value of wiDirty flag.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See above. Always returns the updated WI. 973fe2d.

@michaelkleinhenz
Copy link
Collaborator Author

[test]

@michaelkleinhenz
Copy link
Collaborator Author

rsync: command not found on the build.
[test]

Copy link
Member

@jarifibrahim jarifibrahim left a comment

Choose a reason for hiding this comment

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

We spoke about this on the PR call today. Please use workitem.ChangeSet method instead of creating Change.Set{} struct.

}
}
// return to sender
return resultingWorkItem, changes, nil
Copy link
Member

Choose a reason for hiding this comment

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

@michaelkleinhenz We're returning an empty workitem here if wiDirty is false. This isn't expected, right?
Can you please fix it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah. Fixed. 6f4d7fb.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Removed the redundant var in 3cb93a2.

}

// OnChange executes the action rule. It implements rules.Action. Returns the Work Item with eventual changes applied.
func (act ActionStateToMetaState) OnChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) {
Copy link
Member

Choose a reason for hiding this comment

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

I do not like the fact that actionChanges is a pointer. It is passed to onStateChange, onBoardColumnChange by reference. If one of onStateChange/onBoardColumnChange fails, there is a possibility that the original object would be modified even when there was an error.

@michaelkleinhenz Can we get rid of pointers from OnChange, onBoardColumnChange and onStateChange methods? It is possible that original object is not modified when the error occurs in any of the methods but it would be better if we didn't use pointers where they are not necessary. This would help in debugging the failures as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok, that is actually a good reason. I don't think that can happen with the state rule, but in case of an error, the actionChanges could be left in an undefined state. I updated the signature. f83ff16. Note that this also required some changes to the other existing rules.

Copy link
Member

Choose a reason for hiding this comment

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

@michaelkleinhenz You're awesome. Thank you for changing it 🎉

return workitem, nil
}

func (act ActionStateToMetaState) getValueListFromEnumField(wit *workitem.WorkItemType, fieldName string) ([]interface{}, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is wit a pointer? Let's not use pointers at unnecessary places.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. 5cd9aef.

}

// onBoardColumnsChange is executed when the columns change. It eventually updates the metastate and the state. Returns the Work Item with eventual changes applied.
func (act ActionStateToMetaState) onBoardColumnsChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we please not use a actionChanges as a pointer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done. See #2249 (comment)

}

// onStateChange is executed when the state changes. It eventually updates the metastate and the boardcolumns. Returns the Work Item with eventual changes applied.
func (act ActionStateToMetaState) onStateChange(newContext change.Detector, contextChanges change.Set, configuration string, actionChanges *change.Set) (change.Detector, change.Set, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we please not use a actionChanges as a pointer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done. See #2249 (comment).

}
boards, err := act.Db.Boards().List(act.Ctx, space.SpaceTemplateID)
if err != nil {
return nil, errs.Wrap(err, "error loading work item type: "+err.Error())
Copy link
Member

Choose a reason for hiding this comment

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

@michaelkleinhenz You can get rid of err.Error(). We're already returning it via errs.Wrap(err, ...) .

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

if act.UserID == nil {
return nil, nil, errs.New("userID is nil")
}
if actionChanges == nil {
Copy link
Member

Choose a reason for hiding this comment

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

@kwk Is this the correct way to check for empty actionChanges? Earlier it was pointer and this check made sense but now it represents []changes and each change is

type Set []Change

type Change struct {
	AttributeName string
	NewValue      interface{}
	OldValue      interface{}
}

Should this line be replaced with len(actionChanges) > 0 ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The whole check does not make sense for the non-pointer scenario. This was in there to check that there is actually a usable actionChanges given. As we now always have an instance, this check is redundant. The actionChanges is allowed to be empty here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Member

@jarifibrahim jarifibrahim left a comment

Choose a reason for hiding this comment

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

Thank you for addressing all my concerns. Please wait for @kwk to also approve the PR.

@michaelkleinhenz
Copy link
Collaborator Author

[test]

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants