Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion internal/api/docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,7 @@ components:
properties:
bricks:
items:
$ref: '#/components/schemas/BrickInstance'
$ref: '#/components/schemas/BrickInstanceListItem'
nullable: true
type: array
type: object
Expand Down Expand Up @@ -1380,6 +1380,33 @@ components:
for backward compatibility.'
type: object
type: object
BrickInstanceListItem:
properties:
author:
type: string
category:
type: string
config_variables:
items:
$ref: '#/components/schemas/BrickConfigVariable'
type: array
id:
type: string
model:
type: string
name:
type: string
require_model:
type: boolean
status:
type: string
variables:
additionalProperties:
type: string
description: 'Deprecated: use config_variables instead. This field is kept
for backward compatibility.'
type: object
type: object
BrickListItem:
properties:
author:
Expand Down
17 changes: 16 additions & 1 deletion internal/e2e/client/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions internal/e2e/daemon/bricks_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,20 @@ func TestGetAppBrickInstances(t *testing.T) {
var actualBody models.ErrorResponse
createResp, httpClient := setupTestApp(t)
t.Run("GetAppBrickInstances_Success", func(t *testing.T) {
expectedVariables := map[string]string{
"CUSTOM_MODEL_PATH": "/home/arduino/.arduino-bricks/ei-models",
"EI_CLASSIFICATION_MODEL": "/models/ootb/ei/mobilenet-v2-224px.eim",
}

brickInstances, err := httpClient.GetAppBrickInstancesWithResponse(t.Context(), *createResp.JSON201.Id, func(ctx context.Context, req *http.Request) error { return nil })
require.NoError(t, err)
require.Len(t, *brickInstances.JSON200.Bricks, 1)
require.Equal(t, ImageClassifactionBrickID, *(*brickInstances.JSON200.Bricks)[0].Id)
require.Equal(t, expectedConfigVariables, *(*brickInstances.JSON200.Bricks)[0].ConfigVariables)
require.Equal(t, "Arduino", *(*brickInstances.JSON200.Bricks)[0].Author)
require.Equal(t, "video", *(*brickInstances.JSON200.Bricks)[0].Category)
require.True(t, *(*brickInstances.JSON200.Bricks)[0].RequireModel)
require.Equal(t, expectedVariables, *(*brickInstances.JSON200.Bricks)[0].Variables)

})

Expand Down
4 changes: 2 additions & 2 deletions internal/orchestrator/bricks/bricks.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (s *Service) List() (BrickListResult, error) {
}

func (s *Service) AppBrickInstancesList(a *app.ArduinoApp) (AppBrickInstancesResult, error) {
res := AppBrickInstancesResult{BrickInstances: make([]BrickInstance, len(a.Descriptor.Bricks))}
res := AppBrickInstancesResult{BrickInstances: make([]BrickInstanceListItem, len(a.Descriptor.Bricks))}
for i, brickInstance := range a.Descriptor.Bricks {
brick, found := s.bricksIndex.FindBrickByID(brickInstance.ID)
if !found {
Expand All @@ -80,7 +80,7 @@ func (s *Service) AppBrickInstancesList(a *app.ArduinoApp) (AppBrickInstancesRes

variablesMap, configVariables := getBrickConfigDetails(brick, brickInstance.Variables)

res.BrickInstances[i] = BrickInstance{
res.BrickInstances[i] = BrickInstanceListItem{
ID: brick.ID,
Name: brick.Name,
Author: "Arduino", // TODO: for now we only support our bricks
Expand Down
188 changes: 188 additions & 0 deletions internal/orchestrator/bricks/bricks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,3 +644,191 @@ func TestAppBrickInstanceModelsDetails(t *testing.T) {
})
}
}

func TestAppBrickInstancesList(t *testing.T) {

bIndex := &bricksindex.BricksIndex{
Bricks: []bricksindex.Brick{
{
ID: "arduino:weather_forecast",
Name: "Weather Forecast",
Category: "miscellaneous",
RequireModel: false,
Variables: []bricksindex.BrickVariable{},
},
{
ID: "arduino:object_detection",
Name: "Object Detection",
Category: "video",
ModelName: "yolox-object-detection",
RequireModel: true,
Variables: []bricksindex.BrickVariable{
{Name: "CUSTOM_MODEL_PATH", DefaultValue: "/home/arduino/.arduino-bricks/ei-models", Description: "path to the custom model directory"},
{Name: "EI_OBJ_DETECTION_MODEL", DefaultValue: "/models/ootb/ei/yolo-x-nano.eim", Description: "path to the model file"},
},
},
{
ID: "arduino:audio_classification",
Name: "Audio Classification",
Category: "audio",
ModelName: "glass-breaking",
RequireModel: true,
Variables: []bricksindex.BrickVariable{
{Name: "CUSTOM_MODEL_PATH", DefaultValue: "/home/arduino/.arduino-bricks/ei-models"},
{Name: "EI_AUDIO_CLASSIFICATION_MODEL", DefaultValue: "/models/ootb/ei/glass-breaking.eim"},
},
},
{
ID: "arduino:streamlit_ui",
Name: "WebUI - Streamlit",
Category: "ui",
RequireModel: false,
Ports: []string{"7000", "8000"},
},
},
}

svc := &Service{
bricksIndex: bIndex,
modelsIndex: &modelsindex.ModelsIndex{},
}

tests := []struct {
name string
app *app.ArduinoApp
expectedError string
validate func(*testing.T, AppBrickInstancesResult)
}{
{
name: "Error - Brick not found in Index",
app: &app.ArduinoApp{
Descriptor: app.AppDescriptor{
Bricks: []app.Brick{
{ID: "arduino:non_existent_brick"},
},
},
},
expectedError: "brick not found with id arduino:non_existent_brick",
},
{
name: "Success - Empty App",
app: &app.ArduinoApp{
Descriptor: app.AppDescriptor{
Bricks: []app.Brick{},
},
},
validate: func(t *testing.T, res AppBrickInstancesResult) {
require.Empty(t, res.BrickInstances)
},
},
{
name: "Success - Simple Brick",
app: &app.ArduinoApp{
Descriptor: app.AppDescriptor{
Bricks: []app.Brick{
{ID: "arduino:weather_forecast"},
},
},
},
validate: func(t *testing.T, res AppBrickInstancesResult) {
require.Len(t, res.BrickInstances, 1)
brick := res.BrickInstances[0]

require.Equal(t, "arduino:weather_forecast", brick.ID)
require.Equal(t, "Weather Forecast", brick.Name)
require.Equal(t, "miscellaneous", brick.Category)
require.Equal(t, "installed", brick.Status)
require.Equal(t, "Arduino", brick.Author)
require.False(t, brick.RequireModel)
require.Empty(t, brick.ModelID)
},
},
{
name: "Success - Brick with Model Configured",
app: &app.ArduinoApp{
Descriptor: app.AppDescriptor{
Bricks: []app.Brick{
{
ID: "arduino:object_detection",
Model: "face-detection", // default model overridden
Variables: map[string]string{
"CUSTOM_MODEL_PATH": "/custom/path",
},
},
},
},
},
validate: func(t *testing.T, res AppBrickInstancesResult) {
require.Len(t, res.BrickInstances, 1)
brick := res.BrickInstances[0]

require.Equal(t, "arduino:object_detection", brick.ID)
require.Equal(t, "video", brick.Category)
require.True(t, brick.RequireModel)
require.Equal(t, "face-detection", brick.ModelID)

foundCustom := false
for _, v := range brick.ConfigVariables {
if v.Name == "CUSTOM_MODEL_PATH" {
require.Equal(t, "/custom/path", v.Value)
foundCustom = true
}
}
require.True(t, foundCustom, "Variable CUSTOM_MODEL_PATH should be present and overridden")
},
},
{
name: "Success - Multiple Bricks",
app: &app.ArduinoApp{
Descriptor: app.AppDescriptor{
Bricks: []app.Brick{
{ID: "arduino:streamlit_ui"},
{ID: "arduino:audio_classification", Model: "glass-breaking"},
},
},
},
validate: func(t *testing.T, res AppBrickInstancesResult) {
require.Len(t, res.BrickInstances, 2)

// Brick 1: Streamlit UI
b1 := res.BrickInstances[0]
require.Equal(t, "arduino:streamlit_ui", b1.ID)
require.Equal(t, "WebUI - Streamlit", b1.Name)
require.Equal(t, "Arduino", b1.Author)
require.Equal(t, "ui", b1.Category)
require.Equal(t, "installed", b1.Status)
require.Equal(t, "", b1.ModelID)
require.Empty(t, b1.Variables)
require.Empty(t, b1.ConfigVariables)
require.False(t, b1.RequireModel)

// Brick 2: Audio Classification
b2 := res.BrickInstances[1]
require.Equal(t, "arduino:audio_classification", b2.ID)
require.Equal(t, "audio", b2.Category)
require.True(t, b2.RequireModel)
require.Equal(t, "glass-breaking", b2.ModelID)
require.Equal(t, 2, len(b2.ConfigVariables))
require.Equal(t, "/home/arduino/.arduino-bricks/ei-models", b2.ConfigVariables[0].Value)
require.Equal(t, "/models/ootb/ei/glass-breaking.eim", b2.ConfigVariables[1].Value)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := svc.AppBrickInstancesList(tt.app)

if tt.expectedError != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tt.expectedError)
return
}

require.NoError(t, err)
if tt.validate != nil {
tt.validate(t, result)
}
})
}
}
14 changes: 12 additions & 2 deletions internal/orchestrator/bricks/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,19 @@ type BrickListItem struct {
}

type AppBrickInstancesResult struct {
BrickInstances []BrickInstance `json:"bricks"`
BrickInstances []BrickInstanceListItem `json:"bricks"`
}
type BrickInstanceListItem struct {
ID string `json:"id"`
Name string `json:"name"`
Author string `json:"author"`
Category string `json:"category"`
Status string `json:"status"`
Variables map[string]string `json:"variables,omitempty" description:"Deprecated: use config_variables instead. This field is kept for backward compatibility."`
ConfigVariables []BrickConfigVariable `json:"config_variables,omitempty"`
RequireModel bool `json:"require_model"`
ModelID string `json:"model,omitempty"`
}

type BrickInstance struct {
ID string `json:"id"`
Name string `json:"name"`
Expand Down