• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / reporting / 728455154

pending completion
728455154

Pull #83

gitlab-ci

Fabio Tranchitella
ci(tests): device inventory data aggregation acceptance tests
Pull Request #83: feat: device inventory data aggregation

198 of 234 new or added lines in 6 files covered. (84.62%)

2 existing lines in 1 file now uncovered.

2090 of 2464 relevant lines covered (84.82%)

16.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/api/http/management.go
1
// Copyright 2022 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package http
16

17
import (
18
        "context"
19
        "fmt"
20
        "net/http"
21
        "net/url"
22
        "os"
23
        "strconv"
24
        "strings"
25

26
        "github.com/gin-gonic/gin"
27
        "github.com/pkg/errors"
28

29
        "github.com/mendersoftware/go-lib-micro/identity"
30
        "github.com/mendersoftware/go-lib-micro/rbac"
31
        "github.com/mendersoftware/go-lib-micro/rest.utils"
32

33
        "github.com/mendersoftware/reporting/app/reporting"
34
        "github.com/mendersoftware/reporting/model"
35
)
36

37
const (
38
        ParamPageDefault    = 1
39
        ParamPerPageDefault = 20
40

41
        hdrTotalCount = "X-Total-Count"
42
)
43

44
type ManagementController struct {
45
        reporting reporting.App
46
}
47

48
func NewManagementController(r reporting.App) *ManagementController {
51✔
49
        return &ManagementController{
51✔
50
                reporting: r,
51✔
51
        }
51✔
52
}
51✔
53

54
type attributes struct {
55
        Limit      int `json:"limit"`
56
        Count      int `json:"count"`
57
        Attributes []attribute
58
}
59

60
type attribute struct {
61
        Name  string `json:"name"`
62
        Scope string `json:"scope"`
63
}
64

65
func (mc *ManagementController) Aggregate(c *gin.Context) {
10✔
66
        ctx := c.Request.Context()
10✔
67

10✔
68
        params, err := parseAggregateParams(ctx, c)
10✔
69
        if err != nil {
14✔
70
                rest.RenderError(c,
4✔
71
                        http.StatusBadRequest,
4✔
72
                        errors.Wrap(err, "malformed request body"),
4✔
73
                )
4✔
74
                return
4✔
75
        }
4✔
76

77
        res, err := mc.reporting.InventoryAggregateDevices(ctx, params)
6✔
78
        if err != nil {
8✔
79
                rest.RenderError(c,
2✔
80
                        http.StatusInternalServerError,
2✔
81
                        err,
2✔
82
                )
2✔
83
                return
2✔
84
        }
2✔
85

86
        c.JSON(http.StatusOK, res)
4✔
87
}
88

89
func parseAggregateParams(ctx context.Context, c *gin.Context) (*model.AggregateParams, error) {
10✔
90
        var aggrgateParams model.AggregateParams
10✔
91

10✔
92
        err := c.ShouldBindJSON(&aggrgateParams)
10✔
93
        if err != nil {
12✔
94
                return nil, err
2✔
95
        }
2✔
96

97
        if id := identity.FromContext(ctx); id != nil {
16✔
98
                aggrgateParams.TenantID = id.Tenant
8✔
99
        } else {
8✔
NEW
100
                return nil, errors.New("missing tenant ID from the context")
×
NEW
101
        }
×
102

103
        if scope := rbac.ExtractScopeFromHeader(c.Request); scope != nil {
8✔
NEW
104
                aggrgateParams.Groups = scope.DeviceGroups
×
NEW
105
        }
×
106

107
        if err := aggrgateParams.Validate(); err != nil {
10✔
108
                return nil, err
2✔
109
        }
2✔
110

111
        return &aggrgateParams, nil
6✔
112
}
113

114
func (mc *ManagementController) Attrs(c *gin.Context) {
10✔
115
        ctx := c.Request.Context()
10✔
116

10✔
117
        var tenantID string
10✔
118
        if id := identity.FromContext(ctx); id != nil && id.Tenant != "" {
18✔
119
                tenantID = id.Tenant
8✔
120
        } else {
10✔
121
                rest.RenderError(c,
2✔
122
                        http.StatusBadRequest,
2✔
123
                        errors.New("missing tenant ID from the context"),
2✔
124
                )
2✔
125
                return
2✔
126
        }
2✔
127

128
        mapping, err := mc.reporting.GetMapping(ctx, tenantID)
8✔
129
        if err != nil {
10✔
130
                rest.RenderError(c,
2✔
131
                        http.StatusInternalServerError,
2✔
132
                        errors.Wrap(err, "failed to retrieve the mapping"),
2✔
133
                )
2✔
134
                return
2✔
135
        } else if mapping == nil {
10✔
136
                mapping = &model.Mapping{}
2✔
137
        }
2✔
138

139
        attributesList := make([]attribute, 0, len(mapping.Inventory))
6✔
140
        for _, attr := range mapping.Inventory {
10✔
141
                parts := strings.SplitN(attr, string(os.PathSeparator), 2)
4✔
142
                attributesList = append(attributesList, attribute{
4✔
143
                        Name:  parts[1],
4✔
144
                        Scope: parts[0],
4✔
145
                })
4✔
146
        }
4✔
147
        res := &attributes{
6✔
148
                Limit:      model.MaxMappingInventoryAttributes,
6✔
149
                Count:      len(mapping.Inventory),
6✔
150
                Attributes: attributesList,
6✔
151
        }
6✔
152

6✔
153
        c.JSON(http.StatusOK, res)
6✔
154
}
155

