Add back-end support for report scenarios

This commit is contained in:
Tomaž Jerman
2021-11-10 16:39:21 +01:00
parent 5aafe5319b
commit 4b6b52b3f6
10 changed files with 216 additions and 28 deletions
+23 -4
View File
@@ -86,10 +86,15 @@ func (g genericUpgrades) Upgrade(ctx context.Context, t *ddl.Table) error {
return g.all(ctx,
g.CreateAutomationSessionIndexes,
)
//case "compose_attachment_binds":
// return g.all(ctx,
// g.MigrateComposeAttachmentsToBindsTable,
// )
//case "compose_attachment_binds":
// return g.all(ctx,
// g.MigrateComposeAttachmentsToBindsTable,
// )
case "reports":
return g.all(ctx,
g.AddScenariosField,
)
}
return nil
@@ -278,6 +283,20 @@ func (g genericUpgrades) DropOrganisationTable(ctx context.Context) error {
return err
}
func (g genericUpgrades) AddScenariosField(ctx context.Context) error {
var (
col = &ddl.Column{
Name: "scenarios",
Type: ddl.ColumnType{Type: ddl.ColumnTypeJson},
IsNull: false,
DefaultValue: "NULL",
}
)
_, err := g.u.AddColumn(ctx, "reports", col)
return err
}
func (g genericUpgrades) AlterUsersDropOrganisation(ctx context.Context) error {
_, err := g.u.DropColumn(ctx, "users", "rel_organisation")
return err
+1
View File
@@ -368,6 +368,7 @@ func (Schema) Reports() *Table {
ID,
ColumnDef("handle", ColumnTypeVarchar, ColumnTypeLength(handleLength)),
ColumnDef("meta", ColumnTypeJson),
ColumnDef("scenarios", ColumnTypeJson),
ColumnDef("sources", ColumnTypeJson),
ColumnDef("blocks", ColumnTypeJson),
+3
View File
@@ -451,6 +451,7 @@ func (s Store) internalReportRowScanner(row rowScanner) (res *types.Report, err
&res.ID,
&res.Handle,
&res.Meta,
&res.Scenarios,
&res.Sources,
&res.Blocks,
&res.OwnedBy,
@@ -502,6 +503,7 @@ func (Store) reportColumns(aa ...string) []string {
alias + "id",
alias + "handle",
alias + "meta",
alias + "scenarios",
alias + "sources",
alias + "blocks",
alias + "owned_by",
@@ -539,6 +541,7 @@ func (s Store) internalReportEncoder(res *types.Report) store.Payload {
"id": res.ID,
"handle": res.Handle,
"meta": res.Meta,
"scenarios": res.Scenarios,
"sources": res.Sources,
"blocks": res.Blocks,
"owned_by": res.OwnedBy,
+1
View File
@@ -5,6 +5,7 @@ fields:
- { field: ID }
- { field: Handle, sortable: true }
- { field: Meta, type: '*ReportMeta' }
- { field: Scenarios, type: 'ReportScenarioSet' }
- { field: Sources, type: 'ReportDataSourceSet' }
- { field: Blocks, type: 'ReportBlockSet' }
- { field: OwnedBy }
+2
View File
@@ -1391,6 +1391,7 @@ endpoints:
post:
- { name: handle, type: string, title: Client handle }
- { name: meta, type: '*types.ReportMeta', title: Additional info, parser: types.ParseReportMeta }
- { name: scenarios, type: "types.ReportScenarioSet", title: Report scenarios }
- { name: sources, type: "types.ReportDataSourceSet", title: Report source definitions }
- { name: blocks, type: "types.ReportBlockSet", title: Report blocks definition }
- { name: labels, type: 'map[string]string', title: Labels, parser: label.ParseStrings }
@@ -1407,6 +1408,7 @@ endpoints:
post:
- { name: handle, type: string, title: Client handle }
- { name: meta, type: '*types.ReportMeta', title: Additional info, parser: types.ParseReportMeta }
- { name: scenarios, type: "types.ReportScenarioSet", title: Report scenarios }
- { name: sources, type: "types.ReportDataSourceSet", title: Report sources definition }
- { name: blocks, type: "types.ReportBlockSet", title: Report blocks definition }
- { name: labels, type: 'map[string]string', title: Labels, parser: label.ParseStrings }
+13 -11
View File
@@ -93,11 +93,12 @@ func (ctrl *Report) Create(ctx context.Context, r *request.ReportCreate) (interf
var (
err error
app = &types.Report{
Handle: r.Handle,
Meta: r.Meta,
Sources: r.Sources,
Blocks: r.Blocks,
Labels: r.Labels,
Handle: r.Handle,
Meta: r.Meta,
Scenarios: r.Scenarios,
Sources: r.Sources,
Blocks: r.Blocks,
Labels: r.Labels,
}
)
@@ -109,12 +110,13 @@ func (ctrl *Report) Update(ctx context.Context, r *request.ReportUpdate) (interf
var (
err error
app = &types.Report{
ID: r.ReportID,
Handle: r.Handle,
Meta: r.Meta,
Sources: r.Sources,
Blocks: r.Blocks,
Labels: r.Labels,
ID: r.ReportID,
Handle: r.Handle,
Meta: r.Meta,
Scenarios: r.Scenarios,
Sources: r.Sources,
Blocks: r.Blocks,
Labels: r.Labels,
}
)
+47 -11
View File
@@ -79,6 +79,11 @@ type (
// Additional info
Meta *types.ReportMeta
// Scenarios POST parameter
//
// Report scenarios
Scenarios types.ReportScenarioSet
// Sources POST parameter
//
// Report source definitions
@@ -111,6 +116,11 @@ type (
// Additional info
Meta *types.ReportMeta
// Scenarios POST parameter
//
// Report scenarios
Scenarios types.ReportScenarioSet
// Sources POST parameter
//
// Report sources definition
@@ -286,11 +296,12 @@ func NewReportCreate() *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,
"blocks": r.Blocks,
"labels": r.Labels,
"handle": r.Handle,
"meta": r.Meta,
"scenarios": r.Scenarios,
"sources": r.Sources,
"blocks": r.Blocks,
"labels": r.Labels,
}
}
@@ -304,6 +315,11 @@ func (r ReportCreate) GetMeta() *types.ReportMeta {
return r.Meta
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetScenarios() types.ReportScenarioSet {
return r.Scenarios
}
// Auditable returns all auditable/loggable parameters
func (r ReportCreate) GetSources() types.ReportDataSourceSet {
return r.Sources
@@ -359,6 +375,13 @@ func (r *ReportCreate) Fill(req *http.Request) (err error) {
}
}
//if val, ok := req.Form["scenarios[]"]; ok && len(val) > 0 {
// r.Scenarios, err = types.ReportScenarioSet(val), nil
// 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 {
@@ -397,12 +420,13 @@ func NewReportUpdate() *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,
"blocks": r.Blocks,
"labels": r.Labels,
"reportID": r.ReportID,
"handle": r.Handle,
"meta": r.Meta,
"scenarios": r.Scenarios,
"sources": r.Sources,
"blocks": r.Blocks,
"labels": r.Labels,
}
}
@@ -421,6 +445,11 @@ func (r ReportUpdate) GetMeta() *types.ReportMeta {
return r.Meta
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetScenarios() types.ReportScenarioSet {
return r.Scenarios
}
// Auditable returns all auditable/loggable parameters
func (r ReportUpdate) GetSources() types.ReportDataSourceSet {
return r.Sources
@@ -476,6 +505,13 @@ func (r *ReportUpdate) Fill(req *http.Request) (err error) {
}
}
//if val, ok := req.Form["scenarios[]"]; ok && len(val) > 0 {
// r.Scenarios, err = types.ReportScenarioSet(val), nil
// 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 {
+1
View File
@@ -184,6 +184,7 @@ func (svc *report) Update(ctx context.Context, upd *types.Report) (report *types
// Assign changed values after afterUpdate events are emitted
report.Handle = upd.Handle
report.Meta = upd.Meta
report.Scenarios = upd.Scenarios
report.Sources = upd.Sources
report.Blocks = upd.Blocks
report.UpdatedAt = now()
+31 -2
View File
@@ -16,8 +16,9 @@ type (
Handle string `json:"handle"`
Meta *ReportMeta `json:"meta,omitempty"`
Sources ReportDataSourceSet `json:"sources"`
Blocks ReportBlockSet `json:"blocks"`
Scenarios ReportScenarioSet `json:"scenarios,omitempty"`
Sources ReportDataSourceSet `json:"sources"`
Blocks ReportBlockSet `json:"blocks"`
// Report labels
Labels map[string]string `json:"labels,omitempty"`
@@ -31,6 +32,14 @@ type (
DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
ReportScenarioSet []*ReportScenario
ScenarioFilterMap map[string]*report.Filter
ReportScenario struct {
// ScenarioID uint64 `json:"scenarioID,string,omitempty"`
Label string `json:"label"`
Filters ScenarioFilterMap `json:"filters,omitempty"`
}
ReportDataSource struct {
Meta interface{} `json:"meta,omitempty"`
Step *report.StepDefinition `json:"step"`
@@ -164,6 +173,26 @@ func (vv *ReportDataSourceSet) Scan(value interface{}) error {
return nil
}
// Scan on ReportScenarioSet gracefully handles conversion from NULL
func (vv ReportScenarioSet) Value() (driver.Value, error) {
return json.Marshal(vv)
}
func (vv *ReportScenarioSet) 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 = ReportScenarioSet{}
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
}
// func (r *Report) decodeTranslations(tt locale.ResourceTranslationIndex) {
// var aux *locale.ResourceTranslation
+94
View File
@@ -0,0 +1,94 @@
package system
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/cortezaproject/corteza-server/pkg/id"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/service"
"github.com/cortezaproject/corteza-server/system/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
)
func (h helper) createReport(report *types.Report) *types.Report {
if report.ID == 0 {
report.ID = id.Next()
}
if report.CreatedAt.IsZero() {
report.CreatedAt = time.Now()
}
h.a.NoError(service.DefaultStore.CreateReport(context.Background(), report))
return report
}
func (h helper) clearReports() {
h.noError(store.TruncateReports(context.Background(), service.DefaultStore))
}
func (h helper) lookupReportByHandle(handle string) *types.Report {
res, err := store.LookupReportByHandle(context.Background(), service.DefaultStore, handle)
h.noError(err)
return res
}
func TestReportScenarios_create(t *testing.T) {
h := newHelper(t)
h.clearReports()
helpers.AllowMe(h, types.ComponentRbacResource(), "report.create")
h.apiInit().
Post("/reports/").
Header("Accept", "application/json").
JSON(`{
"handle": "test_report",
"scenarios": [{ "label": "scenario 1", "filters": { "ds_1": { "raw": "field == 'value'" } } }]
}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
r := h.lookupReportByHandle("test_report")
h.a.NotNil(r)
h.a.Len(r.Scenarios, 1)
sc := r.Scenarios[0]
h.a.NotNil(sc.Filters)
h.a.Contains(sc.Filters, "ds_1")
}
func TestReportScenarios_update(t *testing.T) {
h := newHelper(t)
h.clearReports()
helpers.AllowMe(h, types.ReportRbacResource(0), "update")
r := h.createReport(&types.Report{
Handle: "test_report",
})
h.apiInit().
Put(fmt.Sprintf("/reports/%d", r.ID)).
Header("Accept", "application/json").
JSON(`{
"handle": "test_report",
"scenarios": [{ "label": "scenario 1", "filters": { "ds_1": { "raw": "field == 'value'" } } }]
}`).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
r = h.lookupReportByHandle("test_report")
h.a.NotNil(r)
h.a.Len(r.Scenarios, 1)
sc := r.Scenarios[0]
h.a.NotNil(sc.Filters)
h.a.Contains(sc.Filters, "ds_1")
}