Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7508a4b

Browse files
lucarin91mirkoCrobu
andcommitted
API: add "UsedByApps" to the details of a brick (#30)
The API returning the details of a brick show fill in the UsedByApps property. The App Lab uses this to show what examples or apps are using a given brick.* add useByApps field for brick details endpoint * partial test implementation * add test end2end * delete wrong tests * refactoring * make lint happy * code review fixes * fix error message --------- Co-authored-by: mirkoCrobu <m.crobu@ext.arduino.cc>
1 parent d43f0e5 commit 7508a4b

File tree

7 files changed

+121
-13
lines changed

7 files changed

+121
-13
lines changed

‎cmd/arduino-app-cli/brick/bricks.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ package brick
1717

1818
import (
1919
"github.com/spf13/cobra"
20+
21+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2022
)
2123

22-
func NewBrickCmd() *cobra.Command {
24+
func NewBrickCmd(cfg config.Configuration) *cobra.Command {
2325
appCmd := &cobra.Command{
2426
Use: "brick",
2527
Short: "Manage Arduino Bricks",
2628
}
2729

2830
appCmd.AddCommand(newBricksListCmd())
29-
appCmd.AddCommand(newBricksDetailsCmd())
31+
appCmd.AddCommand(newBricksDetailsCmd(cfg))
3032

3133
return appCmd
3234
}

‎cmd/arduino-app-cli/brick/details.go‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,23 @@ import (
2525
"github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/internal/servicelocator"
2626
"github.com/arduino/arduino-app-cli/cmd/feedback"
2727
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricks"
28+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2829
)
2930

30-
func newBricksDetailsCmd() *cobra.Command {
31+
func newBricksDetailsCmd(cfg config.Configuration) *cobra.Command {
3132
return &cobra.Command{
3233
Use: "details",
3334
Short: "Details of a specific brick",
3435
Args: cobra.ExactArgs(1),
3536
Run: func(cmd *cobra.Command, args []string) {
36-
bricksDetailsHandler(args[0])
37+
bricksDetailsHandler(args[0], cfg)
3738
},
3839
}
3940
}
4041

41-
func bricksDetailsHandler(id string) {
42-
res, err := servicelocator.GetBrickService().BricksDetails(id)
42+
func bricksDetailsHandler(id string, cfg config.Configuration) {
43+
res, err := servicelocator.GetBrickService().BricksDetails(id, servicelocator.GetAppIDProvider(),
44+
cfg)
4345
if err != nil {
4446
if errors.Is(err, bricks.ErrBrickNotFound) {
4547
feedback.Fatal(err.Error(), feedback.ErrBadArgument)

‎cmd/arduino-app-cli/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func run(configuration cfg.Configuration) error {
7171

7272
rootCmd.AddCommand(
7373
app.NewAppCmd(configuration),
74-
brick.NewBrickCmd(),
74+
brick.NewBrickCmd(configuration),
7575
completion.NewCompletionCommand(),
7676
daemon.NewDaemonCmd(configuration, Version),
7777
properties.NewPropertiesCmd(configuration),

‎internal/api/api.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func NewHTTPRouter(
5656
mux.Handle("GET /v1/version", handlers.HandlerVersion(version))
5757
mux.Handle("GET /v1/config", handlers.HandleConfig(cfg))
5858
mux.Handle("GET /v1/bricks", handlers.HandleBrickList(brickService))
59-
mux.Handle("GET /v1/bricks/{brickID}", handlers.HandleBrickDetails(brickService))
59+
mux.Handle("GET /v1/bricks/{brickID}", handlers.HandleBrickDetails(brickService, idProvider, cfg))
6060

6161
mux.Handle("GET /v1/properties", handlers.HandlePropertyKeys(cfg))
6262
mux.Handle("GET /v1/properties/{key}", handlers.HandlePropertyGet(cfg))

‎internal/api/handlers/bricks.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/arduino/arduino-app-cli/internal/api/models"
2727
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
2828
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricks"
29+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2930
"github.com/arduino/arduino-app-cli/internal/render"
3031
)
3132

@@ -153,14 +154,15 @@ func HandleBrickCreate(
153154
}
154155
}
155156

156-
func HandleBrickDetails(brickService *bricks.Service) http.HandlerFunc {
157+
func HandleBrickDetails(brickService *bricks.Service, idProvider *app.IDProvider,
158+
cfg config.Configuration) http.HandlerFunc {
157159
return func(w http.ResponseWriter, r *http.Request) {
158160
id := r.PathValue("brickID")
159161
if id == "" {
160162
render.EncodeResponse(w, http.StatusBadRequest, models.ErrorResponse{Details: "id must be set"})
161163
return
162164
}
163-
res, err := brickService.BricksDetails(id)
165+
res, err := brickService.BricksDetails(id, idProvider, cfg)
164166
if err != nil {
165167
if errors.Is(err, bricks.ErrBrickNotFound) {
166168
details := fmt.Sprintf("brick with id %q not found", id)

‎internal/e2e/daemon/brick_test.go‎

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,44 @@ import (
2424

2525
"github.com/arduino/go-paths-helper"
2626
"github.com/stretchr/testify/require"
27+
"go.bug.st/f"
2728

2829
"github.com/arduino/arduino-app-cli/internal/api/models"
30+
"github.com/arduino/arduino-app-cli/internal/e2e/client"
2931
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricksindex"
3032
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
3133
"github.com/arduino/arduino-app-cli/internal/store"
3234
)
3335

36+
func setupTestBrick(t *testing.T) (*client.CreateAppResp, *client.ClientWithResponses) {
37+
httpClient := GetHttpclient(t)
38+
createResp, err := httpClient.CreateAppWithResponse(
39+
t.Context(),
40+
&client.CreateAppParams{SkipSketch: f.Ptr(true)},
41+
client.CreateAppRequest{
42+
Icon: f.Ptr("💻"),
43+
Name: "test-app",
44+
Description: f.Ptr("My app description"),
45+
},
46+
func(ctx context.Context, req *http.Request) error { return nil },
47+
)
48+
require.NoError(t, err)
49+
require.Equal(t, http.StatusCreated, createResp.StatusCode())
50+
require.NotNil(t, createResp.JSON201)
51+
52+
resp, err := httpClient.UpsertAppBrickInstanceWithResponse(
53+
t.Context(),
54+
*createResp.JSON201.Id,
55+
ImageClassifactionBrickID,
56+
client.BrickCreateUpdateRequest{Model: f.Ptr("mobilenet-image-classification")},
57+
func(ctx context.Context, req *http.Request) error { return nil },
58+
)
59+
require.NoError(t, err)
60+
require.Equal(t, http.StatusOK, resp.StatusCode())
61+
62+
return createResp, httpClient
63+
}
64+
3465
func TestBricksList(t *testing.T) {
3566
httpClient := GetHttpclient(t)
3667

@@ -56,8 +87,8 @@ func TestBricksList(t *testing.T) {
5687
}
5788

5889
func TestBricksDetails(t *testing.T) {
90+
_, httpClient := setupTestBrick(t)
5991

60-
httpClient := GetHttpclient(t)
6192
t.Run("should return 404 Not Found for an invalid brick ID", func(t *testing.T) {
6293
invalidBrickID := "notvalidBrickId"
6394
var actualBody models.ErrorResponse
@@ -76,6 +107,14 @@ func TestBricksDetails(t *testing.T) {
76107
t.Run("should return 200 OK with full details for a valid brick ID", func(t *testing.T) {
77108
validBrickID := "arduino:image_classification"
78109

110+
expectedUsedByApps := []client.AppReference{
111+
{
112+
Id: f.Ptr("dXNlcjp0ZXN0LWFwcA"),
113+
Name: f.Ptr("test-app"),
114+
Icon: f.Ptr("💻"),
115+
},
116+
}
117+
79118
response, err := httpClient.GetBrickDetailsWithResponse(t.Context(), validBrickID, func(ctx context.Context, req *http.Request) error { return nil })
80119
require.NoError(t, err)
81120
require.Equal(t, http.StatusOK, response.StatusCode(), "status code should be 200 ok")
@@ -92,6 +131,7 @@ func TestBricksDetails(t *testing.T) {
92131
require.Equal(t, "path to the model file", *(*response.JSON200.Variables)["EI_CLASSIFICATION_MODEL"].Description)
93132
require.Equal(t, false, *(*response.JSON200.Variables)["EI_CLASSIFICATION_MODEL"].Required)
94133
require.NotEmpty(t, *response.JSON200.Readme)
95-
require.Nil(t, response.JSON200.UsedByApps)
134+
require.NotNil(t, response.JSON200.UsedByApps, "UsedByApps should not be nil")
135+
require.Equal(t, expectedUsedByApps, *(response.JSON200.UsedByApps))
96136
})
97137
}

‎internal/orchestrator/bricks/bricks.go‎

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ package bricks
1818
import (
1919
"errors"
2020
"fmt"
21+
"log/slog"
2122
"slices"
2223

2324
"github.com/arduino/go-paths-helper"
2425
"go.bug.st/f"
2526

2627
"github.com/arduino/arduino-app-cli/internal/orchestrator/app"
2728
"github.com/arduino/arduino-app-cli/internal/orchestrator/bricksindex"
29+
"github.com/arduino/arduino-app-cli/internal/orchestrator/config"
2830
"github.com/arduino/arduino-app-cli/internal/orchestrator/modelsindex"
2931
"github.com/arduino/arduino-app-cli/internal/store"
3032
)
@@ -151,7 +153,8 @@ func getBrickVariableDetails(
151153
return variablesMap, variableDetails
152154
}
153155

154-
func (s *Service) BricksDetails(id string) (BrickDetailsResult, error) {
156+
func (s *Service) BricksDetails(id string, idProvider *app.IDProvider,
157+
cfg config.Configuration) (BrickDetailsResult, error) {
155158
brick, found := s.bricksIndex.FindBrickByID(id)
156159
if !found {
157160
return BrickDetailsResult{}, ErrBrickNotFound
@@ -186,6 +189,11 @@ func (s *Service) BricksDetails(id string) (BrickDetailsResult, error) {
186189
}
187190
})
188191

192+
usedByApps, err := getUsedByApps(cfg, brick.ID, idProvider)
193+
if err != nil {
194+
return BrickDetailsResult{}, fmt.Errorf("unable to get used by apps: %w", err)
195+
}
196+
189197
return BrickDetailsResult{
190198
ID: id,
191199
Name: brick.Name,
@@ -197,9 +205,63 @@ func (s *Service) BricksDetails(id string) (BrickDetailsResult, error) {
197205
Readme: readme,
198206
ApiDocsPath: apiDocsPath,
199207
CodeExamples: codeExamples,
208+
UsedByApps: usedByApps,
200209
}, nil
201210
}
202211

212+
func getUsedByApps(
213+
cfg config.Configuration, brickId string, idProvider *app.IDProvider) ([]AppReference, error) {
214+
var (
215+
pathsToExplore paths.PathList
216+
appPaths paths.PathList
217+
)
218+
pathsToExplore.Add(cfg.ExamplesDir())
219+
pathsToExplore.Add(cfg.AppsDir())
220+
usedByApps := []AppReference{}
221+
222+
for _, p := range pathsToExplore {
223+
res, err := p.ReadDirRecursiveFiltered(func(file *paths.Path) bool {
224+
if file.Base() == ".cache" {
225+
return false
226+
}
227+
if file.Join("app.yaml").NotExist() && file.Join("app.yml").NotExist() {
228+
return true
229+
}
230+
return false
231+
}, paths.FilterDirectories(), paths.FilterOutNames("python", "sketch", ".cache"))
232+
if err != nil {
233+
slog.Error("unable to list apps", slog.String("error", err.Error()))
234+
return usedByApps, err
235+
}
236+
appPaths.AddAllMissing(res)
237+
}
238+
239+
for _, file := range appPaths {
240+
app, err := app.Load(file.String())
241+
if err != nil {
242+
// we are not considering the broken apps
243+
slog.Warn("unable to parse app.yaml, skipping", "path", file.String(), "error", err.Error())
244+
continue
245+
}
246+
247+
for _, b := range app.Descriptor.Bricks {
248+
if b.ID == brickId {
249+
id, err := idProvider.IDFromPath(app.FullPath)
250+
if err != nil {
251+
return usedByApps, fmt.Errorf("failed to get app ID for %s: %w", app.FullPath, err)
252+
}
253+
usedByApps = append(usedByApps, AppReference{
254+
Name: app.Name,
255+
ID: id.String(),
256+
Icon: app.Descriptor.Icon,
257+
})
258+
break
259+
}
260+
}
261+
}
262+
return usedByApps, nil
263+
}
264+
203265
type BrickCreateUpdateRequest struct {
204266
ID string `json:"-"`
205267
Model *string `json:"model"`

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /