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

Added Tags for Organizational Management #369

Merged
merged 5 commits into from
Jun 13, 2020
Merged

Added Tags for Organizational Management #369

merged 5 commits into from
Jun 13, 2020

Conversation

fenriskiba
Copy link
Contributor

Added Tags for Organizational Management

Developed in collaboration with @bradphilips and @nambup.

Description

  • Adding Tags in a many-to-many relationship with flag
  • Added tags query parameter to findFlags
  • Added findTags, createTag, deleteTag, findAllTags API calls to manage Tags
  • Added option to postEvaluationBatch to evaluate for all Flags with provided Tags
  • Added unit and integration tests for Tags
  • Added elements in the UI to search, edit, and view Tags on a Flag

Motivation and Context

Please see Issue #362

How Has This Been Tested?

  • Unit Tests
  • Integration Tests
  • Tested in Local
  • Tested in internal sandbox environment

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codecov-commenter
Copy link

codecov-commenter commented Jun 2, 2020

Codecov Report

Merging #369 into master will increase coverage by 1.88%.
The diff coverage is 93.42%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #369      +/-   ##
==========================================
+ Coverage   81.38%   83.26%   +1.88%     
==========================================
  Files          27       27              
  Lines        1606     1733     +127     
==========================================
+ Hits         1307     1443     +136     
+ Misses        222      210      -12     
- Partials       77       80       +3     
Impacted Files Coverage Δ
pkg/entity/db.go 76.47% <ø> (ø)
pkg/handler/eval_cache_fetcher.go 83.78% <81.81%> (+1.43%) ⬆️
pkg/handler/crud.go 89.03% <91.66%> (+0.65%) ⬆️
pkg/handler/eval.go 86.80% <91.89%> (+9.34%) ⬆️
pkg/entity/fixture.go 100.00% <100.00%> (ø)
pkg/entity/flag.go 95.45% <100.00%> (+1.16%) ⬆️
pkg/handler/eval_cache.go 82.14% <100.00%> (+7.14%) ⬆️
pkg/handler/handler.go 83.33% <100.00%> (+1.33%) ⬆️
pkg/util/util.go 100.00% <100.00%> (ø)

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 4bd88aa...b8dc36f. Read the comment docs.

);
}
return this.flags;
}
},
methods: {
tagFormatter(row, col, val) {
return val.map(tag => tag.value).join(', ');
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe we can use el-tag for showing tags

image

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah we'll work on getting this working 👍

v-model="newTag.value"
ref="saveTagInput"
size="mini"
:trigger-on-focus="false"
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 if we set trigger-on-focus to true, it helps to standardize tags creation and reduce typos

Copy link
Contributor

@bradphilips bradphilips Jun 4, 2020

Choose a reason for hiding this comment

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

@zhouzhuojie this was set to false because focus was triggering a save operation which we didn't want, requiring the saveTagInput method have to handle or ignore values that are empty but would still cancel your operation when tabbed away. This conflicts with entry process we're using with the save on enter and cancel.

},
loadAllTags() {
Axios.get(
`${API_URL}/tags`
Copy link
Collaborator

Choose a reason for hiding this comment

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

I noticed that even when we delete the tag from across flags, it still shows up in the autocomplete.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is actually intended for the Flags delete, because it simply removes the reference to the Tag from the Flag and not the actual Tag itself because it could be referenced by another Flag. Let us know if there's another way you'd like to handle this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@zhouzhuojie, I was talking to @bradphilips and a couple of our other Flagr users, and we wondered if this would be a preferred way to handle this anyway. It would keep us from creating and deleting the same tag over and over if it goes in and out of use.

Brad also mentioned an option to search for any flags with the removed tag and fully delete the tag if there aren't any. I can see an argument for doing that to prevent unused tags from sticking around.

Personally, I like leaving them the way it's currently set up, but I don't think we'd argue if you want to fully delete the tags.

Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks, lgtm


results := []*entity.Flag{}
for _, t := range tags {
s := util.SafeString(t)
Copy link
Collaborator

Choose a reason for hiding this comment

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

t is already a string type, so no need to cast to string here

Copy link
Contributor

Choose a reason for hiding this comment

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

Perfect, we'll remove this 👍

}
}

return FlattenFlags(results)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I can see that you are doing dedup here. Since flag id order doesn't matter, how about we define

tagCache:        map[string]map[uint]*entity.Flag      // i.e. tag => map[flagID]*entity.Flag

And when loop through the tags, we just need to get a union of the maps, and then return the values of that union map as a slice

@@ -39,7 +39,8 @@ func (e *eval) PostEvaluation(params evaluation.PostEvaluationParams) middleware
ErrorMessage("empty body"))
}

