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

Add breakers to racks #492

Merged
merged 11 commits into from
Jul 12, 2024
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
Loading