Skip to content

Commit

Permalink
+ Chart 处理器
Browse files Browse the repository at this point in the history
  • Loading branch information
trheyi committed Oct 19, 2021
1 parent afd781c commit 2a45703
Show file tree
Hide file tree
Showing 13 changed files with 520 additions and 30 deletions.
60 changes: 60 additions & 0 deletions chart/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package chart

import (
"github.com/yaoapp/xiang/share"
)

// SetupAPIs 设定API数据
func (chart *Chart) SetupAPIs() {

defaults := map[string]share.API{
"data": apiDataDefault(),
"setting": apiSettingDefault(),
}

// 开发者填写的规则
for name := range chart.APIs {
if _, has := defaults[name]; !has {
delete(chart.APIs, name)
continue
}

api := defaults[name]
api.Name = name
if chart.APIs[name].Process != "" {
api.Process = chart.APIs[name].Process
}

if chart.APIs[name].Guard != "" {
api.Guard = chart.APIs[name].Guard
}

if chart.APIs[name].Default != nil {
api.Default = chart.APIs[name].Default
}

defaults[name] = api
}

chart.APIs = defaults
}

// apiSearchDefault data 接口默认值
func apiDataDefault() share.API {
param := map[string]interface{}{}
return share.API{
Name: "data",
Guard: "bearer-jwt",
Process: "xiang.chart.data",
Default: []interface{}{param},
}
}

// apiSettingDefault setting 接口默认值
func apiSettingDefault() share.API {
return share.API{
Name: "setting",
Guard: "bearer-jwt",
Process: "xiang.chart.setting",
}
}
28 changes: 27 additions & 1 deletion chart/chart.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package chart

import (
"fmt"

jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou"
"github.com/yaoapp/kun/exception"
"github.com/yaoapp/xiang/config"
"github.com/yaoapp/xiang/share"
Expand Down Expand Up @@ -36,7 +39,11 @@ func LoadFrom(dir string, prefix string) {

// LoadChart 载入数据表格
func LoadChart(source []byte, name string) (*Chart, error) {
chart := Chart{}
chart := Chart{
Flow: gou.Flow{
Name: name,
},
}
err := jsoniter.Unmarshal(source, &chart)
if err != nil {
xlog.Println(name)
Expand All @@ -45,5 +52,24 @@ func LoadChart(source []byte, name string) (*Chart, error) {
return nil, err
}
chart.Prepare()
chart.SetupAPIs()

return &chart, nil
}

// Select 读取已加载图表
func Select(name string) *Chart {
chart, has := Charts[name]
if !has {
exception.New(
fmt.Sprintf("Chart:%s; 尚未加载", name),
400,
).Throw()
}
return chart
}

// GetData 运行 flow 返回数值
func (chart Chart) GetData(params map[string]interface{}) interface{} {
return chart.Flow.Exec(params)
}
82 changes: 82 additions & 0 deletions chart/process.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package chart

import (
"strings"

"github.com/yaoapp/gou"
"github.com/yaoapp/kun/maps"
)

// 注册处理器
func init() {
gou.RegisterProcessHandler("xiang.chart.data", ProcessData)
gou.RegisterProcessHandler("xiang.chart.setting", ProcessSetting)
}

// ProcessData xiang.chart.data
// 查询数据分析图表中定义的数据
func ProcessData(process *gou.Process) interface{} {

process.ValidateArgNums(3)
name := process.ArgsString(0)
params := process.ArgsMap(1)
chart := Select(name)
api := chart.APIs["data"]
if process.NumOfArgsIs(4) && api.IsAllow(process.Args[3]) {
return nil
}

if len(api.Default) > 0 {
if defaults, ok := api.Default[0].(map[string]interface{}); ok {
for key, value := range defaults {
if !params.Has(key) {
params.Set(key, value)
}
}
}
}

return chart.GetData(params)
}

// ProcessSetting xiang.chart.setting
// 查询数据分析图表中定义的数据
func ProcessSetting(process *gou.Process) interface{} {

process.ValidateArgNums(2)
name := process.ArgsString(0)
field := process.ArgsString(1)
chart := Select(name)
api := chart.APIs["setting"]
if process.NumOfArgsIs(2) && api.IsAllow(process.Args[1]) {
return nil
}

fields := strings.Split(field, ",")
setting := maps.Map{
"name": chart.Name,
"label": chart.Label,
"version": chart.Version,
"description": chart.Description,
"filters": chart.Filters,
"page": chart.Page,
}

if len(fields) == 1 && setting.Has(fields[0]) {
field := strings.TrimSpace(fields[0])
return setting.Get(field)
}

if len(fields) > 1 {
res := maps.Map{}
for _, field := range fields {
field = strings.TrimSpace(field)
if setting.Has(field) {
res.Set(field, setting.Get(field))
}
}
return res
}
return setting

}
77 changes: 77 additions & 0 deletions chart/process_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package chart

import (
"net/url"
"testing"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/yaoapp/gou"
"github.com/yaoapp/kun/any"
"github.com/yaoapp/kun/utils"
"github.com/yaoapp/xiang/config"
_ "github.com/yaoapp/xiang/helper"
"github.com/yaoapp/xiang/model"
"github.com/yaoapp/xiang/query"
"github.com/yaoapp/xiang/share"
)

func init() {
share.DBConnect(config.Conf.Database)
model.Load(config.Conf)
query.Load(config.Conf)
Load(config.Conf)
}
func TestProcessSetting(t *testing.T) {

args := []interface{}{
"service.compare",
nil,
&gin.Context{},
}
process := gou.NewProcess("xiang.chart.Setting", args...)
response := ProcessSetting(process)
assert.NotNil(t, response)
res := any.Of(response).Map()
assert.True(t, res.Has("name"))
assert.True(t, res.Has("label"))
assert.True(t, res.Has("description"))
assert.True(t, res.Has("page"))
assert.True(t, res.Has("version"))

args = []interface{}{
"service.compare",
"page,name",
&gin.Context{},
}
process = gou.NewProcess("xiang.chart.Setting", args...)
response = ProcessSetting(process)
assert.NotNil(t, response)

res = any.Of(response).Map()
assert.True(t, res.Has("name"))
assert.True(t, res.Has("page"))
assert.False(t, res.Has("label"))
}

func TestProcessData(t *testing.T) {

params := url.Values{
"from": []string{"1981-01-01", "1990-01-01"},
}
params.Set("to", "2049-12-31")

args := []interface{}{
"service.compare",
params,
&gin.Context{},
}
process := gou.NewProcess("xiang.chart.Data", args...)
response := ProcessData(process)
utils.Dump(response)

assert.NotNil(t, response)

res := any.Of(response).Map().Dot()
assert.Equal(t, "北京", res.Get("合并.0.城市"))
}
9 changes: 1 addition & 8 deletions chart/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import (
// Chart 图表格式
type Chart struct {
gou.Flow
APIs map[string]API `json:"apis,omitempty"`
APIs map[string]share.API `json:"apis,omitempty"`
Filters map[string]share.Filter `json:"filters,omitempty"`
Page share.Page `json:"page,omitempty"`
}

// API 图表 API
type API struct {
Disable bool `json:"disable,omitempty"`
Guard string `json:"guard,omitempty"`
Default interface{} `json:"default,omitempty"`
}
78 changes: 78 additions & 0 deletions helper/array.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package helper

import (
"fmt"

jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/kun/exception"
)

// ArrayPluckValue ArrayPluck 参数
type ArrayPluckValue struct {
Key string `json:"key"`
Value string `json:"value"`
Items []map[string]interface{} `json:"items"`
}

// ArrayPluck 将多个数据记录集合,合并为一个数据记录集合
// columns: ["城市", "行业", "计费"]
// pluck: {
// "行业":{"key":"city", "value":"数量", "items":[{"city":"北京", "数量":32},{"city":"上海", "数量":20}]},
// "计费":{"key":"city", "value":"计费种类", "items":[{"city":"北京", "计费种类":6},{"city":"西安", "计费种类":3}]},
// }
// return: [
// {"城市":"北京", "行业":32, "计费":6},
// {"城市":"上海", "行业":20, "计费":null},
// {"城市":"西安", "行业":null, "计费":6}
// ]
func ArrayPluck(columns []string, pluck map[string]interface{}) []map[string]interface{} {
if len(columns) < 2 {
exception.New("ArrayPluck 参数错误, 应至少包含两列。", 400).Ctx(columns).Throw()
}

primary := columns[0]
data := map[string]map[string]interface{}{}

// 解析数据
for name, value := range pluck { // name="行业", value={"key":"city", "value":"数量", "items":[{"city":"北京", "数量":32},{"city":"上海", "数量":20}]},
arg := OfArrayPluckValue(value)
for _, item := range arg.Items { // item = [{"city":"北京", "数量":32},{"city":"上海", "数量":20}]
if v, has := item[arg.Key]; has { // arg.Key = "city"
key := fmt.Sprintf("%#v", v) // key = `"北京"`
val := item[arg.Value] // arg.Value = "数量", val = 32
if _, has := data[key]; !has {
data[key] = map[string]interface{}{} // {`"北京"`: {}}
data[key][primary] = v // {`"北京"`: {"城市":"北京"}}
}
data[key][name] = val // {`"北京"`: {"城市":"北京", "行业":32}}
}
}
}

// 空值处理
res := []map[string]interface{}{}
for key := range data { // key = `"北京"`
for name := range pluck { // name = "行业"
if _, has := data[key][name]; !has {
data[key][name] = nil
}
}
res = append(res, data[key])
}

return res
}

// OfArrayPluckValue Any 转 ArrayPluckValue
func OfArrayPluckValue(any interface{}) ArrayPluckValue {
content, err := jsoniter.Marshal(any)
if err != nil {
exception.New("ArrayPluck 参数错误", 400).Ctx(err.Error()).Throw()
}
value := ArrayPluckValue{Items: []map[string]interface{}{}}
err = jsoniter.Unmarshal(content, &value)
if err != nil {
exception.New("ArrayPluck 参数错误", 400).Ctx(err.Error()).Throw()
}
return value
}
23 changes: 23 additions & 0 deletions helper/array_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package helper

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/yaoapp/kun/maps"
)

func TestArrayPluck(t *testing.T) {
columns := []string{"城市", "行业", "计费"}
pluck := map[string]interface{}{
"行业": map[string]interface{}{"key": "city", "value": "数量", "items": []map[string]interface{}{{"city": "北京", "数量": 32}, {"city": "上海", "数量": 20}}},
"计费": map[string]interface{}{"key": "city", "value": "计费种类", "items": []map[string]interface{}{{"city": "北京", "计费种类": 6}, {"city": "西安", "计费种类": 3}}},
}
items := ArrayPluck(columns, pluck)
assert.Equal(t, 3, len(items))
for _, item := range items {
maps.Of(item).Has("城市")
maps.Of(item).Has("行业")
maps.Of(item).Has("计费")
}
}
Loading

0 comments on commit 2a45703

Please sign in to comment.