diff --git a/service/guard.go b/service/guard.go index 8259d0cf..42589a74 100644 --- a/service/guard.go +++ b/service/guard.go @@ -7,6 +7,7 @@ import ( "github.com/yaoapp/yao/helper" table_v0 "github.com/yaoapp/yao/table" + "github.com/yaoapp/yao/widgets/chart" "github.com/yaoapp/yao/widgets/form" "github.com/yaoapp/yao/widgets/table" ) @@ -18,6 +19,7 @@ var Guards = map[string]gin.HandlerFunc{ "table-guard": table_v0.Guard, // Table Guard ( v0.9 table) "widget-table": table.Guard, // Widget Table Guard "widget-form": form.Guard, // Widget Form Guard + "widget-chart": chart.Guard, // Widget Chart Guard } // JWT Bearer JWT diff --git a/widgets/chart/action.go b/widgets/chart/action.go index 838b160f..83fc8c5d 100644 --- a/widgets/chart/action.go +++ b/widgets/chart/action.go @@ -1,12 +1,6 @@ package chart import ( - "fmt" - - "github.com/yaoapp/gou" - "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/log" - "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/widgets/action" ) @@ -14,15 +8,18 @@ var processActionDefaults = map[string]*action.Process{ "Setting": { Name: "yao.chart.Setting", + Guard: "bearer-jwt", Process: "yao.chart.Xgen", Default: []interface{}{nil}, }, "Component": { Name: "yao.chart.Component", + Guard: "bearer-jwt", Default: []interface{}{nil, nil, nil}, }, "Data": { Name: "yao.chart.Data", + Guard: "bearer-jwt", Default: []interface{}{nil}, }, } @@ -42,80 +39,4 @@ func (act *ActionDSL) SetDefaultProcess() { WithBefore(act.BeforeData).WithAfter(act.AfterData). Merge(processActionDefaults["Data"]). SetHandler(processHandler) - -} - -func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { - - form, err := Get(process) - if err != nil { - return nil, err - } - args := p.Args(process) - - // Process - name := p.Process - if name == "" { - name = p.ProcessBind - } - - if name == "" { - log.Error("[chart] %s %s process is required", form.ID, p.Name) - return nil, fmt.Errorf("[chart] %s %s process is required", form.ID, p.Name) - } - - // Before Hook - if p.Before != nil { - log.Trace("[chart] %s %s before: exec(%v)", form.ID, p.Name, args) - newArgs, err := p.Before.Exec(args, process.Sid, process.Global) - if err != nil { - log.Error("[chart] %s %s before: %s", form.ID, p.Name, err.Error()) - } else { - log.Trace("[chart] %s %s before: args:%v", form.ID, p.Name, args) - args = newArgs - } - } - - // Execute Process - act, err := gou.ProcessOf(name, args...) - if err != nil { - log.Error("[chart] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - return nil, fmt.Errorf("[chart] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - - res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec() - if err != nil { - log.Error("[chart] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - return nil, fmt.Errorf("[chart] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - - // Compute Out - switch p.Name { - - case "yao.chart.Data": - switch res.(type) { - case map[string]interface{}, maps.MapStr: - data := any.MapOf(res).MapStrAny - err := form.computeData(process, data) - if err != nil { - log.Error("[chart] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - res = data - } - break - } - - // After hook - if p.After != nil { - log.Trace("[chart] %s %s after: exec(%v)", form.ID, p.Name, res) - newRes, err := p.After.Exec(res, process.Sid, process.Global) - if err != nil { - log.Error("[chart] %s %s after: %s", form.ID, p.Name, err.Error()) - } else { - log.Trace("[chart] %s %s after: %v", form.ID, p.Name, newRes) - res = newRes - } - } - - return res, nil } diff --git a/widgets/chart/api.go b/widgets/chart/api.go index 73736dca..2a465cc0 100644 --- a/widgets/chart/api.go +++ b/widgets/chart/api.go @@ -1,11 +1,63 @@ package chart import ( + "fmt" + + "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/widgets/action" ) +// Guard form widget chart +func Guard(c *gin.Context) { + + id := c.Param("id") + if id == "" { + abort(c, 400, "the chart widget id does not found") + return + } + + chart, has := Charts[id] + if !has { + abort(c, 404, fmt.Sprintf("the chart widget %s does not exist", id)) + return + } + + act, err := chart.getAction(c.FullPath()) + if err != nil { + abort(c, 404, err.Error()) + return + } + + err = act.UseGuard(c, id) + if err != nil { + abort(c, 400, err.Error()) + return + } + +} + +func abort(c *gin.Context, code int, message string) { + c.JSON(code, gin.H{"code": code, "message": message}) + c.Abort() +} + +func (chart *DSL) getAction(path string) (*action.Process, error) { + + switch path { + case "/api/__yao/chart/:id/setting": + return chart.Action.Setting, nil + case "/api/__yao/chart/:id/component/:xpath/:method": + return chart.Action.Component, nil + case "/api/__yao/chart/:id/data": + return chart.Action.Data, nil + } + + return nil, fmt.Errorf("the form widget %s %s action does not exist", chart.ID, path) +} + // export API func exportAPI() error { @@ -13,7 +65,7 @@ func exportAPI() error { Name: "Widget Form API", Description: "Widget Form API", Version: share.VERSION, - Guard: "-", + Guard: "widget-chart", Group: "__yao/chart", Paths: []gou.Path{}, } diff --git a/widgets/chart/chart.go b/widgets/chart/chart.go index 1eeb9d1d..efa90d79 100644 --- a/widgets/chart/chart.go +++ b/widgets/chart/chart.go @@ -37,11 +37,10 @@ var Charts map[string]*DSL = map[string]*DSL{} // New create a new DSL func New(id string) *DSL { return &DSL{ - ID: id, - Fields: &FieldsDSL{Chart: field.Columns{}, Filter: field.Filters{}}, - CProps: field.CloudProps{}, - ComputesIn: field.ComputeFields{}, - ComputesOut: field.ComputeFields{}, + ID: id, + Fields: &FieldsDSL{Chart: field.Columns{}, Filter: field.Filters{}}, + CProps: field.CloudProps{}, + Config: map[string]interface{}{}, } } @@ -143,10 +142,13 @@ func MustGet(chart interface{}) *DSL { func (dsl *DSL) Parse() error { // ComputeFields - // dsl.Fields.Chart.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) + err := dsl.computeMapping() + if err != nil { + return err + } // Filters - err := dsl.Fields.Filter.CPropsMerge(dsl.CProps, func(name string, filter field.FilterDSL) (xpath string) { + err = dsl.Fields.Filter.CPropsMerge(dsl.CProps, func(name string, filter field.FilterDSL) (xpath string) { return fmt.Sprintf("fields.filter.%s.edit.props", name) }) @@ -173,6 +175,11 @@ func (dsl *DSL) Xgen() (map[string]interface{}, error) { return nil, err } + // full width default value + if _, has := dsl.Config["full"]; !has { + dsl.Config["full"] = true + } + setting["fields"] = fields setting["config"] = dsl.Config for _, cProp := range dsl.CProps { diff --git a/widgets/chart/compute.go b/widgets/chart/compute.go index ead1dbca..6e3b4b56 100644 --- a/widgets/chart/compute.go +++ b/widgets/chart/compute.go @@ -2,43 +2,73 @@ package chart import ( "fmt" - "strings" - "github.com/yaoapp/gou" - "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/widgets/compute" + "github.com/yaoapp/yao/widgets/field" ) -func (dsl *DSL) computeData(process *gou.Process, values map[string]interface{}) error { +func (dsl *DSL) getField() func(string) (*field.ColumnDSL, string, error) { + return func(name string) (*field.ColumnDSL, string, error) { + field, has := dsl.Fields.Chart[name] + if !has { + return nil, "fields.chart", fmt.Errorf("fields.chart.%s does not exist", name) + } + return &field, "fields.chart", nil + } +} - messages := []string{} - for key := range values { - err := dsl.computeOut(process, key, values) - if err != nil { - messages = append(messages, err.Error()) +func (dsl *DSL) getFilter() func(string) (*field.FilterDSL, string, error) { + return func(name string) (*field.FilterDSL, string, error) { + field, has := dsl.Fields.Filter[name] + if !has { + return nil, "fields.filter", fmt.Errorf("fields.filter.%s does not exist", name) + } + return &field, "fields.filter", nil + } +} + +func (dsl *DSL) computeMapping() error { + if dsl.Computes == nil { + dsl.Computes = &compute.Maps{ + Filter: map[string][]compute.Unit{}, + Edit: map[string][]compute.Unit{}, + View: map[string][]compute.Unit{}, } } - if len(messages) > 0 { - return fmt.Errorf("%s", strings.Join(messages, ";")) + if dsl.Fields == nil { + return nil + } + + // Filter + if dsl.Fields.Filter != nil && dsl.Layout.Filter != nil && dsl.Layout.Filter.Columns != nil { + for _, inst := range dsl.Layout.Filter.Columns { + if filter, has := dsl.Fields.Filter[inst.Name]; has { + if filter.Edit != nil && filter.Edit.Compute != nil { + bind := filter.FilterBind() + if _, has := dsl.Computes.Filter[bind]; !has { + dsl.Computes.Filter[bind] = []compute.Unit{} + } + dsl.Computes.Filter[bind] = append(dsl.Computes.Filter[bind], compute.Unit{Name: inst.Name, Kind: compute.Filter}) + } + } + } + } + + if dsl.Fields.Chart != nil && dsl.Layout.Chart != nil && dsl.Layout.Chart.Columns != nil { + for _, inst := range dsl.Layout.Chart.Columns { + if field, has := dsl.Fields.Chart[inst.Name]; has { + // View + if field.View != nil && field.View.Compute != nil { + bind := field.ViewBind() + if _, has := dsl.Computes.View[bind]; !has { + dsl.Computes.View[bind] = []compute.Unit{} + } + dsl.Computes.View[bind] = append(dsl.Computes.View[bind], compute.Unit{Name: inst.Name, Kind: compute.View}) + } + } + } } return nil } - -func (dsl *DSL) computeOut(process *gou.Process, key string, values map[string]interface{}) error { - if name, has := dsl.ComputesOut[key]; has { - compute, err := gou.ProcessOf(name, key, values[key], values) - if err != nil { - log.Error("[chart] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[chart] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - } - - res, err := compute.WithGlobal(process.Global).WithSID(process.Sid).Exec() - if err != nil { - log.Error("[chart] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[chart] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - } - values[key] = res - } - return nil -} diff --git a/widgets/chart/handler.go b/widgets/chart/handler.go new file mode 100644 index 00000000..5aa6f6bf --- /dev/null +++ b/widgets/chart/handler.go @@ -0,0 +1,85 @@ +package chart + +import ( + "fmt" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/widgets/action" +) + +// ******************************** +// * Execute the process of form * +// ******************************** +// Life-Circle: Before Hook → Run Process → Compute View → After Hook +// Execute Compute View On: Data +func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { + + chart, err := Get(process) + if err != nil { + return nil, err + } + args := p.Args(process) + + // Process + name := p.Process + if name == "" { + name = p.ProcessBind + } + + if name == "" { + log.Error("[chart] %s %s process is required", chart.ID, p.Name) + return nil, fmt.Errorf("[chart] %s %s process is required", chart.ID, p.Name) + } + + // Compute Filter + err = chart.ComputeFilter(p.Name, process, args, chart.getFilter()) + if err != nil { + log.Error("[chart] %s %s Compute Filter Error: %s", chart.ID, p.Name, err.Error()) + } + + // Before Hook + if p.Before != nil { + log.Trace("[chart] %s %s before: exec(%v)", chart.ID, p.Name, args) + newArgs, err := p.Before.Exec(args, process.Sid, process.Global) + if err != nil { + log.Error("[chart] %s %s before: %s", chart.ID, p.Name, err.Error()) + } else { + log.Trace("[chart] %s %s before: args:%v", chart.ID, p.Name, args) + args = newArgs + } + } + + // Execute Process + act, err := gou.ProcessOf(name, args...) + if err != nil { + log.Error("[chart] %s %s -> %s %s", chart.ID, p.Name, name, err.Error()) + return nil, fmt.Errorf("[chart] %s %s -> %s %s", chart.ID, p.Name, name, err.Error()) + } + + res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec() + if err != nil { + log.Error("[chart] %s %s -> %s %s", chart.ID, p.Name, name, err.Error()) + return nil, fmt.Errorf("[chart] %s %s -> %s %s", chart.ID, p.Name, name, err.Error()) + } + + // Compute View + err = chart.ComputeView(p.Name, process, res, chart.getField()) + if err != nil { + log.Error("[chart] %s %s Compute View Error: %s", chart.ID, p.Name, err.Error()) + } + + // After hook + if p.After != nil { + log.Trace("[chart] %s %s after: exec(%v)", chart.ID, p.Name, res) + newRes, err := p.After.Exec(res, process.Sid, process.Global) + if err != nil { + log.Error("[chart] %s %s after: %s", chart.ID, p.Name, err.Error()) + } else { + log.Trace("[chart] %s %s after: %v", chart.ID, p.Name, newRes) + res = newRes + } + } + + return res, nil +} diff --git a/widgets/chart/types.go b/widgets/chart/types.go index e5a88830..5ea3fe1d 100644 --- a/widgets/chart/types.go +++ b/widgets/chart/types.go @@ -3,21 +3,21 @@ package chart import ( "github.com/yaoapp/yao/widgets/action" "github.com/yaoapp/yao/widgets/component" + "github.com/yaoapp/yao/widgets/compute" "github.com/yaoapp/yao/widgets/field" "github.com/yaoapp/yao/widgets/hook" ) // DSL the chart DSL type DSL struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Action *ActionDSL `json:"action"` - Layout *LayoutDSL `json:"layout"` - Fields *FieldsDSL `json:"fields"` - Config map[string]interface{} `json:"config,omitempty"` - ComputesIn field.ComputeFields `json:"-"` - ComputesOut field.ComputeFields `json:"-"` - CProps field.CloudProps `json:"-"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Action *ActionDSL `json:"action"` + Layout *LayoutDSL `json:"layout"` + Fields *FieldsDSL `json:"fields"` + Config map[string]interface{} `json:"config,omitempty"` + CProps field.CloudProps `json:"-"` + compute.Computable } // ActionDSL the chart action DSL @@ -31,14 +31,23 @@ type ActionDSL struct { // FieldsDSL the chart fields DSL type FieldsDSL struct { - Filter field.Filters `json:"filter,omitempty"` - Chart field.Columns `json:"chart,omitempty"` + Filter field.Filters `json:"filter,omitempty"` + Chart field.Columns `json:"chart,omitempty"` + filterMap map[string]field.FilterDSL + chartMap map[string]field.ColumnDSL } // LayoutDSL the chart layout DSL type LayoutDSL struct { Operation *OperationLayoutDSL `json:"operation,omitempty"` Chart *ViewLayoutDSL `json:"chart,omitempty"` + Filter *FilterLayoutDSL `json:"filter,omitempty"` +} + +// FilterLayoutDSL layout.filter +type FilterLayoutDSL struct { + Actions component.Actions `json:"actions,omitempty"` + Columns component.Instances `json:"columns,omitempty"` } // OperationLayoutDSL layout.operation diff --git a/widgets/compute/compute.go b/widgets/compute/compute.go index c42492b1..a4b2dcc2 100644 --- a/widgets/compute/compute.go +++ b/widgets/compute/compute.go @@ -11,7 +11,7 @@ import ( "github.com/yaoapp/yao/widgets/field" ) -var views = map[string]bool{"find": true, "get": true, "search": true} +var views = map[string]bool{"find": true, "get": true, "search": true, "data": true} // ComputeEdit edit compute edit func (c *Computable) ComputeEdit(name string, process *gou.Process, args []interface{}, getField func(string) (*field.ColumnDSL, string, error)) error {