156
func (mc *ManagementController) Search(c *gin.Context) {
20✔
157
        ctx := c.Request.Context()
20✔
158
        params, err := parseSearchParams(ctx, c)
20✔
159
        if err != nil {
24✔
160
                rest.RenderError(c,
4✔
161
                        http.StatusBadRequest,
4✔
162
                        errors.Wrap(err, "malformed request body"),
4✔
163
                )
4✔
164
                return
4✔
165
        }
4✔
166

167
        res, total, err := mc.reporting.InventorySearchDevices(ctx, params)
16✔
168
        if err != nil {
18✔
169
                rest.RenderError(c,
2✔
170
                        http.StatusInternalServerError,
2✔
171
                        err,
2✔
172
                )
2✔
173
                return
2✔
174
        }
2✔
175

176
        pageLinkHdrs(c, params.Page, params.PerPage, total)
14✔
177

14✔
178
        c.Header(hdrTotalCount, strconv.Itoa(total))
14✔
179
        c.JSON(http.StatusOK, res)
14✔
180
}
181

182
func parseSearchParams(ctx context.Context, c *gin.Context) (*model.SearchParams, error) {
35✔
183
        var searchParams model.SearchParams
35✔
184

35✔
185
        err := c.ShouldBindJSON(&searchParams)
35✔
186
        if err != nil {
37✔
187
                return nil, err
2✔
188
        }
2✔
189

190
        if id := identity.FromContext(ctx); id != nil {
66✔
191
                searchParams.TenantID = id.Tenant
33✔
192
        } else {
33✔
193
                return nil, errors.New("missing tenant ID from the context")
×
194
        }
×
195

196
        if scope := rbac.ExtractScopeFromHeader(c.Request); scope != nil {
35✔
197
                searchParams.Groups = scope.DeviceGroups
2✔
198
        }
2✔
199

200
        if searchParams.PerPage <= 0 {
58✔
201
                searchParams.PerPage = ParamPerPageDefault
25✔
202
        }
25✔
203
        if searchParams.Page <= 0 {
58✔
204
                searchParams.Page = ParamPageDefault
25✔
205
        }
25✔
206

207
        if err := searchParams.Validate(); err != nil {
37✔
208
                return nil, err
4✔
209
        }
4✔
210

211
        return &searchParams, nil
29✔
212
}
213

214
func pageLinkHdrs(c *gin.Context, page, perPage, total int) {
25✔
215
        url := &url.URL{
25✔
216
                Path:     c.Request.URL.Path,
25✔
217
                RawQuery: c.Request.URL.RawQuery,
25✔
218
                Fragment: c.Request.URL.Fragment,
25✔
219
        }
25✔
220

25✔
221
        query := url.Query()
25✔
222

25✔
223
        query.Set("page", "1")
25✔
224
        query.Set("per_page", fmt.Sprintf("%d", perPage))
25✔
225
        url.RawQuery = query.Encode()
25✔
226
        Link := fmt.Sprintf(`<%s>;rel="first"`, url.String())
25✔
227
        // Previous page
25✔
228
        if page > 1 {
29✔
229
                query.Set("page", fmt.Sprintf("%d", page-1))
4✔
230
                url.RawQuery = query.Encode()
4✔
231
                Link = fmt.Sprintf(`%s, <%s>;rel="previous"`, Link, url.String())
4✔
232
        }
4✔
233

234
        // Next page
235
        if total > (perPage*page - 1) {
25✔
236
                query.Set("page", fmt.Sprintf("%d", page+1))
×
237
                url.RawQuery = query.Encode()
×
238
                Link = fmt.Sprintf(`%s, <%s>;rel="next"`, Link, url.String())
×
239

×
240
        }
×
241
        c.Header("Link", Link)
25✔
242
}
243

244
func (mc *ManagementController) SearchAttrs(c *gin.Context) {
×
245
        ctx := c.Request.Context()
×
246

×
247
        id := identity.FromContext(ctx)
×
248
        res, err := mc.reporting.GetSearchableInvAttrs(ctx, id.Tenant)
×
249
        if err != nil {
×
250
                rest.RenderError(c,
×
251
                        http.StatusInternalServerError,
×
252
                        err,
×
253
                )
×
254
                return
×
255
        }
×
256

257
        c.JSON(http.StatusOK, res)
×
258
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc