Base reporting service definitions
This commit is contained in:
10
def/system.report.yaml
Normal file
10
def/system.report.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
rbac:
|
||||
operations:
|
||||
read:
|
||||
description: Read report
|
||||
update:
|
||||
description: Update report
|
||||
delete:
|
||||
description: Delete report
|
||||
run:
|
||||
description: Run report
|
||||
@@ -42,6 +42,11 @@ rbac:
|
||||
templates.search:
|
||||
description: List, search or filter templates
|
||||
|
||||
report.create:
|
||||
description: Create report
|
||||
reports.search:
|
||||
description: List, search or filter reports
|
||||
|
||||
reminder.assign:
|
||||
description: Assign reminders
|
||||
|
||||
|
||||
14
pkg/envoy/resource/rbac_references_system.gen.go
generated
14
pkg/envoy/resource/rbac_references_system.gen.go
generated
@@ -11,6 +11,7 @@ package resource
|
||||
// - system.apigw-route.yaml
|
||||
// - system.application.yaml
|
||||
// - system.auth-client.yaml
|
||||
// - system.report.yaml
|
||||
// - system.role.yaml
|
||||
// - system.template.yaml
|
||||
// - system.user.yaml
|
||||
@@ -72,6 +73,19 @@ func SystemAuthClientRbacReferences(authClient string) (res *Ref, pp []*Ref, err
|
||||
return
|
||||
}
|
||||
|
||||
// SystemReportRbacReferences generates RBAC references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
//
|
||||
// This function is auto-generated
|
||||
func SystemReportRbacReferences(report string) (res *Ref, pp []*Ref, err error) {
|
||||
if report != "*" {
|
||||
res = &Ref{ResourceType: types.ReportResourceType, Identifiers: MakeIdentifiers(report)}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SystemRoleRbacReferences generates RBAC references
|
||||
//
|
||||
// Resources with "envoy: false" are skipped
|
||||
|
||||
11
pkg/envoy/resource/rbac_rules_parse.gen.go
generated
11
pkg/envoy/resource/rbac_rules_parse.gen.go
generated
@@ -20,6 +20,7 @@ package resource
|
||||
// - system.apigw-route.yaml
|
||||
// - system.application.yaml
|
||||
// - system.auth-client.yaml
|
||||
// - system.report.yaml
|
||||
// - system.role.yaml
|
||||
// - system.template.yaml
|
||||
// - system.user.yaml
|
||||
@@ -210,6 +211,16 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) {
|
||||
)
|
||||
return systemTypes.AuthClientResourceType, ref, pp, err
|
||||
|
||||
case systemTypes.ReportResourceType:
|
||||
if len(path) != 1 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path))
|
||||
}
|
||||
ref, pp, err := SystemReportRbacReferences(
|
||||
// report
|
||||
path[0],
|
||||
)
|
||||
return systemTypes.ReportResourceType, ref, pp, err
|
||||
|
||||
case systemTypes.RoleResourceType:
|
||||
if len(path) != 1 {
|
||||
return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path))
|
||||
|
||||
2
store/interfaces.gen.go
generated
2
store/interfaces.gen.go
generated
@@ -36,6 +36,7 @@ package store
|
||||
// - store/messagebus_queue_settings.yaml
|
||||
// - store/rbac_rules.yaml
|
||||
// - store/reminders.yaml
|
||||
// - store/reports.yaml
|
||||
// - store/role_members.yaml
|
||||
// - store/roles.yaml
|
||||
// - store/settings.yaml
|
||||
@@ -81,6 +82,7 @@ type (
|
||||
MessagebusQueueSettings
|
||||
RbacRules
|
||||
Reminders
|
||||
Reports
|
||||
RoleMembers
|
||||
Roles
|
||||
Settings
|
||||
|
||||
1
store/rdbms/compose_records.gen.go
generated
1
store/rdbms/compose_records.gen.go
generated
@@ -11,6 +11,7 @@ package rdbms
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
|
||||
@@ -67,6 +67,7 @@ func (s Schema) Tables() []*Table {
|
||||
s.Labels(),
|
||||
s.Flags(),
|
||||
s.Templates(),
|
||||
s.Reports(),
|
||||
s.ComposeAttachment(),
|
||||
s.ComposeChart(),
|
||||
s.ComposeModule(),
|
||||
@@ -351,6 +352,22 @@ func (Schema) Templates() *Table {
|
||||
)
|
||||
}
|
||||
|
||||
func (Schema) Reports() *Table {
|
||||
return TableDef("reports",
|
||||
ID,
|
||||
ColumnDef("handle", ColumnTypeVarchar, ColumnTypeLength(handleLength)),
|
||||
ColumnDef("meta", ColumnTypeJson),
|
||||
ColumnDef("sources", ColumnTypeJson),
|
||||
ColumnDef("projections", ColumnTypeJson),
|
||||
|
||||
ColumnDef("owned_by", ColumnTypeIdentifier),
|
||||
CUDTimestamps,
|
||||
CUDUsers,
|
||||
|
||||
AddIndex("unique_handle", IExpr("LOWER(handle)"), IWhere("LENGTH(handle) > 0 AND deleted_at IS NULL")),
|
||||
)
|
||||
}
|
||||
|
||||
func (Schema) ComposeAttachment() *Table {
|
||||
// @todo merge with general attachment table
|
||||
|
||||
|
||||
634
store/rdbms/reports.gen.go
generated
Normal file
634
store/rdbms/reports.gen.go
generated
Normal file
@@ -0,0 +1,634 @@
|
||||
package rdbms
|
||||
|
||||
// This file is an auto-generated file
|
||||
//
|
||||
// Template: pkg/codegen/assets/store_rdbms.gen.go.tpl
|
||||
// Definitions: store/reports.yaml
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior
|
||||
// and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/store/rdbms/builders"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
var _ = errors.Is
|
||||
|
||||
// SearchReports returns all matching rows
|
||||
//
|
||||
// This function calls convertReportFilter with the given
|
||||
// types.ReportFilter and expects to receive a working squirrel.SelectBuilder
|
||||
func (s Store) SearchReports(ctx context.Context, f types.ReportFilter) (types.ReportSet, types.ReportFilter, error) {
|
||||
var (
|
||||
err error
|
||||
set []*types.Report
|
||||
q squirrel.SelectBuilder
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
q, err = s.convertReportFilter(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Paging enabled
|
||||
// {search: {enablePaging:true}}
|
||||
// Cleanup unwanted cursor values (only relevant is f.PageCursor, next&prev are reset and returned)
|
||||
f.PrevPage, f.NextPage = nil, nil
|
||||
|
||||
if f.PageCursor != nil {
|
||||
// Page cursor exists so we need to validate it against used sort
|
||||
// To cover the case when paging cursor is set but sorting is empty, we collect the sorting instructions
|
||||
// from the cursor.
|
||||
// This (extracted sorting info) is then returned as part of response
|
||||
if f.Sort, err = f.PageCursor.Sort(f.Sort); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure results are always sorted at least by primary keys
|
||||
if f.Sort.Get("id") == nil {
|
||||
f.Sort = append(f.Sort, &filter.SortExpr{
|
||||
Column: "id",
|
||||
Descending: f.Sort.LastDescending(),
|
||||
})
|
||||
}
|
||||
|
||||
// Cloned sorting instructions for the actual sorting
|
||||
// Original are passed to the fetchFullPageOfUsers fn used for cursor creation so it MUST keep the initial
|
||||
// direction information
|
||||
sort := f.Sort.Clone()
|
||||
|
||||
// When cursor for a previous page is used it's marked as reversed
|
||||
// This tells us to flip the descending flag on all used sort keys
|
||||
if f.PageCursor != nil && f.PageCursor.ROrder {
|
||||
sort.Reverse()
|
||||
}
|
||||
|
||||
// Apply sorting expr from filter to query
|
||||
if q, err = setOrderBy(q, sort, s.sortableReportColumns()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set, f.PrevPage, f.NextPage, err = s.fetchFullPageOfReports(
|
||||
ctx,
|
||||
q, f.Sort, f.PageCursor,
|
||||
f.Limit,
|
||||
f.Check,
|
||||
func(cur *filter.PagingCursor) squirrel.Sqlizer {
|
||||
return builders.CursorCondition(cur, nil)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.PageCursor = nil
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
|
||||
// fetchFullPageOfReports collects all requested results.
|
||||
//
|
||||
// Function applies:
|
||||
// - cursor conditions (where ...)
|
||||
// - limit
|
||||
//
|
||||
// Main responsibility of this function is to perform additional sequential queries in case when not enough results
|
||||
// are collected due to failed check on a specific row (by check fn).
|
||||
//
|
||||
// Function then moves cursor to the last item fetched
|
||||
func (s Store) fetchFullPageOfReports(
|
||||
ctx context.Context,
|
||||
q squirrel.SelectBuilder,
|
||||
sort filter.SortExprSet,
|
||||
cursor *filter.PagingCursor,
|
||||
reqItems uint,
|
||||
check func(*types.Report) (bool, error),
|
||||
cursorCond func(*filter.PagingCursor) squirrel.Sqlizer,
|
||||
) (set []*types.Report, prev, next *filter.PagingCursor, err error) {
|
||||
var (
|
||||
aux []*types.Report
|
||||
|
||||
// When cursor for a previous page is used it's marked as reversed
|
||||
// This tells us to flip the descending flag on all used sort keys
|
||||
reversedOrder = cursor != nil && cursor.ROrder
|
||||
|
||||
// copy of the select builder
|
||||
tryQuery squirrel.SelectBuilder
|
||||
|
||||
// Copy no. of required items to limit
|
||||
// Limit will change when doing subsequent queries to fill
|
||||
// the set with all required items
|
||||
limit = reqItems
|
||||
|
||||
// cursor to prev. page is only calculated when cursor is used
|
||||
hasPrev = cursor != nil
|
||||
|
||||
// next cursor is calculated when there are more pages to come
|
||||
hasNext bool
|
||||
)
|
||||
|
||||
set = make([]*types.Report, 0, DefaultSliceCapacity)
|
||||
|
||||
for try := 0; try < MaxRefetches; try++ {
|
||||
if cursor != nil {
|
||||
tryQuery = q.Where(cursorCond(cursor))
|
||||
} else {
|
||||
tryQuery = q
|
||||
}
|
||||
|
||||
if limit > 0 {
|
||||
// fetching + 1 so we know if there are more items
|
||||
// we can fetch (next-page cursor)
|
||||
tryQuery = tryQuery.Limit(uint64(limit + 1))
|
||||
}
|
||||
|
||||
if aux, err = s.QueryReports(ctx, tryQuery, check); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
if len(aux) == 0 {
|
||||
// nothing fetched
|
||||
break
|
||||
}
|
||||
|
||||
// append fetched items
|
||||
set = append(set, aux...)
|
||||
|
||||
if reqItems == 0 {
|
||||
// no max requested items specified, break out
|
||||
break
|
||||
}
|
||||
|
||||
collected := uint(len(set))
|
||||
|
||||
if reqItems > collected {
|
||||
// not enough items fetched, try again with adjusted limit
|
||||
limit = reqItems - collected
|
||||
|
||||
if limit < MinEnsureFetchLimit {
|
||||
// In case limit is set very low and we've missed records in the first fetch,
|
||||
// make sure next fetch limit is a bit higher
|
||||
limit = MinEnsureFetchLimit
|
||||
}
|
||||
|
||||
// Update cursor so that it points to the last item fetched
|
||||
cursor = s.collectReportCursorValues(set[collected-1], sort...)
|
||||
|
||||
// Copy reverse flag from sorting
|
||||
cursor.LThen = sort.Reversed()
|
||||
continue
|
||||
}
|
||||
|
||||
if reqItems < collected {
|
||||
set = set[:reqItems]
|
||||
hasNext = true
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
collected := len(set)
|
||||
|
||||
if collected == 0 {
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
|
||||
if reversedOrder {
|
||||
// Fetched set needs to be reversed because we've forced a descending order to get the previous page
|
||||
for i, j := 0, collected-1; i < j; i, j = i+1, j-1 {
|
||||
set[i], set[j] = set[j], set[i]
|
||||
}
|
||||
|
||||
// when in reverse-order rules on what cursor to return change
|
||||
hasPrev, hasNext = hasNext, hasPrev
|
||||
}
|
||||
|
||||
if hasPrev {
|
||||
prev = s.collectReportCursorValues(set[0], sort...)
|
||||
prev.ROrder = true
|
||||
prev.LThen = !sort.Reversed()
|
||||
}
|
||||
|
||||
if hasNext {
|
||||
next = s.collectReportCursorValues(set[collected-1], sort...)
|
||||
next.LThen = sort.Reversed()
|
||||
}
|
||||
|
||||
return set, prev, next, nil
|
||||
}
|
||||
|
||||
// QueryReports queries the database, converts and checks each row and
|
||||
// returns collected set
|
||||
//
|
||||
// Fn also returns total number of fetched items and last fetched item so that the caller can construct cursor
|
||||
// for next page of results
|
||||
func (s Store) QueryReports(
|
||||
ctx context.Context,
|
||||
q squirrel.Sqlizer,
|
||||
check func(*types.Report) (bool, error),
|
||||
) ([]*types.Report, error) {
|
||||
var (
|
||||
tmp = make([]*types.Report, 0, DefaultSliceCapacity)
|
||||
set = make([]*types.Report, 0, DefaultSliceCapacity)
|
||||
res *types.Report
|
||||
|
||||
// Query rows with
|
||||
rows, err = s.Query(ctx, q)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
if err = rows.Err(); err == nil {
|
||||
res, err = s.internalReportRowScanner(rows)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tmp = append(tmp, res)
|
||||
}
|
||||
|
||||
for _, res = range tmp {
|
||||
|
||||
// check fn set, call it and see if it passed the test
|
||||
// if not, skip the item
|
||||
if check != nil {
|
||||
if chk, err := check(res); err != nil {
|
||||
return nil, err
|
||||
} else if !chk {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// LookupReportByID searches for report by ID
|
||||
//
|
||||
// It returns report even if deleted
|
||||
func (s Store) LookupReportByID(ctx context.Context, id uint64) (*types.Report, error) {
|
||||
return s.execLookupReport(ctx, squirrel.Eq{
|
||||
s.preprocessColumn("rp.id", ""): store.PreprocessValue(id, ""),
|
||||
})
|
||||
}
|
||||
|
||||
// LookupReportByHandle searches for report by Handle
|
||||
//
|
||||
// It returns report even if deleted
|
||||
func (s Store) LookupReportByHandle(ctx context.Context, handle string) (*types.Report, error) {
|
||||
return s.execLookupReport(ctx, squirrel.Eq{
|
||||
s.preprocessColumn("rp.handle", ""): store.PreprocessValue(handle, ""),
|
||||
|
||||
"rp.deleted_at": nil,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateReport creates one or more rows in reports table
|
||||
func (s Store) CreateReport(ctx context.Context, rr ...*types.Report) (err error) {
|
||||
for _, res := range rr {
|
||||
err = s.checkReportConstraints(ctx, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.execCreateReports(ctx, s.internalReportEncoder(res))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateReport updates one or more existing rows in reports
|
||||
func (s Store) UpdateReport(ctx context.Context, rr ...*types.Report) error {
|
||||
return s.partialReportUpdate(ctx, nil, rr...)
|
||||
}
|
||||
|
||||
// partialReportUpdate updates one or more existing rows in reports
|
||||
func (s Store) partialReportUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Report) (err error) {
|
||||
for _, res := range rr {
|
||||
err = s.checkReportConstraints(ctx, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.execUpdateReports(
|
||||
ctx,
|
||||
squirrel.Eq{
|
||||
s.preprocessColumn("rp.id", ""): store.PreprocessValue(res.ID, ""),
|
||||
},
|
||||
s.internalReportEncoder(res).Skip("id").Only(onlyColumns...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpsertReport updates one or more existing rows in reports
|
||||
func (s Store) UpsertReport(ctx context.Context, rr ...*types.Report) (err error) {
|
||||
for _, res := range rr {
|
||||
err = s.checkReportConstraints(ctx, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.execUpsertReports(ctx, s.internalReportEncoder(res))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteReport Deletes one or more rows from reports table
|
||||
func (s Store) DeleteReport(ctx context.Context, rr ...*types.Report) (err error) {
|
||||
for _, res := range rr {
|
||||
|
||||
err = s.execDeleteReports(ctx, squirrel.Eq{
|
||||
s.preprocessColumn("rp.id", ""): store.PreprocessValue(res.ID, ""),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteReportByID Deletes row from the reports table
|
||||
func (s Store) DeleteReportByID(ctx context.Context, ID uint64) error {
|
||||
return s.execDeleteReports(ctx, squirrel.Eq{
|
||||
s.preprocessColumn("rp.id", ""): store.PreprocessValue(ID, ""),
|
||||
})
|
||||
}
|
||||
|
||||
// TruncateReports Deletes all rows from the reports table
|
||||
func (s Store) TruncateReports(ctx context.Context) error {
|
||||
return s.Truncate(ctx, s.reportTable())
|
||||
}
|
||||
|
||||
// execLookupReport prepares Report query and executes it,
|
||||
// returning types.Report (or error)
|
||||
func (s Store) execLookupReport(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Report, err error) {
|
||||
var (
|
||||
row rowScanner
|
||||
)
|
||||
|
||||
row, err = s.QueryRow(ctx, s.reportsSelectBuilder().Where(cnd))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err = s.internalReportRowScanner(row)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// execCreateReports updates all matched (by cnd) rows in reports with given data
|
||||
func (s Store) execCreateReports(ctx context.Context, payload store.Payload) error {
|
||||
return s.Exec(ctx, s.InsertBuilder(s.reportTable()).SetMap(payload))
|
||||
}
|
||||
|
||||
// execUpdateReports updates all matched (by cnd) rows in reports with given data
|
||||
func (s Store) execUpdateReports(ctx context.Context, cnd squirrel.Sqlizer, set store.Payload) error {
|
||||
return s.Exec(ctx, s.UpdateBuilder(s.reportTable("rp")).Where(cnd).SetMap(set))
|
||||
}
|
||||
|
||||
// execUpsertReports inserts new or updates matching (by-primary-key) rows in reports with given data
|
||||
func (s Store) execUpsertReports(ctx context.Context, set store.Payload) error {
|
||||
upsert, err := s.config.UpsertBuilder(
|
||||
s.config,
|
||||
s.reportTable(),
|
||||
set,
|
||||
s.preprocessColumn("id", ""),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.Exec(ctx, upsert)
|
||||
}
|
||||
|
||||
// execDeleteReports Deletes all matched (by cnd) rows in reports with given data
|
||||
func (s Store) execDeleteReports(ctx context.Context, cnd squirrel.Sqlizer) error {
|
||||
return s.Exec(ctx, s.DeleteBuilder(s.reportTable("rp")).Where(cnd))
|
||||
}
|
||||
|
||||
func (s Store) internalReportRowScanner(row rowScanner) (res *types.Report, err error) {
|
||||
res = &types.Report{}
|
||||
|
||||
if _, has := s.config.RowScanners["report"]; has {
|
||||
scanner := s.config.RowScanners["report"].(func(_ rowScanner, _ *types.Report) error)
|
||||
err = scanner(row, res)
|
||||
} else {
|
||||
err = row.Scan(
|
||||
&res.ID,
|
||||
&res.Handle,
|
||||
&res.Meta,
|
||||
&res.Sources,
|
||||
&res.Projections,
|
||||
&res.OwnedBy,
|
||||
&res.CreatedBy,
|
||||
&res.UpdatedBy,
|
||||
&res.DeletedBy,
|
||||
&res.CreatedAt,
|
||||
&res.UpdatedAt,
|
||||
&res.DeletedAt,
|
||||
)
|
||||
}
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.ErrNotFound.Stack(1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Store("could not scan report db row: %s", err).Wrap(err)
|
||||
} else {
|
||||
return res, nil
|
||||
}
|
||||
}
|
||||
|
||||
// QueryReports returns squirrel.SelectBuilder with set table and all columns
|
||||
func (s Store) reportsSelectBuilder() squirrel.SelectBuilder {
|
||||
return s.SelectBuilder(s.reportTable("rp"), s.reportColumns("rp")...)
|
||||
}
|
||||
|
||||
// reportTable name of the db table
|
||||
func (Store) reportTable(aa ...string) string {
|
||||
var alias string
|
||||
if len(aa) > 0 {
|
||||
alias = " AS " + aa[0]
|
||||
}
|
||||
|
||||
return "reports" + alias
|
||||
}
|
||||
|
||||
// ReportColumns returns all defined table columns
|
||||
//
|
||||
// With optional string arg, all columns are returned aliased
|
||||
func (Store) reportColumns(aa ...string) []string {
|
||||
var alias string
|
||||
if len(aa) > 0 {
|
||||
alias = aa[0] + "."
|
||||
}
|
||||
|
||||
return []string{
|
||||
alias + "id",
|
||||
alias + "handle",
|
||||
alias + "meta",
|
||||
alias + "sources",
|
||||
alias + "projections",
|
||||
alias + "owned_by",
|
||||
alias + "created_by",
|
||||
alias + "updated_by",
|
||||
alias + "deleted_by",
|
||||
alias + "created_at",
|
||||
alias + "updated_at",
|
||||
alias + "deleted_at",
|
||||
}
|
||||
}
|
||||
|
||||
// {true true false true true true}
|
||||
|
||||
// sortableReportColumns returns all Report columns flagged as sortable
|
||||
//
|
||||
// With optional string arg, all columns are returned aliased
|
||||
func (Store) sortableReportColumns() map[string]string {
|
||||
return map[string]string{
|
||||
"id": "id", "handle": "handle", "created_at": "created_at",
|
||||
"createdat": "created_at",
|
||||
"updated_at": "updated_at",
|
||||
"updatedat": "updated_at",
|
||||
"deleted_at": "deleted_at",
|
||||
"deletedat": "deleted_at",
|
||||
}
|
||||
}
|
||||
|
||||
// internalReportEncoder encodes fields from types.Report to store.Payload (map)
|
||||
//
|
||||
// Encoding is done by using generic approach or by calling encodeReport
|
||||
// func when rdbms.customEncoder=true
|
||||
func (s Store) internalReportEncoder(res *types.Report) store.Payload {
|
||||
return store.Payload{
|
||||
"id": res.ID,
|
||||
"handle": res.Handle,
|
||||
"meta": res.Meta,
|
||||
"sources": res.Sources,
|
||||
"projections": res.Projections,
|
||||
"owned_by": res.OwnedBy,
|
||||
"created_by": res.CreatedBy,
|
||||
"updated_by": res.UpdatedBy,
|
||||
"deleted_by": res.DeletedBy,
|
||||
"created_at": res.CreatedAt,
|
||||
"updated_at": res.UpdatedAt,
|
||||
"deleted_at": res.DeletedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// collectReportCursorValues collects values from the given resource that and sets them to the cursor
|
||||
// to be used for pagination
|
||||
//
|
||||
// Values that are collected must come from sortable, unique or primary columns/fields
|
||||
// At least one of the collected columns must be flagged as unique, otherwise fn appends primary keys at the end
|
||||
//
|
||||
// Known issue:
|
||||
// when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on
|
||||
// undeleted items)
|
||||
func (s Store) collectReportCursorValues(res *types.Report, cc ...*filter.SortExpr) *filter.PagingCursor {
|
||||
var (
|
||||
cursor = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()}
|
||||
|
||||
hasUnique bool
|
||||
|
||||
// All known primary key columns
|
||||
|
||||
pkId bool
|
||||
|
||||
collect = func(cc ...*filter.SortExpr) {
|
||||
for _, c := range cc {
|
||||
switch c.Column {
|
||||
case "id":
|
||||
cursor.Set(c.Column, res.ID, c.Descending)
|
||||
|
||||
pkId = true
|
||||
case "handle":
|
||||
cursor.Set(c.Column, res.Handle, c.Descending)
|
||||
|
||||
case "created_at":
|
||||
cursor.Set(c.Column, res.CreatedAt, c.Descending)
|
||||
|
||||
case "updated_at":
|
||||
cursor.Set(c.Column, res.UpdatedAt, c.Descending)
|
||||
|
||||
case "deleted_at":
|
||||
cursor.Set(c.Column, res.DeletedAt, c.Descending)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
collect(cc...)
|
||||
if !hasUnique || !(pkId && true) {
|
||||
collect(&filter.SortExpr{Column: "id", Descending: false})
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
// checkReportConstraints performs lookups (on valid) resource to check if any of the values on unique fields
|
||||
// already exists in the store
|
||||
//
|
||||
// Using built-in constraint checking would be more performant but unfortunately we cannot rely
|
||||
// on the full support (MySQL does not support conditional indexes)
|
||||
func (s *Store) checkReportConstraints(ctx context.Context, res *types.Report) error {
|
||||
// Consider resource valid when all fields in unique constraint check lookups
|
||||
// have valid (non-empty) value
|
||||
//
|
||||
// Only string and uint64 are supported for now
|
||||
// feel free to add additional types if needed
|
||||
var valid = true
|
||||
|
||||
valid = valid && len(res.Handle) > 0
|
||||
|
||||
if !valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
{
|
||||
ex, err := s.LookupReportByHandle(ctx, res.Handle)
|
||||
if err == nil && ex != nil && ex.ID != res.ID {
|
||||
return store.ErrNotUnique.Stack(1)
|
||||
} else if !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
23
store/rdbms/reports.go
Normal file
23
store/rdbms/reports.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package rdbms
|
||||
|
||||
import (
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
func (s Store) convertReportFilter(f types.ReportFilter) (query squirrel.SelectBuilder, err error) {
|
||||
query = s.reportsSelectBuilder()
|
||||
|
||||
query = filter.StateCondition(query, "rp.deleted_at", f.Deleted)
|
||||
|
||||
if len(f.LabeledIDs) > 0 {
|
||||
query = query.Where(squirrel.Eq{"rp.id": f.LabeledIDs})
|
||||
}
|
||||
|
||||
if f.Handle != "" {
|
||||
query = query.Where(squirrel.Eq{"rp.handle": f.Handle})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
85
store/reports.gen.go
generated
Normal file
85
store/reports.gen.go
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
package store
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Template: pkg/codegen/assets/store_base.gen.go.tpl
|
||||
// Definitions: store/reports.yaml
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
Reports interface {
|
||||
SearchReports(ctx context.Context, f types.ReportFilter) (types.ReportSet, types.ReportFilter, error)
|
||||
LookupReportByID(ctx context.Context, id uint64) (*types.Report, error)
|
||||
LookupReportByHandle(ctx context.Context, handle string) (*types.Report, error)
|
||||
|
||||
CreateReport(ctx context.Context, rr ...*types.Report) error
|
||||
|
||||
UpdateReport(ctx context.Context, rr ...*types.Report) error
|
||||
|
||||
UpsertReport(ctx context.Context, rr ...*types.Report) error
|
||||
|
||||
DeleteReport(ctx context.Context, rr ...*types.Report) error
|
||||
DeleteReportByID(ctx context.Context, ID uint64) error
|
||||
|
||||
TruncateReports(ctx context.Context) error
|
||||
}
|
||||
)
|
||||
|
||||
var _ *types.Report
|
||||
var _ context.Context
|
||||
|
||||
// SearchReports returns all matching Reports from store
|
||||
func SearchReports(ctx context.Context, s Reports, f types.ReportFilter) (types.ReportSet, types.ReportFilter, error) {
|
||||
return s.SearchReports(ctx, f)
|
||||
}
|
||||
|
||||
// LookupReportByID searches for report by ID
|
||||
//
|
||||
// It returns report even if deleted
|
||||
func LookupReportByID(ctx context.Context, s Reports, id uint64) (*types.Report, error) {
|
||||
return s.LookupReportByID(ctx, id)
|
||||
}
|
||||
|
||||
// LookupReportByHandle searches for report by Handle
|
||||
//
|
||||
// It returns report even if deleted
|
||||
func LookupReportByHandle(ctx context.Context, s Reports, handle string) (*types.Report, error) {
|
||||
return s.LookupReportByHandle(ctx, handle)
|
||||
}
|
||||
|
||||
// CreateReport creates one or more Reports in store
|
||||
func CreateReport(ctx context.Context, s Reports, rr ...*types.Report) error {
|
||||
return s.CreateReport(ctx, rr...)
|
||||
}
|
||||
|
||||
// UpdateReport updates one or more (existing) Reports in store
|
||||
func UpdateReport(ctx context.Context, s Reports, rr ...*types.Report) error {
|
||||
return s.UpdateReport(ctx, rr...)
|
||||
}
|
||||
|
||||
// UpsertReport creates new or updates existing one or more Reports in store
|
||||
func UpsertReport(ctx context.Context, s Reports, rr ...*types.Report) error {
|
||||
return s.UpsertReport(ctx, rr...)
|
||||
}
|
||||
|
||||
// DeleteReport Deletes one or more Reports from store
|
||||
func DeleteReport(ctx context.Context, s Reports, rr ...*types.Report) error {
|
||||
return s.DeleteReport(ctx, rr...)
|
||||
}
|
||||
|
||||
// DeleteReportByID Deletes Report from store
|
||||
func DeleteReportByID(ctx context.Context, s Reports, ID uint64) error {
|
||||
return s.DeleteReportByID(ctx, ID)
|
||||
}
|
||||
|
||||
// TruncateReports Deletes all Reports from store
|
||||
func TruncateReports(ctx context.Context, s Reports) error {
|
||||
return s.TruncateReports(ctx)
|
||||
}
|
||||
36
store/reports.yaml
Normal file
36
store/reports.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
import:
|
||||
- github.com/cortezaproject/corteza-server/system/types
|
||||
|
||||
fields:
|
||||
- { field: ID }
|
||||
- { field: Handle, sortable: true }
|
||||
- { field: Meta, type: '*ReportMeta' }
|
||||
- { field: Sources, type: 'ReportDataSourceSet' }
|
||||
- { field: Projections, type: 'ReportProjectionSet' }
|
||||
- { field: OwnedBy }
|
||||
- { field: CreatedBy }
|
||||
- { field: UpdatedBy }
|
||||
- { field: DeletedBy }
|
||||
- { field: CreatedAt, sortable: true }
|
||||
- { field: UpdatedAt, sortable: true }
|
||||
- { field: DeletedAt, sortable: true }
|
||||
|
||||
lookups:
|
||||
- fields: [ ID ]
|
||||
description: |-
|
||||
searches for report by ID
|
||||
|
||||
It returns report even if deleted
|
||||
|
||||
- fields: [ Handle ]
|
||||
uniqueConstraintCheck: true
|
||||
filter: { DeletedAt: nil }
|
||||
description: |-
|
||||
searches for report by Handle
|
||||
|
||||
It returns report even if deleted
|
||||
|
||||
rdbms:
|
||||
alias: rp
|
||||
table: reports
|
||||
customFilterConverter: true
|
||||
6
store/tests/gen_test.go
generated
6
store/tests/gen_test.go
generated
@@ -34,6 +34,7 @@ package tests
|
||||
// - store/messagebus_queue_settings.yaml
|
||||
// - store/rbac_rules.yaml
|
||||
// - store/reminders.yaml
|
||||
// - store/reports.yaml
|
||||
// - store/role_members.yaml
|
||||
// - store/roles.yaml
|
||||
// - store/settings.yaml
|
||||
@@ -211,6 +212,11 @@ func testAllGenerated(t *testing.T, s store.Storer) {
|
||||
testReminders(t, s)
|
||||
})
|
||||
|
||||
// Run generated tests for Reports
|
||||
t.Run("Reports", func(t *testing.T) {
|
||||
testReports(t, s)
|
||||
})
|
||||
|
||||
// Run generated tests for RoleMembers
|
||||
t.Run("RoleMembers", func(t *testing.T) {
|
||||
testRoleMembers(t, s)
|
||||
|
||||
11
store/tests/reports_test.go
Normal file
11
store/tests/reports_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
)
|
||||
|
||||
func testReports(t *testing.T, s store.Reports) {
|
||||
t.Skip("@todo")
|
||||
}
|
||||
116
system/rest.yaml
116
system/rest.yaml
@@ -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"
|
||||
|
||||
190
system/rest/handlers/report.go
Normal file
190
system/rest/handlers/report.go
Normal 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
184
system/rest/report.go
Normal 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
|
||||
}
|
||||
766
system/rest/request/report.go
Normal file
766
system/rest/request/report.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
135
system/service/access_control.gen.go
generated
135
system/service/access_control.gen.go
generated
@@ -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
342
system/service/report.go
Normal 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
780
system/service/report_actions.gen.go
generated
Normal 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
|
||||
}
|
||||
92
system/service/report_actions.yaml
Normal file
92
system/service/report_actions.yaml
Normal 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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
33
system/types/rbac.gen.go
generated
33
system/types/rbac.gen.go
generated
@@ -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
158
system/types/report.go
Normal 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
|
||||
}
|
||||
24
system/types/type_labels.gen.go
generated
24
system/types/type_labels.gen.go
generated
@@ -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 {
|
||||
|
||||
61
system/types/type_set.gen.go
generated
61
system/types/type_set.gen.go
generated
@@ -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.
|
||||
|
||||
90
system/types/type_set.gen_test.go
generated
90
system/types/type_set.gen_test.go
generated
@@ -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)
|
||||
|
||||
@@ -23,3 +23,5 @@ types:
|
||||
labelResourceType: template
|
||||
ApigwRoute: {}
|
||||
ApigwFilter: {}
|
||||
Report:
|
||||
labelResourceType: report
|
||||
|
||||
Reference in New Issue
Block a user