3
0

Base reporting service definitions

This commit is contained in:
Tomaž Jerman
2021-05-30 15:24:22 +02:00
parent 6751d0ec8b
commit 4aa429362b
30 changed files with 3836 additions and 0 deletions

View File

@@ -1341,6 +1341,122 @@ endpoints:
type: json.RawMessage
required: false
title: Rendering options
- title: Reports
path: "/reports"
entrypoint: report
authentication: []
imports:
- github.com/cortezaproject/corteza-server/pkg/label
- github.com/cortezaproject/corteza-server/pkg/report
- github.com/cortezaproject/corteza-server/system/types
apis:
- name: list
method: GET
title: List reports
path: "/"
parameters:
get:
- name: handle
required: false
title: Report handle
type: string
- name: deleted
required: false
title: Exclude (0, default), include (1) or return only (2) deleted reports
type: uint
- type: map[string]string
name: labels
title: Labels
parser: label.ParseStrings
- type: uint
name: limit
title: Limit
- type: string
name: pageCursor
title: Page cursor
- type: string
name: sort
title: Sort items
- name: create
method: POST
title: Create report
path: "/"
parameters:
post:
- { name: handle, type: string, title: Client handle }
- { name: meta, type: '*types.ReportMeta', title: Additional info, parser: types.ParseReportMeta }
- { name: sources, type: "types.ReportDataSourceSet", title: Report source definitions }
- { name: projections, type: "types.ReportProjectionSet", title: Report projections definition }
- { name: labels, type: 'map[string]string', title: Labels, parser: label.ParseStrings }
- name: update
method: PUT
title: Update report
path: "/{reportID}"
parameters:
path:
- type: uint64
name: reportID
required: true
title: Report ID
post:
- { name: handle, type: string, title: Client handle }
- { name: meta, type: '*types.ReportMeta', title: Additional info, parser: types.ParseReportMeta }
- { name: sources, type: "types.ReportDataSourceSet", title: Report sources definition }
- { name: projections, type: "types.ReportProjectionSet", title: Report projections definition }
- { name: labels, type: 'map[string]string', title: Labels, parser: label.ParseStrings }
- name: read
method: GET
title: Read report details
path: "/{reportID}"
parameters:
path:
- type: uint64
name: reportID
required: true
title: Report ID
- name: delete
method: DELETE
title: Remove report
path: "/{reportID}"
parameters:
path:
- type: uint64
name: reportID
required: true
title: Report ID
- name: undelete
method: POST
title: Undelete report
path: "/{reportID}/undelete"
parameters:
path:
- type: uint64
name: reportID
required: true
title: Report ID
# @todo better name
- name: runFresh
method: POST
title: Run report (fresh)
path: "/run"
parameters:
post:
- { name: sources, type: "types.ReportDataSourceSet", title: Report steps definition }
- { name: steps, type: "report.StepDefinitionSet", title: Report steps definition }
- { name: frames, type: "report.FrameDefinitionSet", title: Report data frame definitions }
- name: run
method: POST
title: Run report
path: "/{reportID}/run"
parameters:
path:
- type: uint64
name: reportID
required: true
title: Report ID
post:
- { name: frames, type: "report.FrameDefinitionSet", title: Report data frame definitions }
- title: Statistics
entrypoint: stats
path: "/stats"

View File

@@ -0,0 +1,190 @@
package handlers
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/system/rest/request"
"github.com/go-chi/chi"
"net/http"
)
type (
// Internal API interface
ReportAPI interface {
List(context.Context, *request.ReportList) (interface{}, error)
Create(context.Context, *request.ReportCreate) (interface{}, error)
Update(context.Context, *request.ReportUpdate) (interface{}, error)
Read(context.Context, *request.ReportRead) (interface{}, error)
Delete(context.Context, *request.ReportDelete) (interface{}, error)
Undelete(context.Context, *request.ReportUndelete) (interface{}, error)
RunFresh(context.Context, *request.ReportRunFresh) (interface{}, error)
Run(context.Context, *request.ReportRun) (interface{}, error)
}
// HTTP API interface
Report struct {
List func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Undelete func(http.ResponseWriter, *http.Request)
RunFresh func(http.ResponseWriter, *http.Request)
Run func(http.ResponseWriter, *http.Request)
}
)
func NewReport(h ReportAPI) *Report {
return &Report{
List: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportList()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.List(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportCreate()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Create(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Update: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportUpdate()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Update(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Read: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportRead()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Read(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Delete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportDelete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Delete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Undelete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportUndelete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Undelete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
RunFresh: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportRunFresh()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.RunFresh(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
Run: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportRun()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Run(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}
api.Send(w, r, value)
},
}
}
func (h Report) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Get("/reports/", h.List)
r.Post("/reports/", h.Create)
r.Put("/reports/{reportID}", h.Update)
r.Get("/reports/{reportID}", h.Read)
r.Delete("/reports/{reportID}", h.Delete)
r.Post("/reports/{reportID}/undelete", h.Undelete)
r.Post("/reports/run", h.RunFresh)
r.Post("/reports/{reportID}/run", h.Run)
})
}

184
system/rest/report.go Normal file
View File

@@ -0,0 +1,184 @@
package rest
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/api"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/report"
"github.com/cortezaproject/corteza-server/system/rest/request"
"github.com/cortezaproject/corteza-server/system/service"
"github.com/cortezaproject/corteza-server/system/types"
"github.com/pkg/errors"
)
var _ = errors.Wrap
type (
Report struct {
report reportService
ac reportAccessController
}
reportService interface {
LookupByID(ctx context.Context, ID uint64) (app *types.Report, err error)
Search(ctx context.Context, filter types.ReportFilter) (aa types.ReportSet, f types.ReportFilter, err error)
Create(ctx context.Context, new *types.Report) (app *types.Report, err error)
Update(ctx context.Context, upd *types.Report) (app *types.Report, err error)
Delete(ctx context.Context, ID uint64) (err error)
Undelete(ctx context.Context, ID uint64) (err error)
// @todo
// Run(ctx context.Context, ID uint64, dd report.DatasetDefinitionSet) (rr *report.Matrix, err error)
RunFresh(ctx context.Context, src types.ReportDataSourceSet, st report.StepDefinitionSet, dd report.FrameDefinitionSet) (rr []*report.Frame, err error)
}
reportAccessController interface {
CanGrant(context.Context) bool
CanUpdateReport(context.Context, *types.Report) bool
CanDeleteReport(context.Context, *types.Report) bool
CanRunReport(context.Context, *types.Report) bool
}
reportPayload struct {
*types.Report
CanGrant bool `json:"canGrant"`
CanUpdateReport bool `json:"canUpdateReport"`
CanDeleteReport bool `json:"canDeleteReport"`
CanRunReport bool `json:"canRunReport"`
}
reportSetPayload struct {
Filter types.ReportFilter `json:"filter"`
Set []*reportPayload `json:"set"`
}
reportFramePayload struct {
Frames []*report.Frame `json:"frames"`
}
)
func (Report) New() *Report {
return &Report{
report: service.DefaultReport,
ac: service.DefaultAccessControl,
}
}
func (ctrl *Report) List(ctx context.Context, r *request.ReportList) (interface{}, error) {
var (
err error
f = types.ReportFilter{
Handle: r.Handle,
Labels: r.Labels,
Deleted: filter.State(r.Deleted),
}
)
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
return nil, err
}
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
return nil, err
}
set, filter, err := ctrl.report.Search(ctx, f)
return ctrl.makeFilterPayload(ctx, set, filter, err)
}
func (ctrl *Report) Create(ctx context.Context, r *request.ReportCreate) (interface{}, error) {
var (
err error
app = &types.Report{
Handle: r.Handle,
Meta: r.Meta,
Sources: r.Sources,
Projections: r.Projections,
Labels: r.Labels,
}
)
app, err = ctrl.report.Create(ctx, app)
return ctrl.makePayload(ctx, app, err)
}
func (ctrl *Report) Update(ctx context.Context, r *request.ReportUpdate) (interface{}, error) {
var (
err error
app = &types.Report{
ID: r.ReportID,
Handle: r.Handle,
Meta: r.Meta,
Sources: r.Sources,
Projections: r.Projections,
Labels: r.Labels,
}
)
app, err = ctrl.report.Update(ctx, app)
return ctrl.makePayload(ctx, app, err)
}
func (ctrl *Report) Read(ctx context.Context, r *request.ReportRead) (interface{}, error) {
app, err := ctrl.report.LookupByID(ctx, r.ReportID)
return ctrl.makePayload(ctx, app, err)
}
func (ctrl *Report) Delete(ctx context.Context, r *request.ReportDelete) (interface{}, error) {
return api.OK(), ctrl.report.Delete(ctx, r.ReportID)
}
func (ctrl *Report) Undelete(ctx context.Context, r *request.ReportUndelete) (interface{}, error) {
return api.OK(), ctrl.report.Undelete(ctx, r.ReportID)
}
func (ctrl *Report) Run(ctx context.Context, r *request.ReportRun) (interface{}, error) {
// @todo...
return nil, nil
}
func (ctrl *Report) RunFresh(ctx context.Context, r *request.ReportRunFresh) (interface{}, error) {
rr, err := ctrl.report.RunFresh(ctx, r.Sources, r.Steps, r.Frames)
return ctrl.makeReportFramePayload(ctx, rr, err)
}
func (ctrl Report) makePayload(ctx context.Context, m *types.Report, err error) (*reportPayload, error) {
if err != nil || m == nil {
return nil, err
}
return &reportPayload{
Report: m,
CanGrant: ctrl.ac.CanGrant(ctx),
CanUpdateReport: ctrl.ac.CanUpdateReport(ctx, m),
CanDeleteReport: ctrl.ac.CanDeleteReport(ctx, m),
}, nil
}
func (ctrl Report) makeFilterPayload(ctx context.Context, nn types.ReportSet, f types.ReportFilter, err error) (*reportSetPayload, error) {
if err != nil {
return nil, err
}
msp := &reportSetPayload{Filter: f, Set: make([]*reportPayload, len(nn))}
for i := range nn {
msp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
}
return msp, nil
}
func (ctrl Report) makeReportFramePayload(ctx context.Context, ff []*report.Frame, err error) (*reportFramePayload, error) {
if err != nil || len(ff) == 0 {
return nil, err
}
return &reportFramePayload{
Frames: ff,
}, nil
}

