Skip to content

Commit

Permalink
Add breakers to racks (#492)
Browse files Browse the repository at this point in the history
* feat(cli, api) add breakers

* fix(cli) tests

* fix(cli) tests

* refactor(cli) update attributes

* feat(cli) avoid sep/pillar/breaker in wrong obj

* fix(cli) better doc and test

* fix(cli) minor test improvement

* fix(cli) add tests

* fix(cli,api) update tests

* fix(cli) check len value for update interact
  • Loading branch information
helderbetiol authored Jul 12, 2024
1 parent 7d102a1 commit 822627d
Show file tree
Hide file tree
Showing 24 changed files with 1,067 additions and 590 deletions.
5 changes: 4 additions & 1 deletion API/models/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ import (
"strings"
"time"

"github.com/elliotchance/pie/v2"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

var AttrsWithInnerObj = []string{"pillars", "separators", "breakers"}

// Helper functions

func domainHasObjects(domain string) bool {
Expand Down Expand Up @@ -90,7 +93,7 @@ func updateOldObjWithPatch(old map[string]interface{}, patch map[string]interfac
for k, v := range patch {
switch patchValueCasted := v.(type) {
case map[string]interface{}:
if k == "pillars" || k == "separators" {
if pie.Contains(AttrsWithInnerObj, k) {
old[k] = v
} else {
switch oldValueCasted := old[k].(type) {
Expand Down
34 changes: 32 additions & 2 deletions API/models/schemas/rack_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
},
"posXYUnit": {
"type": "string",
"enum": ["m", "t", "f"]
"enum": [
"m",
"t",
"f"
]
},
"posZUnit": {
"type": "string",
Expand All @@ -44,6 +48,32 @@
},
"clearance": {
"$ref": "refs/types.json#/definitions/clearanceVector"
},
"breakers": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": [
"powerpanel"
],
"properties": {
"powerpanel": {
"type": "string"
},
"circuit": {
"type": "string"
},
"type": {
"type": "string"
},
"tag": {
"type": "string"
},
"intensity": {
"type": "number"
}
}
}
}
},
"required": [
Expand All @@ -63,7 +93,7 @@
"height": 47,
"heightUnit": "U",
"rotation": [45, 45, 45],
"posXYZ": [4.6666666666667, -2, 0],
"posXYZ": [4.6666666666667, -2, 0],
"posXYUnit": "m",
"size": [80, 100.532442],
"sizeUnit": "cm",
Expand Down
21 changes: 21 additions & 0 deletions API/models/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,32 @@ func addAndRemoveFromTags(entity int, objectID string, object map[string]interfa

delete(object, "tags-")
}

// check tags for rack breakers
if err := VerifyTagForRackBreaker(object); err != nil {
return err
}
}

return nil
}

func VerifyTagForRackBreaker(object map[string]interface{}) *u.Error {
if breakers, ok := object["attributes"].(map[string]any)["breakers"].(map[string]any); ok {
tagsToCheck := []any{}
for _, breaker := range breakers {
if tag, ok := breaker.(map[string]any)["tag"]; ok {
tagsToCheck = append(tagsToCheck, tag)
}
}
err := verifyTagList(tagsToCheck)
if err != nil {
return err
}
}
return nil
}

// Deletes tag with slug "slug"
func DeleteTag(slug string) *u.Error {
tag, err := repository.GetTagBySlug(slug)
Expand Down
18 changes: 18 additions & 0 deletions API/models/tag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,24 @@ func TestCreateObjectWithTagsAsStringReturnsError(t *testing.T) {
assert.ErrorContains(t, err, "JSON body doesn't validate with the expected JSON schema")
}

func TestVerifyTagForRackBreakerWorks(t *testing.T) {
err := createTag("exists")
require.Nil(t, err)
rack := test_utils.GetEntityMap("rack", "rack-breaker-tags", "", integration.TestDBName)
rack["attributes"].(map[string]any)["breakers"] = map[string]any{"mybreaker": map[string]any{"tag": "exists"}}
err = models.VerifyTagForRackBreaker(rack)
assert.Nil(t, err)
}

func TestVerifyTagForRackBreakerError(t *testing.T) {
rack := test_utils.GetEntityMap("rack", "rack-breaker-tags", "", integration.TestDBName)
rack["attributes"].(map[string]any)["breakers"] = map[string]any{"mybreaker": map[string]any{"tag": "not-exists"}}
err := models.VerifyTagForRackBreaker(rack)
assert.NotNil(t, err)
assert.Equal(t, u.ErrBadFormat, err.Type)
assert.Equal(t, "Tag \"not-exists\" not found", err.Message)
}

func createTag(slug string) *u.Error {
_, err := models.CreateEntity(
u.TAG,
Expand Down
3 changes: 2 additions & 1 deletion API/repository/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ func ConnectToDB(host, port, user, pass, dbName, tenantName string) error {
func SetupDB(db *mongo.Database) error {
// Indexes creation
// Enforce unique children
for _, entity := range []int{u.DOMAIN, u.SITE, u.BLDG, u.ROOM, u.RACK, u.DEVICE, u.AC, u.PWRPNL, u.CABINET, u.CORRIDOR, u.GROUP, u.STRAYOBJ, u.GENERIC} {
for _, entity := range []int{u.DOMAIN, u.SITE, u.BLDG, u.ROOM, u.RACK, u.DEVICE, u.AC,
u.PWRPNL, u.CABINET, u.CORRIDOR, u.GROUP, u.STRAYOBJ, u.GENERIC, u.VIRTUALOBJ} {
if err := createUniqueIndex(db, u.EntityToString(entity), bson.M{"id": 1}); err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions CLI/controllers/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ func (controller Controller) UnsetAttribute(path string, attr string) error {
if !hasAttributes {
return fmt.Errorf("object has no attributes")
}
if vconfigAttr, found := strings.CutPrefix(attr, VIRTUALCONFIG+"."); found {
if vconfigAttr, found := strings.CutPrefix(attr, VirtualConfigAttr+"."); found {
if len(vconfigAttr) < 1 {
return fmt.Errorf("invalid attribute name")
} else if vAttrs, ok := attributes[VIRTUALCONFIG].(map[string]any); !ok {
return fmt.Errorf("object has no " + VIRTUALCONFIG)
} else if vAttrs, ok := attributes[VirtualConfigAttr].(map[string]any); !ok {
return fmt.Errorf("object has no " + VirtualConfigAttr)
} else {
delete(vAttrs, vconfigAttr)
}
Expand Down
68 changes: 68 additions & 0 deletions CLI/controllers/interact.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package controllers

import (
"cli/utils"
"fmt"
"strings"
)
Expand Down Expand Up @@ -101,3 +102,70 @@ func (controller Controller) InteractObject(path string, keyword string, val int
//-1 since its not neccessary to check for filtering
return Ogree3D.InformOptional("Interact", -1, ans)
}

func (controller Controller) UpdateInteract(path, attrName string, values []any, hasSharpe bool) error {
if attrName != "labelFont" && len(values) != 1 {
return fmt.Errorf("only 1 value expected")
}
switch attrName {
case "displayContent", "alpha", "tilesName", "tilesColor", "U", "slots", "localCS":
return controller.SetBooleanInteractAttribute(path, values, attrName, hasSharpe)
case "label":
return controller.SetLabel(path, values, hasSharpe)
case "labelFont":
return controller.SetLabelFont(path, values)
case "labelBackground":
return controller.SetLabelBackground(path, values)
}
return nil
}

func (controller Controller) SetLabel(path string, values []any, hasSharpe bool) error {
value, err := utils.ValToString(values[0], "value")
if err != nil {
return err
}
return controller.InteractObject(path, "label", value, hasSharpe)
}

func (controller Controller) SetLabelFont(path string, values []any) error {
msg := "The font can only be bold or italic" +
" or be in the form of color@[colorValue]." +
"\n\nFor more information please refer to: " +
"\nhttps://github.com/ditrit/OGrEE-3D/wiki/CLI-langage#interact-with-objects"

switch len(values) {
case 1:
if values[0] != "bold" && values[0] != "italic" {
return fmt.Errorf(msg)
}
return controller.InteractObject(path, "labelFont", values[0], false)
case 2:
if values[0] != "color" {
return fmt.Errorf(msg)
}
c, ok := utils.ValToColor(values[1])
if !ok {
return fmt.Errorf("please provide a valid 6 length hex value for the color")
}
return controller.InteractObject(path, "labelFont", "color@"+c, false)
default:
return fmt.Errorf(msg)
}
}

func (controller Controller) SetLabelBackground(path string, values []any) error {
c, ok := utils.ValToColor(values[0])
if !ok {
return fmt.Errorf("please provide a valid 6 length hex value for the color")
}
return controller.InteractObject(path, "labelBackground", c, false)
}

func (controller Controller) SetBooleanInteractAttribute(path string, values []any, attrName string, hasSharpe bool) error {
boolVal, err := utils.ValToBool(values[0], attrName)
if err != nil {
return err
}
return controller.InteractObject(path, attrName, boolVal, hasSharpe)
}
46 changes: 28 additions & 18 deletions CLI/controllers/interact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,44 +71,44 @@ func interactLabelTestSetup(t *testing.T) (cmd.Controller, *mocks.APIPort, *mock
}

func TestLabelNotStringReturnsError(t *testing.T) {
err := cmd.C.InteractObject("/Physical/BASIC/A/R1", "label", 1, false)
err := cmd.C.UpdateInteract("/Physical/BASIC/A/R1", "label", []any{1}, false)
assert.NotNil(t, err)
assert.Errorf(t, err, "The label value must be a string")
}

func TestNonExistingAttrReturnsError(t *testing.T) {
controller, _, _ := interactLabelTestSetup(t)
err := controller.InteractObject("/Physical/BASIC/A/R1/A01", "label", "abc", true)
err := controller.UpdateInteract("/Physical/BASIC/A/R1/A01", "label", []any{"abc"}, true)
assert.NotNil(t, err)
assert.Errorf(t, err, "The specified attribute 'abc' does not exist in the object. \nPlease view the object (ie. $> get) and try again")
}

func TestInteractObject(t *testing.T) {
func TestUpdateInteract(t *testing.T) {
tests := []struct {
name string
path string
keyword string
value interface{}
value []interface{}
fromAttr bool
}{
{"LabelStringOk", "/Physical/BASIC/A/R1/A01", "label", "string", false},
{"LabelSingleAttrOk", "/Physical/BASIC/A/R1/A01", "label", "name", true},
{"LabelStringWithOneAttrOk", "/Physical/BASIC/A/R1/A01", "label", "My name is #name", false},
{"LabelStringWithMultipleAttrOk", "/Physical/BASIC/A/R1/A01", "label", "My name is #name and I am a #category", false},
{"LabelSingleAttrAndStringOk", "/Physical/BASIC/A/R1/A01", "label", "name is my name", true},
{"LabelSingleAttrAndStringWithAttrOk", "/Physical/BASIC/A/R1/A01", "label", "name\n#id", true},
{"FontItalicOk", "/Physical/BASIC/A/R1/A01", "labelFont", "italic", false},
{"LabelFontBoldOk", "/Physical/BASIC/A/R1/A01", "labelFont", "bold", false},
{"LabelColorOk", "/Physical/BASIC/A/R1/A01", "labelFont", "color@C0FFEE", false},
{"LabelBackgroundOk", "/Physical/BASIC/A/R1/A01", "labelBackground", "C0FFEE", false},
{"ContentOk", "/Physical/BASIC/A/R1/A01", "displayContent", true, false},
{"AlphaOk", "/Physical/BASIC/A/R1/A01", "alpha", true, false},
{"LabelStringOk", "/Physical/BASIC/A/R1/A01", "label", []any{"string"}, false},
{"LabelSingleAttrOk", "/Physical/BASIC/A/R1/A01", "label", []any{"name"}, true},
{"LabelStringWithOneAttrOk", "/Physical/BASIC/A/R1/A01", "label", []any{"My name is #name"}, false},
{"LabelStringWithMultipleAttrOk", "/Physical/BASIC/A/R1/A01", "label", []any{"My name is #name and I am a #category"}, false},
{"LabelSingleAttrAndStringOk", "/Physical/BASIC/A/R1/A01", "label", []any{"name is my name"}, true},
{"LabelSingleAttrAndStringWithAttrOk", "/Physical/BASIC/A/R1/A01", "label", []any{"name\n#id"}, true},
{"FontItalicOk", "/Physical/BASIC/A/R1/A01", "labelFont", []any{"italic"}, false},
{"LabelFontBoldOk", "/Physical/BASIC/A/R1/A01", "labelFont", []any{"bold"}, false},
{"LabelColorOk", "/Physical/BASIC/A/R1/A01", "labelFont", []any{"color", "C0FFEE"}, false},
{"LabelBackgroundOk", "/Physical/BASIC/A/R1/A01", "labelBackground", []any{"C0FFEE"}, false},
{"ContentOk", "/Physical/BASIC/A/R1/A01", "displayContent", []any{true}, false},
{"AlphaOk", "/Physical/BASIC/A/R1/A01", "alpha", []any{true}, false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
controller, _, _ := interactLabelTestSetup(t)
err := controller.InteractObject(tt.path, tt.keyword, tt.value, tt.fromAttr)
err := controller.UpdateInteract(tt.path, tt.keyword, tt.value, tt.fromAttr)
assert.Nil(t, err)
})
}
Expand All @@ -135,8 +135,18 @@ func TestInteractObjectWithMock(t *testing.T) {
controller, mockAPI, _ := interactTestSetup(t)

test_utils.MockGetObject(mockAPI, tt.mockObject)
err := controller.InteractObject(tt.path, tt.keyword, tt.value, tt.fromAttr)
err := controller.UpdateInteract(tt.path, tt.keyword, []any{tt.value}, tt.fromAttr)
assert.Nil(t, err)
})
}
}

func TestSetLabel(t *testing.T) {
controller, mockAPI, _, _ := test_utils.NewControllerWithMocks(t)

room := test_utils.GetEntity("rack", "rack", "site.building.room", "domain")
test_utils.MockGetObject(mockAPI, room)
err := controller.SetLabel("/Physical/site/building/room/rack", []any{"myLabel"}, false)

assert.Nil(t, err)
}
6 changes: 3 additions & 3 deletions CLI/controllers/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@ func (controller Controller) UpdateLayer(path string, attributeName string, valu
return err
}

_, err = controller.UpdateObj(path, map[string]any{attributeName: applicability}, false)
_, err = controller.PatchObj(path, map[string]any{attributeName: applicability}, false)
case models.LayerFiltersAdd:
_, err = controller.UpdateObj(path, map[string]any{models.LayerFilters: "& (" + value.(string) + ")"}, false)
_, err = controller.PatchObj(path, map[string]any{models.LayerFilters: "& (" + value.(string) + ")"}, false)
default:
_, err = controller.UpdateObj(path, map[string]any{attributeName: value}, false)
_, err = controller.PatchObj(path, map[string]any{attributeName: value}, false)
if attributeName == "slug" {
State.Hierarchy.Children["Logical"].Children["Layers"].IsCached = false
}
Expand Down
Loading

0 comments on commit 822627d

Please sign in to comment.