Add back-end support for report scenarios
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
|
||||
Generated
+3
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user