View File

@@ -0,0 +1,766 @@
package request
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
//
import (
"encoding/json"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/label"
"github.com/cortezaproject/corteza-server/pkg/payload"
"github.com/cortezaproject/corteza-server/pkg/report"
"github.com/cortezaproject/corteza-server/system/types"
"github.com/go-chi/chi"
"io"
"mime/multipart"
"net/http"
"strings"
)
// dummy vars to prevent
// unused imports complain
var (
_ = chi.URLParam
_ = multipart.ErrMessageTooLarge
_ = payload.ParseUint64s
_ = strings.ToLower
_ = io.EOF
_ = fmt.Errorf
_ = json.NewEncoder
)
type (
// Internal API interface
ReportList struct {
// Handle GET parameter
//
// Report handle
Handle string
// Deleted GET parameter
//
// Exclude (0, default), include (1) or return only (2) deleted reports
Deleted uint
// Labels GET parameter
//
// Labels
Labels map[string]string
// Limit GET parameter
//
// Limit
Limit uint
// PageCursor GET parameter
//
// Page cursor
PageCursor string
// Sort GET parameter
//
// Sort items
Sort string
}
ReportCreate struct {
// Handle POST parameter
//
// Client handle
Handle string
// Meta POST parameter
//
// Additional info
Meta *types.ReportMeta
// Sources POST parameter
//
// Report source definitions
Sources types.ReportDataSourceSet
// Projections POST parameter
//
// Report projections definition
Projections types.ReportProjectionSet
// Labels POST parameter
//
// Labels
Labels map[string]string
}
ReportUpdate struct {
// ReportID PATH parameter
//
// Report ID
ReportID uint64 `json:",string"`
// Handle POST parameter
//
// Client handle
Handle string
// Meta POST parameter
//
// Additional info
Meta *types.ReportMeta
// Sources POST parameter
//
// Report sources definition
Sources types.ReportDataSourceSet
// Projections POST parameter
//
// Report projections definition
Projections types.ReportProjectionSet
// Labels POST parameter
//
// Labels
Labels map[string]string
}
ReportRead struct {
// ReportID PATH parameter
//
// Report ID
ReportID uint64 `json:",string"`
}
ReportDelete struct {
// ReportID PATH parameter
//
// Report ID
ReportID uint64 `json:",string"`
}
ReportUndelete struct {
// ReportID PATH parameter
//
// Report ID
ReportID uint64 `json:",string"`
}
ReportRunFresh struct {
// Sources POST parameter
//
// Report steps definition
Sources types.ReportDataSourceSet
// Steps POST parameter
//
// Report steps definition
Steps report.StepDefinitionSet
// Frames POST parameter
//
// Report data frame definitions
Frames report.FrameDefinitionSet
}
ReportRun struct {
// ReportID PATH parameter
//
// Report ID
ReportID uint64 `json:",string"`
// Frames POST parameter
//
// Report data frame definitions
Frames report.FrameDefinitionSet
}
)
// NewReportList request
func NewReportList() *ReportList {
return &ReportList{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) Auditable() map[string]interface{} {
return map[string]interface{}{
"handle": r.Handle,
"deleted": r.Deleted,
"labels": r.Labels,
"limit": r.Limit,
"pageCursor": r.PageCursor,
"sort": r.Sort,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) GetHandle() string {
return r.Handle
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) GetDeleted() uint {
return r.Deleted
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) GetLabels() map[string]string {
return r.Labels
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) GetLimit() uint {
return r.Limit
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) GetPageCursor() string {
return r.PageCursor
}
// Auditable returns all auditable/loggable parameters
func (r ReportList) GetSort() string {
return r.Sort
}
// Fill processes request and fills internal variables
func (r *ReportList) Fill(req *http.Request) (err error) {
{
// GET params
tmp := req.URL.Query()
if val, ok := tmp["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["deleted"]; ok && len(val) > 0 {
r.Deleted, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := tmp["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
if val, ok := tmp["limit"]; ok && len(val) > 0 {
r.Limit, err = payload.ParseUint(val[0]), nil
if err != nil {
return err
}
}
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
r.PageCursor, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := tmp["sort"]; ok && len(val) > 0 {
r.Sort, err = val[0], nil
if err != nil {
return err
}
}
}
return err
}
// NewReportCreate request
func NewReportCreate() *ReportCreate {
return &ReportCreate{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) Auditable() map[string]interface{} {
return map[string]interface{}{
"handle": r.Handle,
"meta": r.Meta,
"sources": r.Sources,
"projections": r.Projections,
"labels": r.Labels,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetHandle() string {
return r.Handle
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetMeta() *types.ReportMeta {
return r.Meta
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetSources() types.ReportDataSourceSet {
return r.Sources
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetProjections() types.ReportProjectionSet {
return r.Projections
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *ReportCreate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["meta[]"]; ok {
r.Meta, err = types.ParseReportMeta(val)
if err != nil {
return err
}
} else if val, ok := req.Form["meta"]; ok {
r.Meta, err = types.ParseReportMeta(val)
if err != nil {
return err
}
}
//if val, ok := req.Form["sources[]"]; ok && len(val) > 0 {
// r.Sources, err = types.ReportDataSourceSet(val), nil
// if err != nil {
// return err
// }
//}
//if val, ok := req.Form["projections[]"]; ok && len(val) > 0 {
// r.Projections, err = types.ReportProjectionSet(val), nil
// if err != nil {
// return err
// }
//}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
return err
}
// NewReportUpdate request
func NewReportUpdate() *ReportUpdate {
return &ReportUpdate{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) Auditable() map[string]interface{} {
return map[string]interface{}{
"reportID": r.ReportID,
"handle": r.Handle,
"meta": r.Meta,
"sources": r.Sources,
"projections": r.Projections,
"labels": r.Labels,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetReportID() uint64 {
return r.ReportID
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetHandle() string {
return r.Handle
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetMeta() *types.ReportMeta {
return r.Meta
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetSources() types.ReportDataSourceSet {
return r.Sources
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetProjections() types.ReportProjectionSet {
return r.Projections
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetLabels() map[string]string {
return r.Labels
}
// Fill processes request and fills internal variables
func (r *ReportUpdate) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
if val, ok := req.Form["handle"]; ok && len(val) > 0 {
r.Handle, err = val[0], nil
if err != nil {
return err
}
}
if val, ok := req.Form["meta[]"]; ok {
r.Meta, err = types.ParseReportMeta(val)
if err != nil {
return err
}
} else if val, ok := req.Form["meta"]; ok {
r.Meta, err = types.ParseReportMeta(val)
if err != nil {
return err
}
}
//if val, ok := req.Form["sources[]"]; ok && len(val) > 0 {
// r.Sources, err = types.ReportDataSourceSet(val), nil
// if err != nil {
// return err
// }
//}
//if val, ok := req.Form["projections[]"]; ok && len(val) > 0 {
// r.Projections, err = types.ReportProjectionSet(val), nil
// if err != nil {
// return err
// }
//}
if val, ok := req.Form["labels[]"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
} else if val, ok := req.Form["labels"]; ok {
r.Labels, err = label.ParseStrings(val)
if err != nil {
return err
}
}
}
{
var val string
// path params
val = chi.URLParam(req, "reportID")
r.ReportID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewReportRead request
func NewReportRead() *ReportRead {
return &ReportRead{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportRead) Auditable() map[string]interface{} {
return map[string]interface{}{
"reportID": r.ReportID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportRead) GetReportID() uint64 {
return r.ReportID
}
// Fill processes request and fills internal variables
func (r *ReportRead) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "reportID")
r.ReportID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewReportDelete request
func NewReportDelete() *ReportDelete {
return &ReportDelete{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportDelete) Auditable() map[string]interface{} {
return map[string]interface{}{
"reportID": r.ReportID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportDelete) GetReportID() uint64 {
return r.ReportID
}
// Fill processes request and fills internal variables
func (r *ReportDelete) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "reportID")
r.ReportID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewReportUndelete request
func NewReportUndelete() *ReportUndelete {
return &ReportUndelete{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportUndelete) Auditable() map[string]interface{} {
return map[string]interface{}{
"reportID": r.ReportID,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportUndelete) GetReportID() uint64 {
return r.ReportID
}
// Fill processes request and fills internal variables
func (r *ReportUndelete) Fill(req *http.Request) (err error) {
{
var val string
// path params
val = chi.URLParam(req, "reportID")
r.ReportID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}
// NewReportRunFresh request
func NewReportRunFresh() *ReportRunFresh {
return &ReportRunFresh{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportRunFresh) Auditable() map[string]interface{} {
return map[string]interface{}{
"sources": r.Sources,
"steps": r.Steps,
"frames": r.Frames,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportRunFresh) GetSources() types.ReportDataSourceSet {
return r.Sources
}
// Auditable returns all auditable/loggable parameters
func (r ReportRunFresh) GetSteps() report.StepDefinitionSet {
return r.Steps
}
// Auditable returns all auditable/loggable parameters
func (r ReportRunFresh) GetFrames() report.FrameDefinitionSet {
return r.Frames
}
// Fill processes request and fills internal variables
func (r *ReportRunFresh) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
//if val, ok := req.Form["sources[]"]; ok && len(val) > 0 {
// r.Sources, err = types.ReportDataSourceSet(val), nil
// if err != nil {
// return err
// }
//}
//if val, ok := req.Form["steps[]"]; ok && len(val) > 0 {
// r.Steps, err = report.StepDefinitionSet(val), nil
// if err != nil {
// return err
// }
//}
//if val, ok := req.Form["frames[]"]; ok && len(val) > 0 {
// r.Frames, err = report.FrameDefinitionSet(val), nil
// if err != nil {
// return err
// }
//}
}
return err
}
// NewReportRun request
func NewReportRun() *ReportRun {
return &ReportRun{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportRun) Auditable() map[string]interface{} {
return map[string]interface{}{
"reportID": r.ReportID,
"frames": r.Frames,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportRun) GetReportID() uint64 {
return r.ReportID
}
// Auditable returns all auditable/loggable parameters
func (r ReportRun) GetFrames() report.FrameDefinitionSet {
return r.Frames
}
// Fill processes request and fills internal variables
func (r *ReportRun) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return fmt.Errorf("error parsing http request body: %w", err)
}
}
{
if err = req.ParseForm(); err != nil {
return err
}
// POST params
//if val, ok := req.Form["frames[]"]; ok && len(val) > 0 {
// r.Frames, err = report.FrameDefinitionSet(val), nil
// if err != nil {
// return err
// }
//}
}
{
var val string
// path params
val = chi.URLParam(req, "reportID")
r.ReportID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}
}
return err
}

View File

@@ -32,6 +32,7 @@ func MountRoutes(r chi.Router) {
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewApplication(Application{}.New()).MountRoutes(r)
handlers.NewTemplate(Template{}.New()).MountRoutes(r)
handlers.NewReport(Report{}.New()).MountRoutes(r)
handlers.NewSettings(Settings{}.New()).MountRoutes(r)
handlers.NewStats(Stats{}.New()).MountRoutes(r)
handlers.NewReminder(Reminder{}.New()).MountRoutes(r)

View File

@@ -11,6 +11,7 @@ package service
// - system.apigw-route.yaml
// - system.application.yaml
// - system.auth-client.yaml
// - system.report.yaml
// - system.role.yaml
// - system.template.yaml
// - system.user.yaml
@@ -128,6 +129,26 @@ func (svc accessControl) List() (out []map[string]string) {
"any": types.AuthClientRbacResource(0),
"op": "authorize",
},
{
"type": types.ReportResourceType,
"any": types.ReportRbacResource(0),
"op": "read",
},
{
"type": types.ReportResourceType,
"any": types.ReportRbacResource(0),
"op": "update",
},
{
"type": types.ReportResourceType,
"any": types.ReportRbacResource(0),
"op": "delete",
},
{
"type": types.ReportResourceType,
"any": types.ReportRbacResource(0),
"op": "run",
},
{
"type": types.RoleResourceType,
"any": types.RoleRbacResource(0),
@@ -288,6 +309,16 @@ func (svc accessControl) List() (out []map[string]string) {
"any": types.ComponentRbacResource(),
"op": "templates.search",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "report.create",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
"op": "reports.search",
},
{
"type": types.ComponentResourceType,
"any": types.ComponentRbacResource(),
@@ -476,6 +507,34 @@ func (svc accessControl) CanAuthorizeAuthClient(ctx context.Context, r *types.Au
return svc.can(ctx, "authorize", r)
}
// CanReadReport checks if current user can read report
//
// This function is auto-generated
func (svc accessControl) CanReadReport(ctx context.Context, r *types.Report) bool {
return svc.can(ctx, "read", r)
}
// CanUpdateReport checks if current user can update report
//
// This function is auto-generated
func (svc accessControl) CanUpdateReport(ctx context.Context, r *types.Report) bool {
return svc.can(ctx, "update", r)
}
// CanDeleteReport checks if current user can delete report
//
// This function is auto-generated
func (svc accessControl) CanDeleteReport(ctx context.Context, r *types.Report) bool {
return svc.can(ctx, "delete", r)
}
// CanRunReport checks if current user can run report
//
// This function is auto-generated
func (svc accessControl) CanRunReport(ctx context.Context, r *types.Report) bool {
return svc.can(ctx, "run", r)
}
// CanReadRole checks if current user can read role
//
// This function is auto-generated
@@ -700,6 +759,20 @@ func (svc accessControl) CanSearchTemplates(ctx context.Context) bool {
return svc.can(ctx, "templates.search", &types.Component{})
}
// CanCreateReport checks if current user can create report
//
// This function is auto-generated
func (svc accessControl) CanCreateReport(ctx context.Context) bool {
return svc.can(ctx, "report.create", &types.Component{})
}
// CanSearchReports checks if current user can list, search or filter reports
//
// This function is auto-generated
func (svc accessControl) CanSearchReports(ctx context.Context) bool {
return svc.can(ctx, "reports.search", &types.Component{})
}
// CanAssignReminder checks if current user can assign reminders
//
// This function is auto-generated
@@ -762,6 +835,8 @@ func rbacResourceValidator(r string, oo ...string) error {
return rbacApplicationResourceValidator(r, oo...)
case types.AuthClientResourceType:
return rbacAuthClientResourceValidator(r, oo...)
case types.ReportResourceType:
return rbacReportResourceValidator(r, oo...)
case types.RoleResourceType:
return rbacRoleResourceValidator(r, oo...)
case types.TemplateResourceType:
@@ -805,6 +880,13 @@ func rbacResourceOperations(r string) map[string]bool {
"delete": true,
"authorize": true,
}
case types.ReportResourceType:
return map[string]bool{
"read": true,
"update": true,
"delete": true,
"run": true,
}
case types.RoleResourceType:
return map[string]bool{
"read": true,
@@ -848,6 +930,8 @@ func rbacResourceOperations(r string) map[string]bool {
"application.flag.global": true,
"template.create": true,
"templates.search": true,
"report.create": true,
"reports.search": true,
"reminder.assign": true,
"queue.create": true,
"queues.search": true,
@@ -1037,6 +1121,57 @@ func rbacAuthClientResourceValidator(r string, oo ...string) error {
return nil
}
// rbacReportResourceValidator checks validity of rbac resource and operations
//
// Can be called without operations to check for validity of resource string only
//
// This function is auto-generated
func rbacReportResourceValidator(r string, oo ...string) error {
defOps := rbacResourceOperations(r)
for _, o := range oo {
if !defOps[o] {
return fmt.Errorf("invalid operation '%s' for system Report resource", o)
}
}
if !strings.HasPrefix(r, types.ReportResourceType) {
// expecting resource to always include path
return fmt.Errorf("invalid resource type")
}
const sep = "/"
var (
specIdUsed = true
pp = strings.Split(strings.Trim(r[len(types.ReportResourceType):], sep), sep)
prc = []string{
"ID",
}
)
if len(pp) != len(prc) {
return fmt.Errorf("invalid resource path structure")
}
for i, p := range pp {
if p == "*" {
if !specIdUsed {
return fmt.Errorf("invalid resource path wildcard level (%d) for Report", i)
}
specIdUsed = false
continue
}
specIdUsed = true
if _, err := cast.ToUint64E(p); err != nil {
return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p)
}
}
return nil
}
// rbacRoleResourceValidator checks validity of rbac resource and operations
//
// Can be called without operations to check for validity of resource string only

342
system/service/report.go Normal file
View File

@@ -0,0 +1,342 @@
package service
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/label"
rep "github.com/cortezaproject/corteza-server/pkg/report"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
report struct {
ac reportAccessController
eventbus eventDispatcher
actionlog actionlog.Recorder
store store.Storer
}
reportAccessController interface {
CanSearchReports(context.Context) bool
CanCreateReport(context.Context) bool
CanReadReport(context.Context, *types.Report) bool
CanUpdateReport(context.Context, *types.Report) bool
CanDeleteReport(context.Context, *types.Report) bool
CanRunReport(context.Context, *types.Report) bool
}
)
var (
reporters = make(map[string]rep.DatasourceProvider)
)
// Report is a default report service initializer
func Report(s store.Storer, ac reportAccessController, al actionlog.Recorder, eb eventDispatcher) *report {
return &report{store: s, ac: ac, actionlog: al, eventbus: eb}
}
func (svc *report) RegisterReporter(key string, r rep.DatasourceProvider) {
reporters[key] = r
}
func (svc *report) LookupByID(ctx context.Context, ID uint64) (report *types.Report, err error) {
var (
aaProps = &reportActionProps{report: &types.Report{ID: ID}}
)
err = func() error {
if ID == 0 {
return ReportErrInvalidID()
}
if report, err = store.LookupReportByID(ctx, svc.store, ID); err != nil {
return ReportErrInvalidID().Wrap(err)
}
if !svc.ac.CanReadReport(ctx, report) {
return ReportErrNotAllowedToRead()
}
return nil
}()
return report, svc.recordAction(ctx, aaProps, ReportActionLookup, err)
}
func (svc *report) Search(ctx context.Context, rf types.ReportFilter) (rr types.ReportSet, f types.ReportFilter, err error) {
var (
aaProps = &reportActionProps{filter: &rf}
)
// For each fetched item, store backend will check if it is valid or not
rf.Check = func(res *types.Report) (bool, error) {
if !svc.ac.CanReadReport(ctx, res) {
return false, nil
}
return true, nil
}
err = func() error {
if !svc.ac.CanSearchReports(ctx) {
return ReportErrNotAllowedToSearch()
}
if len(rf.Labels) > 0 {
rf.LabeledIDs, err = label.Search(
ctx,
svc.store,
types.Report{}.LabelResourceKind(),
rf.Labels,
)
if err != nil {
return err
}
// labels specified but no labeled resources found
if len(rf.LabeledIDs) == 0 {
return nil
}
}
if rr, f, err = store.SearchReports(ctx, svc.store, rf); err != nil {
return err
}
if err = label.Load(ctx, svc.store, toLabeledReports(rr)...); err != nil {
return err
}
return nil
}()
return rr, f, svc.recordAction(ctx, aaProps, ReportActionSearch, err)
}
func (svc *report) Create(ctx context.Context, new *types.Report) (report *types.Report, err error) {
var (
aaProps = &reportActionProps{new: new}
)
err = func() (err error) {
if !svc.ac.CanCreateReport(ctx) {
return ReportErrNotAllowedToCreate()
}
// if err = svc.eventbus.WaitFor(ctx, event.ReportBeforeCreate(new, nil)); err != nil {
// return
// }
// Set new values after beforeCreate events are emitted
new.ID = nextID()
new.CreatedAt = *now()
if new.Meta == nil {
new.Meta = &types.ReportMeta{}
}
if err = store.CreateReport(ctx, svc.store, new); err != nil {
return
}
if err = label.Create(ctx, svc.store, new); err != nil {
return
}
report = new
// _ = svc.eventbus.WaitFor(ctx, event.ReportAfterCreate(new, nil))
return nil
}()
return report, svc.recordAction(ctx, aaProps, ReportActionCreate, err)
}
func (svc *report) Update(ctx context.Context, upd *types.Report) (report *types.Report, err error) {
var (
aaProps = &reportActionProps{update: upd}
)
err = func() (err error) {
if upd.ID == 0 {
return ReportErrInvalidID()
}
if report, err = store.LookupReportByID(ctx, svc.store, upd.ID); err != nil {
return
}
aaProps.setReport(report)
if !svc.ac.CanUpdateReport(ctx, report) {
return ReportErrNotAllowedToUpdate()
}
// if err = svc.eventbus.WaitFor(ctx, event.ReportBeforeUpdate(upd, report)); err != nil {
// return
// }
// Assign changed values after afterUpdate events are emitted
report.Handle = upd.Handle
report.Sources = upd.Sources
report.UpdatedAt = now()
if upd.Meta != nil {
report.Meta = upd.Meta
}
if err = store.UpdateReport(ctx, svc.store, report); err != nil {
return err
}
if label.Changed(report.Labels, upd.Labels) {
if err = label.Update(ctx, svc.store, upd); err != nil {
return
}
report.Labels = upd.Labels
}
// _ = svc.eventbus.WaitFor(ctx, event.ReportAfterUpdate(upd, report))
return nil
}()
return report, svc.recordAction(ctx, aaProps, ReportActionUpdate, err)
}
func (svc *report) Delete(ctx context.Context, ID uint64) (err error) {
var (
aaProps = &reportActionProps{}
report *types.Report
)
err = func() (err error) {
if ID == 0 {
return ReportErrInvalidID()
}
if report, err = store.LookupReportByID(ctx, svc.store, ID); err != nil {
return
}
aaProps.setReport(report)
if !svc.ac.CanDeleteReport(ctx, report) {
return ReportErrNotAllowedToDelete()
}
// if err = svc.eventbus.WaitFor(ctx, event.ReportBeforeDelete(nil, report)); err != nil {
// return
// }
report.DeletedAt = now()
if err = store.UpdateReport(ctx, svc.store, report); err != nil {
return
}
// _ = svc.eventbus.WaitFor(ctx, event.ReportAfterDelete(nil, report))
return nil
}()
return svc.recordAction(ctx, aaProps, ReportActionDelete, err)
}
func (svc *report) Undelete(ctx context.Context, ID uint64) (err error) {
var (
aaProps = &reportActionProps{}
report *types.Report
)
err = func() (err error) {
if ID == 0 {
return ReportErrInvalidID()
}
if report, err = store.LookupReportByID(ctx, svc.store, ID); err != nil {
return
}
aaProps.setReport(report)
if !svc.ac.CanDeleteReport(ctx, report) {
return ReportErrNotAllowedToUndelete()
}
// if err = svc.eventbus.WaitFor(ctx, event.ReportBeforeUndelete(nil, app)); err != nil {
// return
// }
report.DeletedAt = nil
if err = store.UpdateReport(ctx, svc.store, report); err != nil {
return
}
// _ = svc.eventbus.WaitFor(ctx, event.ReportAfterUndelete(nil, app))
return nil
}()
return svc.recordAction(ctx, aaProps, ReportActionUndelete, err)
}
func (svc *report) Run(ctx context.Context, ID uint64, dd rep.FrameDefinitionSet) (rr interface{}, err error) {
// @todo follow the RunFresh definition here
return nil, nil
}
func (svc *report) RunFresh(ctx context.Context, src types.ReportDataSourceSet, st rep.StepDefinitionSet, dd rep.FrameDefinitionSet) (out []*rep.Frame, err error) {
var (
aaProps = &reportActionProps{}
)
out = make([]*rep.Frame, 0, 4)
err = func() (err error) {
// if err = svc.eventbus.WaitFor(ctx, event.ReportBeforeUpdate(upd, report)); err != nil {
// return
// }
ss := src.ModelSteps()
ss = append(ss, st...)
// Model the report
model, err := rep.Model(ctx, reporters, ss...)
if err != nil {
return
}
err = model.Run(ctx)
if err != nil {
return
}
ff, err := model.Load(ctx, dd...)
if err != nil {
return err
}
out = append(out, ff...)
// _ = svc.eventbus.WaitFor(ctx, event.ReportAfterUpdate(upd, report))
// return nil
return nil
}()
return out, svc.recordAction(ctx, aaProps, ReportActionRun, err)
}
// toLabeledReports converts to []label.LabeledResource
//
// This function is auto-generated.
func toLabeledReports(set []*types.Report) []label.LabeledResource {
if len(set) == 0 {
return nil
}
ll := make([]label.LabeledResource, len(set))
for i := range set {
ll[i] = set[i]
}
return ll
}

780
system/service/report_actions.gen.go generated Normal file
View File

@@ -0,0 +1,780 @@
package service
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// system/service/report_actions.yaml
import (
"context"
"fmt"
"github.com/cortezaproject/corteza-server/pkg/actionlog"
"github.com/cortezaproject/corteza-server/pkg/errors"
"github.com/cortezaproject/corteza-server/system/types"
"strings"
"time"
)
type (
reportActionProps struct {
report *types.Report
new *types.Report
update *types.Report
filter *types.ReportFilter
}
reportAction struct {
timestamp time.Time
resource string
action string
log string
severity actionlog.Severity
// prefix for error when action fails
errorMessage string
props *reportActionProps
}
reportLogMetaKey struct{}
reportPropsMetaKey struct{}
)
var (
// just a placeholder to cover template cases w/o fmt package use
_ = fmt.Println
)
// *********************************************************************************************************************
// *********************************************************************************************************************
// Props methods
// setReport updates reportActionProps's report
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *reportActionProps) setReport(report *types.Report) *reportActionProps {
p.report = report
return p
}
// setNew updates reportActionProps's new
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *reportActionProps) setNew(new *types.Report) *reportActionProps {
p.new = new
return p
}
// setUpdate updates reportActionProps's update
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *reportActionProps) setUpdate(update *types.Report) *reportActionProps {
p.update = update
return p
}
// setFilter updates reportActionProps's filter
//
// Allows method chaining
//
// This function is auto-generated.
//
func (p *reportActionProps) setFilter(filter *types.ReportFilter) *reportActionProps {
p.filter = filter
return p
}
// Serialize converts reportActionProps to actionlog.Meta
//
// This function is auto-generated.
//
func (p reportActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
)
if p.report != nil {
m.Set("report.handle", p.report.Handle, true)
m.Set("report.ID", p.report.ID, true)
}
if p.new != nil {
m.Set("new.handle", p.new.Handle, true)
m.Set("new.ID", p.new.ID, true)
}
if p.update != nil {
m.Set("update.handle", p.update.Handle, true)
m.Set("update.ID", p.update.ID, true)
}
if p.filter != nil {
m.Set("filter.handle", p.filter.Handle, true)
m.Set("filter.deleted", p.filter.Deleted, true)
m.Set("filter.sort", p.filter.Sort, true)
}
return m
}
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
//
func (p reportActionProps) Format(in string, err error) string {
var (
pairs = []string{"{err}"}
// first non-empty string
fns = func(ii ...interface{}) string {
for _, i := range ii {
if s := fmt.Sprintf("%v", i); len(s) > 0 {
return s
}
}
return ""
}
)
if err != nil {
pairs = append(pairs, err.Error())
} else {
pairs = append(pairs, "nil")
}
if p.report != nil {
// replacement for "{report}" (in order how fields are defined)
pairs = append(
pairs,
"{report}",
fns(
p.report.Handle,
p.report.ID,
),
)
pairs = append(pairs, "{report.handle}", fns(p.report.Handle))
pairs = append(pairs, "{report.ID}", fns(p.report.ID))
}
if p.new != nil {
// replacement for "{new}" (in order how fields are defined)
pairs = append(
pairs,
"{new}",
fns(
p.new.Handle,
p.new.ID,
),
)
pairs = append(pairs, "{new.handle}", fns(p.new.Handle))
pairs = append(pairs, "{new.ID}", fns(p.new.ID))
}
if p.update != nil {
// replacement for "{update}" (in order how fields are defined)
pairs = append(
pairs,
"{update}",
fns(
p.update.Handle,
p.update.ID,
),
)
pairs = append(pairs, "{update.handle}", fns(p.update.Handle))
pairs = append(pairs, "{update.ID}", fns(p.update.ID))
}
if p.filter != nil {
// replacement for "{filter}" (in order how fields are defined)
pairs = append(
pairs,
"{filter}",
fns(
p.filter.Handle,
p.filter.Deleted,
p.filter.Sort,
),
)
pairs = append(pairs, "{filter.handle}", fns(p.filter.Handle))
pairs = append(pairs, "{filter.deleted}", fns(p.filter.Deleted))
pairs = append(pairs, "{filter.sort}", fns(p.filter.Sort))
}
return strings.NewReplacer(pairs...).Replace(in)
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action methods
// String returns loggable description as string
//
// This function is auto-generated.
//
func (a *reportAction) String() string {
var props = &reportActionProps{}
if a.props != nil {
props = a.props
}
return props.Format(a.log, nil)
}
func (e *reportAction) ToAction() *actionlog.Action {
return &actionlog.Action{
Resource: e.resource,
Action: e.action,
Severity: e.severity,
Description: e.String(),
Meta: e.props.Serialize(),
}
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Action constructors
// ReportActionSearch returns "system:report.search" action
//
// This function is auto-generated.
//
func ReportActionSearch(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "search",
log: "searched for reports",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ReportActionLookup returns "system:report.lookup" action
//
// This function is auto-generated.
//
func ReportActionLookup(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "lookup",
log: "looked-up for a {report}",
severity: actionlog.Info,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ReportActionCreate returns "system:report.create" action
//
// This function is auto-generated.
//
func ReportActionCreate(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "create",
log: "created {report}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ReportActionUpdate returns "system:report.update" action
//
// This function is auto-generated.
//
func ReportActionUpdate(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "update",
log: "updated {report}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ReportActionDelete returns "system:report.delete" action
//
// This function is auto-generated.
//
func ReportActionDelete(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "delete",
log: "deleted {report}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ReportActionUndelete returns "system:report.undelete" action
//
// This function is auto-generated.
//
func ReportActionUndelete(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "undelete",
log: "undeleted {report}",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// ReportActionRun returns "system:report.run" action
//
// This function is auto-generated.
//
func ReportActionRun(props ...*reportActionProps) *reportAction {
a := &reportAction{
timestamp: time.Now(),
resource: "system:report",
action: "run",
log: "report ran",
severity: actionlog.Notice,
}
if len(props) > 0 {
a.props = props[0]
}
return a
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// Error constructors
// ReportErrGeneric returns "system:report.generic" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrGeneric(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("failed to complete request due to internal error", nil),
errors.Meta("type", "generic"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "{err}"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotFound returns "system:report.notFound" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotFound(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("report not found", nil),
errors.Meta("type", "notFound"),
errors.Meta("resource", "system:report"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrInvalidID returns "system:report.invalidID" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrInvalidID(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("invalid ID", nil),
errors.Meta("type", "invalidID"),
errors.Meta("resource", "system:report"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToRead returns "system:report.notAllowedToRead" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToRead(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to read this report", nil),
errors.Meta("type", "notAllowedToRead"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to read {report}; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToSearch returns "system:report.notAllowedToSearch" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToSearch(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to list or search reports", nil),
errors.Meta("type", "notAllowedToSearch"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to search for reports; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToListReports returns "system:report.notAllowedToListReports" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToListReports(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to list reports", nil),
errors.Meta("type", "notAllowedToListReports"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to list report; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToCreate returns "system:report.notAllowedToCreate" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToCreate(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to create reports", nil),
errors.Meta("type", "notAllowedToCreate"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to create report; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToUpdate returns "system:report.notAllowedToUpdate" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToUpdate(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to update this report", nil),
errors.Meta("type", "notAllowedToUpdate"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to update {report}; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToDelete returns "system:report.notAllowedToDelete" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToDelete(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to delete this report", nil),
errors.Meta("type", "notAllowedToDelete"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to delete {report}; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToUndelete returns "system:report.notAllowedToUndelete" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToUndelete(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to undelete this report", nil),
errors.Meta("type", "notAllowedToUndelete"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to undelete {report}; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// ReportErrNotAllowedToRun returns "system:report.notAllowedToRun" as *errors.Error
//
//
// This function is auto-generated.
//
func ReportErrNotAllowedToRun(mm ...*reportActionProps) *errors.Error {
var p = &reportActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("not allowed to run this report", nil),
errors.Meta("type", "notAllowedToRun"),
errors.Meta("resource", "system:report"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(reportLogMetaKey{}, "failed to run {report}; insufficient permissions"),
errors.Meta(reportPropsMetaKey{}, p),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// *********************************************************************************************************************
// *********************************************************************************************************************
// recordAction is a service helper function wraps function that can return error
//
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
//
func (svc report) recordAction(ctx context.Context, props *reportActionProps, actionFn func(...*reportActionProps) *reportAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
return err
} else if err == nil {
// action completed w/o error, record it
svc.actionlog.Record(ctx, actionFn(props).ToAction())
return nil
}
a := actionFn(props).ToAction()
// Extracting error information and recording it as action
a.Error = err.Error()
switch c := err.(type) {
case *errors.Error:
m := c.Meta()
a.Error = err.Error()
a.Severity = actionlog.Severity(m.AsInt("severity"))
a.Description = props.Format(m.AsString(reportLogMetaKey{}), err)
if p, has := m[reportPropsMetaKey{}]; has {
a.Meta = p.(*reportActionProps).Serialize()
}
svc.actionlog.Record(ctx, a)
default:
svc.actionlog.Record(ctx, a)
}
// Original error is passed on
return err
}

View File

@@ -0,0 +1,92 @@
# List of loggable service actions
resource: system:report
service: report
# Default sensitivity for actions
defaultActionSeverity: notice
# default severity for errors
defaultErrorSeverity: error
import:
- github.com/cortezaproject/corteza-server/system/types
props:
- name: report
type: "*types.Report"
fields: [ handle, ID ]
- name: new
type: "*types.Report"
fields: [ handle, ID ]
- name: update
type: "*types.Report"
fields: [ handle, ID ]
- name: filter
type: "*types.ReportFilter"
fields: [ handle, deleted, sort ]
actions:
- action: search
log: "searched for reports"
severity: info
- action: lookup
log: "looked-up for a {report}"
severity: info
- action: create
log: "created {report}"
- action: update
log: "updated {report}"
- action: delete
log: "deleted {report}"
- action: undelete
log: "undeleted {report}"
- action: run
log: report ran
errors:
- error: notFound
message: "report not found"
severity: warning
- error: invalidID
message: "invalid ID"
severity: warning
- error: notAllowedToRead
message: "not allowed to read this report"
log: "failed to read {report}; insufficient permissions"
- error: notAllowedToSearch
message: "not allowed to list or search reports"
log: "failed to search for reports; insufficient permissions"
- error: notAllowedToListReports
message: "not allowed to list reports"
log: "failed to list report; insufficient permissions"
- error: notAllowedToCreate
message: "not allowed to create reports"
log: "failed to create report; insufficient permissions"
- error: notAllowedToUpdate
message: "not allowed to update this report"
log: "failed to update {report}; insufficient permissions"
- error: notAllowedToDelete
message: "not allowed to delete this report"
log: "failed to delete {report}; insufficient permissions"
- error: notAllowedToUndelete
message: "not allowed to undelete this report"
log: "failed to undelete {report}; insufficient permissions"
- error: notAllowedToRun
message: "not allowed to run this report"
log: "failed to run {report}; insufficient permissions"

View File

@@ -77,6 +77,7 @@ var (
DefaultQueue *queue
DefaultApigwRoute *apigwRoute
DefaultApigwFilter *apigwFilter
DefaultReport *report
DefaultStatistics *statistics
@@ -159,6 +160,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
hcd.Add(objstore.Healthcheck(DefaultObjectStore), "ObjectStore/System")
DefaultRenderer = Renderer(c.Template)
DefaultReport = Report(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
DefaultAuthNotification = AuthNotification(CurrentSettings, DefaultRenderer, c.Auth)
DefaultAuth = Auth()
DefaultAuthClient = AuthClient(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service(), c.Auth)

View File

@@ -32,6 +32,11 @@ func ParseAuthClientMeta(ss []string) (p *AuthClientMeta, err error) {
return p, parseStringsInput(ss, &p)
}
func ParseReportMeta(ss []string) (p *ReportMeta, err error) {
p = &ReportMeta{}
return p, parseStringsInput(ss, &p)
}
func ParseAuthClientSecurity(ss []string) (p *AuthClientSecurity, err error) {
p = &AuthClientSecurity{}
return p, parseStringsInput(ss, &p)

View File

@@ -11,6 +11,7 @@ package types
// - system.apigw-route.yaml
// - system.application.yaml
// - system.auth-client.yaml
// - system.report.yaml
// - system.role.yaml
// - system.template.yaml
// - system.user.yaml
@@ -33,6 +34,7 @@ const (
ApigwRouteResourceType = "corteza::system:apigw-route"
ApplicationResourceType = "corteza::system:application"
AuthClientResourceType = "corteza::system:auth-client"
ReportResourceType = "corteza::system:report"
RoleResourceType = "corteza::system:role"
TemplateResourceType = "corteza::system:template"
UserResourceType = "corteza::system:user"
@@ -163,6 +165,37 @@ func AuthClientRbacResourceTpl() string {
return "%s/%s"
}
// RbacResource returns string representation of RBAC resource for Report by calling ReportRbacResource fn
//
// RBAC resource is in the corteza::system:report/... format
//
// This function is auto-generated
func (r Report) RbacResource() string {
return ReportRbacResource(r.ID)
}
// ReportRbacResource returns string representation of RBAC resource for Report
//
// RBAC resource is in the corteza::system:report/... format
//
// This function is auto-generated
func ReportRbacResource(id uint64) string {
cpts := []interface{}{ReportResourceType}
if id != 0 {
cpts = append(cpts, strconv.FormatUint(id, 10))
} else {
cpts = append(cpts, "*")
}
return fmt.Sprintf(ReportRbacResourceTpl(), cpts...)
}
// @todo template
func ReportRbacResourceTpl() string {
return "%s/%s"
}
// RbacResource returns string representation of RBAC resource for Role by calling RoleRbacResource fn
//
// RBAC resource is in the corteza::system:role/... format

158
system/types/report.go Normal file
View File

@@ -0,0 +1,158 @@
package types
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/pkg/report"
)
type (
Report struct {
ID uint64 `json:"reportID,string"`
Handle string `json:"handle"`
Meta *ReportMeta `json:"meta,omitempty"`
Sources ReportDataSourceSet `json:"sources"`
Projections ReportProjectionSet `json:"projections"`
// Report labels
Labels map[string]string `json:"labels,omitempty"`
OwnedBy uint64 `json:"ownedBy"`
CreatedBy uint64 `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
UpdatedBy uint64 `json:"updatedBy,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
DeletedBy uint64 `json:"deletedBy,omitempty"`
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
ReportDataSource struct {
Name string `json:"name,omitempty"`
Groups []*ReportStepGroup
}
ReportDataSourceSet []*ReportDataSource
ReportStepGroup struct {
Name string `json:"name,omitempty"`
Steps report.StepDefinitionSet `json:"steps"`
}
ReportMeta struct {
Name string `json:"name"`
Description string `json:"description"`
}
ReportProjection struct {
Name string `json:"name"`
Key string `json:"key"`
Description string `json:"description"`
Kind string `json:"kind"`
Options map[string]interface{} `json:"options"`
}
ReportProjectionSet []*ReportProjection
ReportFilter struct {
ReportID []uint64 `json:"reportID"`
Handle string `json:"handle"`
Deleted filter.State `json:"deleted"`
LabeledIDs []uint64 `json:"-"`
Labels map[string]string `json:"labels,omitempty"`
// Check fn is called by store backend for each resource found function can
// modify the resource and return false if store should not return it
//
// Store then loads additional resources to satisfy the paging parameters
Check func(*Report) (bool, error) `json:"-"`
// Standard helpers for paging and sorting
filter.Sorting
filter.Paging
}
)
// @todo make better
func (ss ReportDataSourceSet) ModelSteps() report.StepDefinitionSet {
out := make(report.StepDefinitionSet, 0, 124)
for _, s := range ss {
for _, g := range s.Groups {
out = append(out, g.Steps...)
}
}
return out
}
// Store stuff
func (vv *ReportMeta) Scan(value interface{}) error {
//lint:ignore S1034 This typecast is intentional, we need to get []byte out of a []uint8
switch value.(type) {
case nil:
*vv = ReportMeta{}
case []uint8:
b := value.([]byte)
if err := json.Unmarshal(b, vv); err != nil {
return fmt.Errorf("cannot scan '%v' into ReportMeta: %w", string(b), err)
}
}
return nil
}
// Scan on ReportMeta gracefully handles conversion from NULL
func (vv *ReportMeta) Value() (driver.Value, error) {
if vv == nil {
return []byte("null"), nil
}
return json.Marshal(vv)
}
// Scan on ReportProjectionSet gracefully handles conversion from NULL
func (vv ReportProjectionSet) Value() (driver.Value, error) {
return json.Marshal(vv)
}
func (vv *ReportProjectionSet) Scan(value interface{}) error {
//lint:ignore S1034 This typecast is intentional, we need to get []byte out of a []uint8
switch value.(type) {
case nil:
*vv = ReportProjectionSet{}
case []uint8:
b := value.([]byte)
if err := json.Unmarshal(b, vv); err != nil {
return fmt.Errorf("cannot scan '%v' into ReportProjectionSet: %w", string(b), err)
}
}
return nil
}
// Scan on ReportDataSourceSet gracefully handles conversion from NULL
func (vv ReportDataSourceSet) Value() (driver.Value, error) {
return json.Marshal(vv)
}
func (vv *ReportDataSourceSet) Scan(value interface{}) error {
//lint:ignore S1034 This typecast is intentional, we need to get []byte out of a []uint8
switch value.(type) {
case nil:
*vv = ReportDataSourceSet{}
case []uint8:
b := value.([]byte)
if err := json.Unmarshal(b, vv); err != nil {
return fmt.Errorf("cannot scan '%v' into ReportDataSourceSet: %w", string(b), err)
}
}
return nil
}

View File

@@ -56,6 +56,30 @@ func (m AuthClient) LabelResourceID() uint64 {
return m.ID
}
// SetLabel adds new label to label map
func (m *Report) SetLabel(key string, value string) {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
m.Labels[key] = value
}
// GetLabels adds new label to label map
func (m Report) GetLabels() map[string]string {
return m.Labels
}
// GetLabels adds new label to label map
func (Report) LabelResourceKind() string {
return "report"
}
// GetLabels adds new label to label map
func (m Report) LabelResourceID() uint64 {
return m.ID
}
// SetLabel adds new label to label map
func (m *Role) SetLabel(key string, value string) {
if m.Labels == nil {

View File

@@ -60,6 +60,11 @@ type (
// This type is auto-generated.
ReminderSet []*Reminder
// ReportSet slice of Report
//
// This type is auto-generated.
ReportSet []*Report
// RoleSet slice of Role
//
// This type is auto-generated.
@@ -594,6 +599,62 @@ func (set ReminderSet) IDs() (IDs []uint64) {
return
}
// Walk iterates through every slice item and calls w(Report) err
//
// This function is auto-generated.
func (set ReportSet) Walk(w func(*Report) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(Report) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set ReportSet) Filter(f func(*Report) (bool, error)) (out ReportSet, err error) {
var ok bool
out = ReportSet{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
} else if ok {
out = append(out, set[i])
}
}
return
}
// FindByID finds items from slice by its ID property
//
// This function is auto-generated.
func (set ReportSet) FindByID(ID uint64) *Report {
for i := range set {
if set[i].ID == ID {
return set[i]
}
}
return nil
}
// IDs returns a slice of uint64s from all items in the set
//
// This function is auto-generated.
func (set ReportSet) IDs() (IDs []uint64) {
IDs = make([]uint64, len(set))
for i := range set {
IDs[i] = set[i].ID
}
return
}
// Walk iterates through every slice item and calls w(Role) err
//
// This function is auto-generated.

View File

@@ -846,6 +846,96 @@ func TestReminderSetIDs(t *testing.T) {
}
}
func TestReportSetWalk(t *testing.T) {
var (
value = make(ReportSet, 3)
req = require.New(t)
)
// check walk with no errors
{
err := value.Walk(func(*Report) error {
return nil
})
req.NoError(err)
}
// check walk with error
req.Error(value.Walk(func(*Report) error { return fmt.Errorf("walk error") }))
}
func TestReportSetFilter(t *testing.T) {
var (
value = make(ReportSet, 3)
req = require.New(t)
)
// filter nothing
{
set, err := value.Filter(func(*Report) (bool, error) {
return true, nil
})
req.NoError(err)
req.Equal(len(set), len(value))
}
// filter one item
{
found := false
set, err := value.Filter(func(*Report) (bool, error) {
if !found {
found = true
return found, nil
}
return false, nil
})
req.NoError(err)
req.Len(set, 1)
}
// filter error
{
_, err := value.Filter(func(*Report) (bool, error) {
return false, fmt.Errorf("filter error")
})
req.Error(err)
}
}
func TestReportSetIDs(t *testing.T) {
var (
value = make(ReportSet, 3)
req = require.New(t)
)
// construct objects
value[0] = new(Report)
value[1] = new(Report)
value[2] = new(Report)
// set ids
value[0].ID = 1
value[1].ID = 2
value[2].ID = 3
// Find existing
{
val := value.FindByID(2)
req.Equal(uint64(2), val.ID)
}
// Find non-existing
{
val := value.FindByID(4)
req.Nil(val)
}
// List IDs from set
{
val := value.IDs()
req.Equal(len(val), len(value))
}
}
func TestRoleSetWalk(t *testing.T) {
var (
value = make(RoleSet, 3)

View File

@@ -23,3 +23,5 @@ types:
labelResourceType: template
ApigwRoute: {}
ApigwFilter: {}
Report:
labelResourceType: report