Skip to content

Commit

Permalink
feat(api,ui): add labels to categorize workflows (#3278)
Browse files Browse the repository at this point in the history
  • Loading branch information
bnjjj authored and sguiheux committed Aug 31, 2018
1 parent cb2bb9e commit ff2de67
Show file tree
Hide file tree
Showing 44 changed files with 1,532 additions and 42 deletions.
3 changes: 3 additions & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func (api *API) InitRouter() {
// Project
r.Handle("/project", r.GET(api.getProjectsHandler, AllowProvider(true), EnableTracing()), r.POST(api.addProjectHandler))
r.Handle("/project/{permProjectKey}", r.GET(api.getProjectHandler), r.PUT(api.updateProjectHandler), r.DELETE(api.deleteProjectHandler))
r.Handle("/project/{permProjectKey}/labels", r.PUT(api.putProjectLabelsHandler))
r.Handle("/project/{permProjectKey}/group", r.POST(api.addGroupInProjectHandler))
r.Handle("/project/{permProjectKey}/group/import", r.POST(api.importGroupsInProjectHandler, DEPRECATED))
r.Handle("/project/{permProjectKey}/group/{group}", r.PUT(api.updateGroupRoleOnProjectHandler), r.DELETE(api.deleteGroupFromProjectHandler))
Expand Down Expand Up @@ -222,6 +223,8 @@ func (api *API) InitRouter() {

r.Handle("/project/{permProjectKey}/workflows", r.POST(api.postWorkflowHandler, EnableTracing()), r.GET(api.getWorkflowsHandler, AllowProvider(true), EnableTracing()))
r.Handle("/project/{key}/workflows/{permWorkflowName}", r.GET(api.getWorkflowHandler, AllowProvider(true), EnableTracing()), r.PUT(api.putWorkflowHandler, EnableTracing()), r.DELETE(api.deleteWorkflowHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/label", r.POST(api.postWorkflowLabelHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/label/{labelID}", r.DELETE(api.deleteWorkflowLabelHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/rollback/{auditID}", r.POST(api.postWorkflowRollbackHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/groups", r.POST(api.postWorkflowGroupHandler))
r.Handle("/project/{key}/workflows/{permWorkflowName}/groups/{groupName}", r.PUT(api.putWorkflowGroupHandler), r.DELETE(api.deleteWorkflowGroupHandler))
Expand Down
88 changes: 88 additions & 0 deletions engine/api/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ func (api *API) getProjectHandler() service.Handler {
withPlatforms := FormBool(r, "withPlatforms")
withFeatures := FormBool(r, "withFeatures")
withIcon := FormBool(r, "withIcon")
withLabels := FormBool(r, "withLabels")

opts := []project.LoadOptionFunc{
project.LoadOptions.WithFavorites,
Expand Down Expand Up @@ -242,6 +243,9 @@ func (api *API) getProjectHandler() service.Handler {
if withIcon {
opts = append(opts, project.LoadOptions.WithIcon)
}
if withLabels {
opts = append(opts, project.LoadOptions.WithLabels)
}

p, errProj := project.Load(api.mustDB(), api.Cache, key, getUser(ctx), opts...)
if errProj != nil {
Expand All @@ -252,6 +256,90 @@ func (api *API) getProjectHandler() service.Handler {
}
}

func (api *API) putProjectLabelsHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// Get project name in URL
vars := mux.Vars(r)
key := vars["permProjectKey"]
db := api.mustDB()

var labels []sdk.Label
if err := UnmarshalBody(r, &labels); err != nil {
return sdk.WrapError(err, "putProjectLabelsHandler> Unmarshall error")
}

// Check is project exist
proj, errProj := project.Load(db, api.Cache, key, getUser(ctx), project.LoadOptions.WithLabels)
if errProj != nil {
return sdk.WrapError(errProj, "putProjectLabelsHandler> Cannot load project from db")
}

var labelsToUpdate, labelsToAdd []sdk.Label
for _, lblUpdated := range labels {
var lblFound bool
for _, lbl := range proj.Labels {
if lbl.ID == lblUpdated.ID {
lblFound = true
}
}
lblUpdated.ProjectID = proj.ID
if lblFound {
labelsToUpdate = append(labelsToUpdate, lblUpdated)
} else {
labelsToAdd = append(labelsToAdd, lblUpdated)
}
}

var labelsToDelete []sdk.Label
for _, lbl := range proj.Labels {
var lblFound bool
for _, lblUpdated := range labels {
if lbl.ID == lblUpdated.ID {
lblFound = true
}
}
if !lblFound {
lbl.ProjectID = proj.ID
labelsToDelete = append(labelsToDelete, lbl)
}
}

tx, errTx := db.Begin()
if errTx != nil {
return sdk.WrapError(errTx, "putProjectLabelsHandler> Cannot create transaction")
}
defer tx.Rollback() //nolint

for _, lblToDelete := range labelsToDelete {
if err := project.DeleteLabel(tx, lblToDelete.ID); err != nil {
return sdk.WrapError(err, "putProjectLabelsHandler> cannot delete label %s with id %d", lblToDelete.Name, lblToDelete.ID)
}
}
for _, lblToUpdate := range labelsToUpdate {
if err := project.UpdateLabel(tx, &lblToUpdate); err != nil {
return sdk.WrapError(err, "putProjectLabelsHandler> cannot update label %s with id %d", lblToUpdate.Name, lblToUpdate.ID)
}
}
for _, lblToAdd := range labelsToAdd {
if err := project.InsertLabel(tx, &lblToAdd); err != nil {
return sdk.WrapError(err, "putProjectLabelsHandler> cannot add label %s with id %d", lblToAdd.Name, lblToAdd.ID)
}
}

if err := tx.Commit(); err != nil {
return sdk.WrapError(err, "putProjectLabelsHandler> cannot commit transaction")
}

p, errP := project.Load(db, api.Cache, key, getUser(ctx), project.LoadOptions.WithLabels, project.LoadOptions.WithWorkflowNames)
if errP != nil {
return sdk.WrapError(errP, "putProjectLabelsHandler> Cannot load project updated from db")
}
event.PublishUpdateProject(p, proj, getUser(ctx))

return service.WriteJSON(w, p, http.StatusOK)
}
}

func (api *API) addProjectHandler() service.Handler {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
//Unmarshal data
Expand Down
72 changes: 72 additions & 0 deletions engine/api/project/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ var LoadOptions = struct {
WithClearPlatforms LoadOptionFunc
WithFavorites LoadOptionFunc
WithFeatures LoadOptionFunc
WithLabels LoadOptionFunc
}{
Default: &loadDefault,
WithIcon: &loadIcon,
Expand All @@ -296,6 +297,7 @@ var LoadOptions = struct {
WithFavorites: &loadFavorites,
WithFeatures: &loadFeatures,
WithApplicationWithDeploymentStrategies: &loadApplicationWithDeploymentStrategies,
WithLabels: &loadLabels,
}

// LoadProjectByNodeJobRunID return a project from node job run id
Expand Down Expand Up @@ -406,6 +408,76 @@ func unwrap(db gorp.SqlExecutor, store cache.Store, p *dbProject, u *sdk.User, o
return &proj, nil
}

// Labels return list of labels given a project ID
func Labels(db gorp.SqlExecutor, projectID int64) ([]sdk.Label, error) {
var labels []sdk.Label
query := `
SELECT project_label.*
FROM project_label
WHERE project_label.project_id = $1
ORDER BY project_label.name
`
if _, err := db.Select(&labels, query, projectID); err != nil {
if err == sql.ErrNoRows {
return labels, nil
}
return labels, sdk.WrapError(err, "Labels> Cannot load labels")
}

return labels, nil
}

// LabelByName return a label given his name and project id
func LabelByName(db gorp.SqlExecutor, projectID int64, labelName string) (sdk.Label, error) {
var label sdk.Label
err := db.SelectOne(&label, "SELECT project_label.* FROM project_label WHERE project_id = $1 AND name = $2", projectID, labelName)

return label, err
}

// DeleteLabel delete a label given a label ID
func DeleteLabel(db gorp.SqlExecutor, labelID int64) error {
query := "DELETE FROM project_label WHERE id = $1"
if _, err := db.Exec(query, labelID); err != nil {
if err == sql.ErrNoRows {
return nil
}
return sdk.WrapError(err, "DeleteLabel> Cannot delete labels")
}

return nil
}

// InsertLabel insert a label
func InsertLabel(db gorp.SqlExecutor, label *sdk.Label) error {
if err := label.Validate(); err != nil {
return sdk.WrapError(err, "InsertLabel>")
}

lbl := dbLabel(*label)
if err := db.Insert(&lbl); err != nil {
return sdk.WrapError(err, "InsertLabel> Cannot insert labels")
}
*label = sdk.Label(lbl)

return nil
}

// UpdateLabel update a label
func UpdateLabel(db gorp.SqlExecutor, label *sdk.Label) error {
if err := label.Validate(); err != nil {
return sdk.WrapError(err, "UpdateLabel>")
}

lbl := dbLabel(*label)
if _, err := db.Update(&lbl); err != nil {
return sdk.WrapError(err, "UpdateLabel> Cannot update labels")
}
*label = sdk.Label(lbl)

return nil
}

// UpdateFavorite add or delete project from user favorites
func UpdateFavorite(db gorp.SqlExecutor, projectID int64, u *sdk.User, add bool) error {
var query string
Expand Down
9 changes: 9 additions & 0 deletions engine/api/project/dao_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ var (
return nil
}

loadLabels = func(db gorp.SqlExecutor, _ cache.Store, proj *sdk.Project, _ *sdk.User) error {
labels, err := Labels(db, proj.ID)
if err != nil {
return sdk.WrapError(err, "project.loadLabels>")
}
proj.Labels = labels
return nil
}

loadFavorites = func(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, u *sdk.User) error {
count, err := db.SelectInt("SELECT COUNT(1) FROM project_favorite WHERE project_id = $1 AND user_id = $2", proj.ID, u.ID)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions engine/api/project/gorp_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import (
type dbProject sdk.Project
type dbProjectVariableAudit sdk.ProjectVariableAudit
type dbProjectKey sdk.ProjectKey
type dbLabel sdk.Label

func init() {
gorpmapping.Register(gorpmapping.New(dbProject{}, "project", true, "id"))
gorpmapping.Register(gorpmapping.New(dbProjectVariableAudit{}, "project_variable_audit", true, "id"))
gorpmapping.Register(gorpmapping.New(dbProjectKey{}, "project_key", false))
gorpmapping.Register(gorpmapping.New(dbLabel{}, "project_label", true, "id"))
}

// PostGet is a db hook
Expand Down
52 changes: 52 additions & 0 deletions engine/api/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,57 @@ func Test_getprojectsHandler_AsProviderWithRequestedUsername(t *testing.T) {
apps, err := sdkclient.ApplicationsList(pkey, cdsclient.FilterByUser(u.Username), cdsclient.WithUsage())
test.NoError(t, err)
assert.Len(t, apps, 1)
}

func Test_putProjectLabelsHandler(t *testing.T) {
api, db, _ := newTestAPI(t, bootstrap.InitiliazeDB)
u, pass := assets.InsertAdminUser(db)

pkey := sdk.RandomString(10)
proj := assets.InsertTestProject(t, api.mustDB(), api.Cache, pkey, pkey, u)
test.NoError(t, group.InsertUserInGroup(api.mustDB(), proj.ProjectGroups[0].Group.ID, u.ID, true))

lbl1 := sdk.Label{
Name: sdk.RandomString(5),
ProjectID: proj.ID,
}
test.NoError(t, project.InsertLabel(db, &lbl1))
lbl2 := sdk.Label{
Name: sdk.RandomString(5),
ProjectID: proj.ID,
}
test.NoError(t, project.InsertLabel(db, &lbl2))

bodyLabels := []sdk.Label{
{ID: lbl1.ID, Name: "this is a test", Color: lbl1.Color},
{Name: "anotherone"},
{Name: "anotheronebis", Color: "#FF0000"},
}
jsonBody, _ := json.Marshal(bodyLabels)
body := bytes.NewBuffer(jsonBody)
vars := map[string]string{
"permProjectKey": proj.Key,
}
uri := api.Router.GetRoute("PUT", api.putProjectLabelsHandler, vars)
req, err := http.NewRequest("PUT", uri, body)
test.NoError(t, err)
assets.AuthentifyRequest(t, req, u, pass)

// Do the request
w := httptest.NewRecorder()
api.Router.Mux.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)

projReturned := sdk.Project{}
test.NoError(t, json.Unmarshal(w.Body.Bytes(), &projReturned))
assert.Equal(t, proj.Key, projReturned.Key)
assert.NotNil(t, projReturned.Labels)
assert.Equal(t, 3, len(projReturned.Labels))
assert.Equal(t, "anotherone", projReturned.Labels[0].Name)
assert.NotZero(t, projReturned.Labels[0].Color)
assert.Equal(t, "anotheronebis", projReturned.Labels[1].Name)
assert.NotZero(t, projReturned.Labels[1].Color)
assert.Equal(t, "#FF0000", projReturned.Labels[1].Color)
assert.Equal(t, "this is a test", projReturned.Labels[2].Name)
assert.NotZero(t, projReturned.Labels[2].Color)
}

0 comments on commit ff2de67

Please sign in to comment.