evalResult := EvalFlag(*evalContext)
flag := LookupFlag(*evalContext)
evalResult := EvalFlagWithContext(flag, *evalContext)
Copy link
Collaborator

Choose a reason for hiding this comment

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

since the function EvalFlag is still there and flag variable is not used below the line, I think you can keep evalResult := EvalFlag(*evalContext)

type Tag struct {
gorm.Model

Value string `sql:"type:varchar(64);unique_index:idx_tag_value"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thinking about the validation of Value field, I think it's probably safer to start with stricter regex pattern, as the tag may grow with inconsistent naming pattern. e.g. eng_team_abc vs Eng Team EFG. A good starting point is util.IsSafeKey.

In the future, we can decouple the validation of tags with loose restrictions if needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

@zhouzhuojie Makes perfect sense, could util.IsSafeKey be re-used here or should we develop an alternate method with our own requirements?

Copy link
Contributor

@bradphilips bradphilips Jun 8, 2020

Choose a reason for hiding this comment

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

Added validation for this in the last commit. I copied and modified the regex from the IsSafeKey slightly to allow for spaces. Let me know if this is to your liking. Thanks!

@bradphilips
Copy link
Contributor

@zhouzhuojie Made some changes for items 1,4,5 and 6. Let us know what you'd like to do on the others. Thanks!

Copy link
Collaborator

@zhouzhuojie zhouzhuojie left a comment

Choose a reason for hiding this comment

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

Left a few comments, and awesome work! Thanks for the contribution. I will take a look again soon.

},
loadAllTags() {
Axios.get(
`${API_URL}/tags`
Copy link
Collaborator

Choose a reason for hiding this comment

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

thanks, lgtm

operationId: findAllTags
parameters:
- in: query
name: limit
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit, we may also want to add offset to enable pagination


if err := getDB().Find(s).Association("Tags").Delete(t).Error; err != nil {
return tag.NewDeleteTagDefault(500).WithPayload(ErrorMessage("%s", err))
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

You may also need entity.SaveFlagSnapshot() to capture tag deletion in flag history.

},
})

// step 0. it should return 0 tags before creaetion
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you also add some tests for limit and tags like parameter?

return values(results)
}

func values(m map[uint]*entity.Flag) []*entity.Flag {
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 the function names values and merge are too generic inside the handler package, and these two functions are simple enough and I think it's better to just inline into the GetByTags function.

@bradphilips
Copy link
Contributor

Left a few comments, and awesome work! Thanks for the contribution. I will take a look again soon.

Awesome, thank you @zhouzhuojie -- Fixed all the additional comments as well. Let us know if there's anything additional.

@zhouzhuojie zhouzhuojie merged commit 41d2757 into openflagr:master Jun 13, 2020
@bradphilips
Copy link
Contributor

@zhouzhuojie thanks so much for the merge. Do you have an idea of when this will get into a release? We're eager to deploy this in our infrastructure! Thanks!

@bradphilips
Copy link
Contributor

@zhouzhuojie thanks so much for the merge. Do you have an idea of when this will get into a release? We're eager to deploy this in our infrastructure! Thanks!

I see that it was just done. Thank you! :)

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

Successfully merging this pull request may close these issues.

4 participants