Add a report description endpoint to query shape

This commit is contained in:
Tomaž Jerman
2021-08-16 09:16:07 +02:00
parent 4da71e78ad
commit 7626714050
10 changed files with 222 additions and 0 deletions
+1
View File
@@ -21,6 +21,7 @@ type (
Datasource interface {
Name() string
Load(context.Context, ...*FrameDefinition) (Loader, Closer, error)
Describe() FrameDescriptionSet
}
// GroupableDatasource is able to provide groupped data
+9
View File
@@ -54,6 +54,15 @@ type (
Paging *filter.Paging `json:"paging"`
Sorting filter.SortExprSet `json:"sorting"`
}
FrameDescriptionSet []*FrameDescription
FrameDescription struct {
Source string `json:"source"`
Ref string `json:"ref,omitempty"`
Columns FrameColumnSet `json:"columns"`
// @todo size and other shape related bits
}
)
const (
+10
View File
@@ -19,6 +19,7 @@ type (
Add(...Step) M
Run(context.Context) error
Load(context.Context, ...*FrameDefinition) ([]*Frame, error)
Describe(source string) (FrameDescriptionSet, error)
}
StepSet []Step
@@ -130,6 +131,15 @@ func (m *model) Run(ctx context.Context) (err error) {
return nil
}
func (m *model) Describe(source string) (out FrameDescriptionSet, err error) {
ds := m.datasources.Find(source)
if ds == nil {
return nil, fmt.Errorf("model does not contain the datasource: %s", source)
}
return ds.Describe(), nil
}
func (m *model) Load(ctx context.Context, dd ...*FrameDefinition) ([]*Frame, error) {
var err error
+28
View File
@@ -93,6 +93,34 @@ func (d *joinedDataset) Name() string {
return d.def.Name
}
func (d *joinedDataset) Describe() FrameDescriptionSet {
out := make(FrameDescriptionSet, 0, 2)
local := d.base.Describe()
for _, l := range local {
out = append(out,
&FrameDescription{
Source: d.Name(),
Ref: l.Source,
Columns: l.Columns,
},
)
}
foreign := d.foreign.Describe()
for _, f := range foreign {
out = append(out,
&FrameDescription{
Source: d.Name(),
Ref: f.Source,
Columns: f.Columns,
},
)
}
return out
}
// @todo allow x-join filtering
func (d *joinedDataset) Load(ctx context.Context, dd ...*FrameDefinition) (Loader, Closer, error) {
// to hold closer references for all underlying datasources
+9
View File
@@ -174,6 +174,15 @@ func (r *recordDatasource) Group(d report.GroupDefinition, name string) (bool, e
return true, nil
}
func (r *recordDatasource) Describe() report.FrameDescriptionSet {
return report.FrameDescriptionSet{
&report.FrameDescription{
Source: r.Name(),
Columns: r.cols,
},
}
}
func (r *recordDatasource) Load(ctx context.Context, dd ...*report.FrameDefinition) (l report.Loader, c report.Closer, err error) {
def := dd[0]
+10
View File
@@ -1435,6 +1435,16 @@ endpoints:
name: reportID
required: true
title: Report ID
- name: describe
method: POST
title: Describe report (fresh)
path: "/describe"
parameters:
post:
- { name: sources, type: "types.ReportDataSourceSet", title: Report steps definition }
- { name: steps, type: "report.StepDefinitionSet", title: Report steps definition }
- { name: describe, type: "[]string", title: The source descriptions to generate }
# @todo better name
- name: runFresh
method: POST
+19
View File
@@ -25,6 +25,7 @@ type (
Read(context.Context, *request.ReportRead) (interface{}, error)
Delete(context.Context, *request.ReportDelete) (interface{}, error)
Undelete(context.Context, *request.ReportUndelete) (interface{}, error)
Describe(context.Context, *request.ReportDescribe) (interface{}, error)
RunFresh(context.Context, *request.ReportRunFresh) (interface{}, error)
Run(context.Context, *request.ReportRun) (interface{}, error)
}
@@ -37,6 +38,7 @@ type (
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
Undelete func(http.ResponseWriter, *http.Request)
Describe func(http.ResponseWriter, *http.Request)
RunFresh func(http.ResponseWriter, *http.Request)
Run func(http.ResponseWriter, *http.Request)
}
@@ -140,6 +142,22 @@ func NewReport(h ReportAPI) *Report {
api.Send(w, r, value)
},
Describe: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewReportDescribe()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}
value, err := h.Describe(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()
@@ -184,6 +202,7 @@ func (h Report) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http
r.Get("/reports/{reportID}", h.Read)
r.Delete("/reports/{reportID}", h.Delete)
r.Post("/reports/{reportID}/undelete", h.Undelete)
r.Post("/reports/describe", h.Describe)
r.Post("/reports/run", h.RunFresh)
r.Post("/reports/{reportID}/run", h.Run)
})
+5
View File
@@ -30,6 +30,7 @@ type (
// @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)
DescribeFresh(ctx context.Context, src types.ReportDataSourceSet, st report.StepDefinitionSet, sources ...string) (out report.FrameDescriptionSet, err error)
}
reportAccessController interface {
@@ -139,6 +140,10 @@ func (ctrl *Report) Run(ctx context.Context, r *request.ReportRun) (interface{},
return nil, nil
}
func (ctrl *Report) Describe(ctx context.Context, r *request.ReportDescribe) (interface{}, error) {
return ctrl.report.DescribeFresh(ctx, r.Sources, r.Steps, r.Describe...)
}
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)
+92
View File
@@ -148,6 +148,23 @@ type (
ReportID uint64 `json:",string"`
}
ReportDescribe struct {
// Sources POST parameter
//
// Report steps definition
Sources types.ReportDataSourceSet
// Steps POST parameter
//
// Report steps definition
Steps report.StepDefinitionSet
// Describe POST parameter
//
// The source descriptions to generate
Describe []string
}
ReportRunFresh struct {
// Sources POST parameter
//
@@ -623,6 +640,81 @@ func (r *ReportUndelete) Fill(req *http.Request) (err error) {
return err
}
// NewReportDescribe request
func NewReportDescribe() *ReportDescribe {
return &ReportDescribe{}
}
// Auditable returns all auditable/loggable parameters
func (r ReportDescribe) Auditable() map[string]interface{} {
return map[string]interface{}{
"sources": r.Sources,
"steps": r.Steps,
"describe": r.Describe,
}
}
// Auditable returns all auditable/loggable parameters
func (r ReportDescribe) GetSources() types.ReportDataSourceSet {
return r.Sources
}
// Auditable returns all auditable/loggable parameters
func (r ReportDescribe) GetSteps() report.StepDefinitionSet {
return r.Steps
}
// Auditable returns all auditable/loggable parameters
func (r ReportDescribe) GetDescribe() []string {
return r.Describe
}
// Fill processes request and fills internal variables
func (r *ReportDescribe) 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["describe[]"]; ok && len(val) > 0 {
// r.Describe, err = val, nil
// if err != nil {
// return err
// }
//}
}
return err
}
// NewReportRunFresh request
func NewReportRunFresh() *ReportRunFresh {
return &ReportRunFresh{}
+39
View File
@@ -288,6 +288,45 @@ func (svc *report) Run(ctx context.Context, ID uint64, dd rep.FrameDefinitionSet
return nil, nil
}
// actionlog?
func (svc *report) DescribeFresh(ctx context.Context, src types.ReportDataSourceSet, st rep.StepDefinitionSet, sources ...string) (out rep.FrameDescriptionSet, err error) {
// var (
// aaProps = &reportActionProps{}
// )
out = make(rep.FrameDescriptionSet, 0, len(sources)*2)
err = func() (err error) {
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
}
var auxOut rep.FrameDescriptionSet
for _, s := range sources {
auxOut, err = model.Describe(s)
if err != nil {
return err
}
out = append(out, auxOut...)
}
return nil
}()
return out, err
}
func (svc *report) RunFresh(ctx context.Context, src types.ReportDataSourceSet, st rep.StepDefinitionSet, dd rep.FrameDefinitionSet) (out []*rep.Frame, err error) {
var (
aaProps = &reportActionProps{}