8000 feat(api,ui): add labels to categorize workflows (#3278) · ovh/cds@ff2de67 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit ff2de67

Browse files
bnjjjsguiheux
authored andcommitted
feat(api,ui): add labels to categorize workflows (#3278)
1 parent cb2bb9e commit ff2de67

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1532
-42
lines changed

engine/api/api_routes.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ func (api *API) InitRouter() {
115115
// Project
116116
r.Handle("/project", r.GET(api.getProjectsHandler, AllowProvider(true), EnableTracing()), r.POST(api.addProjectHandler))
117117
r.Handle("/project/{permProjectKey}", r.GET(api.getProjectHandler), r.PUT(api.updateProjectHandler), r.DELETE(api.deleteProjectHandler))
118+
r.Handle("/project/{permProjectKey}/labels", r.PUT(api.putProjectLabelsHandler))
118119
r.Handle("/project/{permProjectKey}/group", r.POST(api.addGroupInProjectHandler))
119120
r.Handle("/project/{permProjectKey}/group/import", r.POST(api.importGroupsInProjectHandler, DEPRECATED))
120121
r.Handle("/project/{permProjectKey}/group/{group}", r.PUT(api.updateGroupRoleOnProjectHandler), r.DELETE(api.deleteGroupFromProjectHandler))
@@ -222,6 +223,8 @@ func (api *API) InitRouter() {
222223

223224
r.Handle("/project/{permProjectKey}/workflows", r.POST(api.postWorkflowHandler, EnableTracing()), r.GET(api.getWorkflowsHandler, AllowProvider(true), EnableTracing()))
224225
r.Handle("/project/{key}/workflows/{permWorkflowName}", r.GET(api.getWorkflowHandler, AllowProvider(true), EnableTracing()), r.PUT(api.putWorkflowHandler, EnableTracing()), r.DELETE(api.deleteWorkflowHandler))
226+
r.Handle("/project/{key}/workflows/{permWorkflowName}/label", r.POST(api.postWorkflowLabelHandler))
227+
r.Handle("/project/{key}/workflows/{permWorkflowName}/label/{labelID}", r.DELETE(api.deleteWorkflowLabelHandler))
225228
r.Handle("/project/{key}/workflows/{permWorkflowName}/rollback/{auditID}", r.POST(api.postWorkflowRollbackHandler))
226229
r.Handle("/project/{key}/workflows/{permWorkflowName}/groups", r.POST(api.postWorkflowGroupHandler))
227230
r.Handle("/project/{key}/workflows/{permWorkflowName}/groups/{groupName}", r.PUT(api.putWorkflowGroupHandler), r.DELETE( 10000 api.deleteWorkflowGroupHandler))

engine/api/project.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ func (api *API) getProjectHandler() service.Handler {
193193
withPlatforms := FormBool(r, "withPlatforms")
194194
withFeatures := FormBool(r, "withFeatures")
195195
withIcon := FormBool(r, "withIcon")
196+
withLabels := FormBool(r, "withLabels")
196197

197198
opts := []project.LoadOptionFunc{
198199
project.LoadOptions.WithFavorites,
@@ -242,6 +243,9 @@ func (api *API) getProjectHandler() service.Handler {
242243
if withIcon {
243244
opts = append(opts, project.LoadOptions.WithIcon)
244245
}
246+
if withLabels {
247+
opts = append(opts, project.LoadOptions.WithLabels)
248+
}
245249

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

259+
func (api *API) putProjectLabelsHandler() service.Handler {
260+
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
261+
// Get project name in URL
262+
vars := mux.Vars(r)
263+
key := vars["permProjectKey"]
264+
db := api.mustDB()
265+
266+
var labels []sdk.Label
267+
if err := UnmarshalBody(r, &labels); err != nil {
268+
return sdk.WrapError(err, "putProjectLabelsHandler> Unmarshall error")
269+
}
270+
271+
// Check is project exist
272+
proj, errProj := project.Load(db, api.Cache, key, getUser(ctx), project.LoadOptions.WithLabels)
273+
if errProj != nil {
274+
return sdk.WrapError(errProj, "putProjectLabelsHandler> Cannot load project from db")
275+
}
276+
277+
var labelsToUpdate, labelsToAdd []sdk.Label
278+
for _, lblUpdated := range labels {
279+
var lblFound bool
280+
for _, lbl := range proj.Labels {
281+
if lbl.ID == lblUpdated.ID {
282+
lblFound = true
283+
}
284+
}
285+
lblUpdated.ProjectID = proj.ID
286+
if lblFound {
287+
labelsToUpdate = append(labelsToUpdate, lblUpdated)
288+
} else {
289+
labelsToAdd = append(labelsToAdd, lblUpdated)
290+
}
291+
}
292+
293+
var labelsToDelete []sdk.Label
294+
for _, lbl := range proj.Labels {
295+
var lblFound bool
296+
for _, lblUpdated := range labels {
297+
if lbl.ID == lblUpdated.ID {
298+
lblFound = true
299+
}
300+
}
301+
if !lblFound {
302+
lbl.ProjectID = proj.ID
303+
labelsToDelete = append(labelsToDelete, lbl)
304+
}
305+
}
306+
307+
tx, errTx := db.Begin()
308+
if errTx != nil {
309+
return sdk.WrapError(errTx, "putProjectLabelsHandler> Cannot create transaction")
310+
}
311+
defer tx.Rollback() //nolint
312+
313+
for _, lblToDelete := range labelsToDelete {
314+
if err := project.DeleteLabel(tx, lblToDelete.ID); err != nil {
315+
return sdk.WrapError(err, "putProjectLabelsHandler> cannot delete label %s with id %d", lblToDelete.Name, lblToDelete.ID)
316+
}
317+
}
318+
for _, lblToUpdate := range labelsToUpdate {
319+
if err := project.UpdateLabel(tx, &lblToUpdate); err != nil {
320+
return sdk.WrapError(err, "putProjectLabelsHandler> cannot update label %s with id %d", lblToUpdate.Name, lblToUpdate.ID)
321+
}
322+
}
323+
for _, lblToAdd := range labelsToAdd {
324+
if err := project.InsertLabel(tx, &lblToAdd); err != nil {
325+
return sdk.WrapError(err, "putProjectLabelsHandler> cannot add label %s with id %d", lblToAdd.Name, lblToAdd.ID)
326+
}
327+
}
328+
329+
if err := tx.Commit(); err != nil {
330+
return sdk.WrapError(err, "putProjectLabelsHandler> cannot commit transaction")
331+
}
332+
333+
p, errP := project.Load(db, api.Cache, key, getUser(ctx), project.LoadOptions.WithLabels, project.LoadOptions.WithWorkflowNames)
334+
if errP != nil {
335+
return sdk.WrapError(errP, "putProjectLabelsHandler> Cannot load project updated from db")
336+
}
337+
event.PublishUpdateProject(p, proj, getUser(ctx))
338+
339+
return service.WriteJSON(w, p, http.StatusOK)
340+
}
341+
}
342+
255343
func (api *API) addProjectHandler() service.Handler {
256344
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
257345
//Unmarshal data

engine/api/project/dao.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ var LoadOptions = struct {
271271
WithClearPlatforms LoadOptionFunc
272272
WithFavorites LoadOptionFunc
273273
WithFeatures LoadOptionFunc
274+
WithLabels LoadOptionFunc
274275
}{
275276
Default: &loadDefault,
276277
WithIcon: &loadIcon,
@@ -296,6 +297,7 @@ var LoadOptions = struct {
296297
WithFavorites: &loadFavorites,
297298
WithFeatures: &loadFeatures,
298299
WithApplicationWithDeploymentStrategies: &loadApplicationWithDeploymentStrategies,
300+
WithLabels: &loadLabels,
299301
}
300302

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

411+
// Labels return list of labels given a project ID
412+
func Labels(db gorp.SqlExecutor, projectID int64) ([]sdk.Label, error) {
413+
var labels []sdk.Label
414+
query := `
415+
SELECT project_label.*
416+
FROM project_label
417+
WHERE project_label.project_id = $1
418+
ORDER BY project_label.name
419+
`
420+
if _, err := db.Select(&labels, query, projectID); err != nil {
421+
if err == sql.ErrNoRows {
422+
return labels, nil
423+
}
424+
return labels, sdk.WrapError(err, "Labels> Cannot load labels")
425+
}
426+
427+
return labels, nil
428+
}
429+
430+
// LabelByName return a label given his name and project id
431+
func LabelByName(db gorp.SqlExecutor, projectID int64, labelName string) (sdk.Label, error) {
432+
var label sdk.Label
433+
err := db.SelectOne(&label, "SELECT project_label.* FROM project_label WHERE project_id = $1 AND name = $2", projectID, labelName)
434+
435+
return label, err
436+
}
437+
438+
// DeleteLabel delete a label given a label ID
439+
func DeleteLabel(db gorp.SqlExecutor, labelID int64) error {
440+
query := "DELETE FROM project_label WHERE id = $1"
441+
if _, err := db.Exec(query, labelID); err != nil {
442+
if err == sql.ErrNoRows {
443+
return nil
444+
}
445+
return sdk.WrapError(err, "DeleteLabel> Cannot delete labels")
446+
}
447+ 10000
448+
return nil
449+
}
450+
451+
// InsertLabel insert a label
452+
func InsertLabel(db gorp.SqlExecutor, label *sdk.Label) error {
453+
if err := label.Validate(); err != nil {
454+
return sdk.WrapError(err, "InsertLabel>")
455+
}
456+
457+
lbl := dbLabel(*label)
458+
if err := db.Insert(&lbl); err != nil {
459+
return sdk.WrapError(err, "InsertLabel> Cannot insert labels")
460+
}
461+
*label = sdk.Label(lbl)
462+
463+
return nil
464+
}
465+
466+
// UpdateLabel update a label
467+
func UpdateLabel(db gorp.SqlExecutor, label *sdk.Label) error {
468+
if err := label.Validate(); err != nil {
469+
return sdk.WrapError(err, "UpdateLabel>")
470+
}
471+
472+
lbl := dbLabel(*label)
473+
if _, err := db.Update(&lbl); err != nil {
474+
return sdk.WrapError(err, "UpdateLabel> Cannot update labels")
475+
}
476+
*label = sdk.Label(lbl)
477+
478+
return nil
479+
}
480+
409481
// UpdateFavorite add or delete project from user favorites
410482
func UpdateFavorite(db gorp.SqlExecutor, projectID int64, u *sdk.User, add bool) error {
411483
var query string

engine/api/project/dao_dependencies.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@ var (
235235
return nil
236236
}
237237

238+
loadLabels = func(db gorp.SqlExecutor, _ cache.Store, proj *sdk.Project, _ *sdk.User) error {
239+
labels, err := Labels(db, proj.ID)
240+
if err != nil {
241+
return sdk.WrapError(err, "project.loadLabels>")
242+
}
243+
proj.Labels = labels
244+
return nil
245+
}
246+
238247
loadFavorites = func(db gorp.SqlExecutor, store cache.Store, proj *sdk.Project, u *sdk.User) error {
239248
count, err := db.SelectInt("SELECT COUNT(1) FROM project_favorite WHERE project_id = $1 AND user_id = $2", proj.ID, u.ID)
240249
if err != nil {

engine/api/project/gorp_model.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ import (
1717
type dbProject sdk.Project
1818
type dbProjectVariableAudit sdk.ProjectVariableAudit
1919
type dbProjectKey sdk.ProjectKey
20+
type dbLabel sdk.Label
2021

2122
func init() {
2223
gorpmapping.Register(gorpmapping.New(dbProject{}, "project", true, "id"))
2324
gorpmapping.Register(gorpmapping.New(dbProjectVariableAudit{}, "project_variable_audit", true, "id"))
2425
gorpmapping.Register(gorpmapping.New(dbProjectKey{}, "project_key", false))
26+
gorpmapping.Register(gorpmapping.New(dbLabel{}, "project_label", true, "id"))
2527
}
2628

2729
// PostGet is a db hook

engine/api/project_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,5 +270,57 @@ func Test_getprojectsHandler_AsProviderWithRequestedUsername(t *testing.T) {
270270
apps, err := sdkclient.ApplicationsList(pkey, cdsclient.FilterByUser(u.Username), cdsclient.WithUsage())
271271
test.NoError(t, err)
272272
assert.Len(t, apps, 1)
273+
}
274+
275+
func Test_putProjectLabelsHandler(t *testing.T) {
276+
api, db, _ := newTestAPI(t, bootstrap.InitiliazeDB)
277+
u, pass := assets.InsertAdminUser(db)
278+
279+
pkey := sdk.RandomString(10)
280+
proj := assets.InsertTestProject(t, api.mustDB(), api.Cache, pkey, pkey, u)
281+
test.NoError(t, group.InsertUserInGroup(api.mustDB(), proj.ProjectGroups[0].Group.ID, u.ID, true))
282+
283+
lbl1 := sdk.Label{
284+
Name: sdk.RandomString(5),
285+
ProjectID: proj.ID,
286+
}
287+
test.NoError(t, project.InsertLabel(db, &lbl1))
288+
lbl2 := sdk.Label{
289+
Name: sdk.RandomString(5),
290+
ProjectID: proj.ID,
291+
}
292+
test.NoError(t, project.InsertLabel(db, &lbl2))
293+
294+
bodyLabels := []sdk.Label{
295+
{ID: lbl1.ID, Name: "this is a test", Color: lbl1.Color},
296+
{Name: "anotherone"},
297+
{Name: "anotheronebis", Color: "#FF0000"},
298+
}
299+
jsonBody, _ := json.Marshal(bodyLabels)
300+
body := bytes.NewBuffer(jsonBody)
301+
vars := map[string]string{
302+
"permProjectKey": proj.Key,
303+
}
304+
uri := api.Router.GetRoute("PUT", api.putProjectLabelsHandler, vars)
305+
req, err := http.NewRequest("PUT", uri, body)
306+
test.NoError(t, err)
307+
assets.AuthentifyRequest(t, req, u, pass)
308+
309+
// Do the request
310+
w := httptest.NewRecorder()
311+
api.Router.Mux.ServeHTTP(w, req)
312+
assert.Equal(t, 200, w.Code)
273313

314+
projReturned := sdk.Project{}
315+
test.NoError(t, json.Unmarshal(w.Body.Bytes(), &projReturned))
316+
assert.Equal(t, proj.Key, projReturned.Key)
317+
assert.NotNil(t, projReturned.Labels)
318+
assert.Equal(t, 3, len(projReturned.Labels))
319+
assert.Equal(t, "anotherone", projReturned.Labels[0].Name)
320+
assert.NotZero(t, projReturned.Labels[0].Color)
321+
assert.Equal(t, "anotheronebis", projReturned.Labels[1].Name)
322+
assert.NotZero(t, projReturned.Labels[1].Color)
323+
assert.Equal(t, "#FF0000", projReturned.Labels[1].Color)
324+
assert.Equal(t, "this is a test", projReturned.Labels[2].Name)
325+
assert.NotZero(t, projReturned.Labels[2].Color)
274326
}

0 commit comments

Comments
 (0)
0