作者 yangfu

chart update

1 Name: chart 1 Name: chart
2 Host: 0.0.0.0 2 Host: 0.0.0.0
3 -Port: 8081  
4 -Verbose: false 3 +Port: 8080
  4 +Verbose: true
5 5
6 JwtAuth: 6 JwtAuth:
7 AccessSecret: digital-platform 7 AccessSecret: digital-platform
@@ -12,7 +12,7 @@ Redis: @@ -12,7 +12,7 @@ Redis:
12 Type: node 12 Type: node
13 Pass: 13 Pass:
14 DB: 14 DB:
15 - DataSource: host=106.52.103.187 user=postgres password=UYXN134KUm8TeE7 dbname=skateboard-test port=25431 sslmode=disable TimeZone=Asia/Shanghai 15 + DataSource: host=114.55.200.59 user=postgres password=eagle1010 dbname=sumifcc-bchart-dev port=31543 sslmode=disable TimeZone=Asia/Shanghai
16 16
17 ByteMetadata: 17 ByteMetadata:
18 Name: ApiByteMetadata 18 Name: ApiByteMetadata
@@ -2,6 +2,7 @@ package chart @@ -2,6 +2,7 @@ package chart
2 2
3 import ( 3 import (
4 "context" 4 "context"
  5 + "fmt"
5 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/interanl/pkg/domain" 6 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/interanl/pkg/domain"
6 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/pkg/contextdata" 7 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/pkg/contextdata"
7 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/pkg/xerr" 8 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/pkg/xerr"
@@ -31,6 +32,7 @@ func (l *DeleteChartLogic) DeleteChart(req *types.ChartDeleteRequest) (resp *typ @@ -31,6 +32,7 @@ func (l *DeleteChartLogic) DeleteChart(req *types.ChartDeleteRequest) (resp *typ
31 conn = l.svcCtx.DefaultDBConn() 32 conn = l.svcCtx.DefaultDBConn()
32 chart *domain.Chart 33 chart *domain.Chart
33 tenantId = contextdata.GetTenantFromCtx(l.ctx) 34 tenantId = contextdata.GetTenantFromCtx(l.ctx)
  35 + appPage *domain.AppPage
34 ) 36 )
35 resp = &types.ChartDeleteResponse{} 37 resp = &types.ChartDeleteResponse{}
36 if chart, err = l.svcCtx.ChartRepository.FindOne(l.ctx, conn, req.Id); err != nil { 38 if chart, err = l.svcCtx.ChartRepository.FindOne(l.ctx, conn, req.Id); err != nil {
@@ -39,8 +41,13 @@ func (l *DeleteChartLogic) DeleteChart(req *types.ChartDeleteRequest) (resp *typ @@ -39,8 +41,13 @@ func (l *DeleteChartLogic) DeleteChart(req *types.ChartDeleteRequest) (resp *typ
39 if chart.TenantId != tenantId { 41 if chart.TenantId != tenantId {
40 return nil, xerr.NewErrMsgErr("无权限", nil) 42 return nil, xerr.NewErrMsgErr("无权限", nil)
41 } 43 }
42 - // TODO:图表被引用不能删除  
43 - // TODO:删除下级图表 44 + // 图表被引用不能删除
  45 + if appPage, err = l.svcCtx.AppPageRepository.FindOneByChartId(l.ctx, conn, tenantId, chart.Id); err != nil {
  46 + return nil, xerr.NewErrMsgErr("删除失败", err)
  47 + }
  48 + if appPage.Identify() != nil {
  49 + return nil, xerr.NewErrMsgErr(fmt.Sprintf("该图表已被页面\"%v\"引用", appPage.Name), err)
  50 + }
44 if chart, err = l.svcCtx.ChartRepository.Delete(l.ctx, conn, chart); err != nil { 51 if chart, err = l.svcCtx.ChartRepository.Delete(l.ctx, conn, chart); err != nil {
45 return nil, xerr.NewErrMsgErr("删除失败", err) 52 return nil, xerr.NewErrMsgErr("删除失败", err)
46 } 53 }
@@ -45,6 +45,7 @@ func (l *SaveAsChartLogic) SaveAsChart(req *types.ChartSaveAsRequest) (resp *typ @@ -45,6 +45,7 @@ func (l *SaveAsChartLogic) SaveAsChart(req *types.ChartSaveAsRequest) (resp *typ
45 Pid: 0, 45 Pid: 0,
46 Type: chart.Type, 46 Type: chart.Type,
47 ChartType: chart.ChartType, 47 ChartType: chart.ChartType,
  48 + Name: req.Name,
48 ChartProperty: types.NewPropertyItem(chartSetting.ChartProperty(chart.Cover)), 49 ChartProperty: types.NewPropertyItem(chartSetting.ChartProperty(chart.Cover)),
49 }) 50 })
50 return 51 return
@@ -47,6 +47,7 @@ func (l *SaveChartLogic) SaveChart(req *types.ChartSaveRequest) (resp *types.Cha @@ -47,6 +47,7 @@ func (l *SaveChartLogic) SaveChart(req *types.ChartSaveRequest) (resp *types.Cha
47 Group: tool.Krand(10, tool.KC_RAND_KIND_UPPER), 47 Group: tool.Krand(10, tool.KC_RAND_KIND_UPPER),
48 TenantId: tenantId, 48 TenantId: tenantId,
49 ChartType: req.ChartType, 49 ChartType: req.ChartType,
  50 + Cover: req.Cover,
50 } 51 }
51 if chart.Name == "" { 52 if chart.Name == "" {
52 chart.Name = chart.RandName() 53 chart.Name = chart.RandName()
@@ -2,6 +2,10 @@ package chart @@ -2,6 +2,10 @@ package chart
2 2
3 import ( 3 import (
4 "context" 4 "context"
  5 + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/interanl/pkg/db/transaction"
  6 + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/interanl/pkg/domain"
  7 + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/pkg/contextdata"
  8 + "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/pkg/xerr"
5 9
6 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/api/internal/svc" 10 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/api/internal/svc"
7 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/api/internal/types" 11 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/api/internal/types"
@@ -24,7 +28,38 @@ func NewUpdateChartLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Updat @@ -24,7 +28,38 @@ func NewUpdateChartLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Updat
24 } 28 }
25 29
26 func (l *UpdateChartLogic) UpdateChart(req *types.ChartUpdateRequest) (resp *types.ChartUpdateResponse, err error) { 30 func (l *UpdateChartLogic) UpdateChart(req *types.ChartUpdateRequest) (resp *types.ChartUpdateResponse, err error) {
27 - // todo: add your logic here and delete this line 31 + var (
  32 + conn = l.svcCtx.DefaultDBConn()
  33 + chart *domain.Chart
  34 + chartSetting *domain.ChartSetting
  35 + tenantId = contextdata.GetTenantFromCtx(l.ctx)
  36 + )
28 37
  38 + if chart, err = l.svcCtx.ChartRepository.FindOne(l.ctx, conn, req.Id); err != nil || chart.TenantId != tenantId {
  39 + return nil, xerr.NewErrMsgErr("图表不存在", err)
  40 + }
  41 + if chartSetting, err = l.svcCtx.ChartSettingRepository.FindOne(l.ctx, conn, req.Id); err != nil {
  42 + return nil, xerr.NewErrMsgErr("图表配置不存在", err)
  43 + }
  44 +
  45 + if err = transaction.UseTrans(l.ctx, l.svcCtx.DB, func(ctx context.Context, conn transaction.Conn) error {
  46 + if len(req.Cover) > 0 && chart.Cover != req.Cover {
  47 + chart.Cover = req.Cover
  48 + }
  49 + if chart, err = l.svcCtx.ChartRepository.UpdateWithVersion(l.ctx, conn, chart); err != nil {
  50 + return err
  51 + }
  52 + chartProperty := types.NewProperty(req.ChartProperty)
  53 + chartSetting.Title = chartProperty.Title
  54 + chartSetting.TableAbility = chartProperty.TableAbility
  55 + chartSetting.Series = chartProperty.Series
  56 + chartSetting.Other = chartProperty.Other
  57 + if chartSetting, err = l.svcCtx.ChartSettingRepository.UpdateWithVersion(l.ctx, conn, chartSetting); err != nil {
  58 + return err
  59 + }
  60 + return nil
  61 + }, true); err != nil {
  62 + return nil, xerr.NewErrMsgErr("创建失败", err)
  63 + }
29 return 64 return
30 } 65 }
@@ -35,7 +35,9 @@ func (l *CreateAppPageShareUrlLogic) CreateAppPageShareUrl(req *types.AppPageCre @@ -35,7 +35,9 @@ func (l *CreateAppPageShareUrlLogic) CreateAppPageShareUrl(req *types.AppPageCre
35 if appPage, err = l.svcCtx.AppPageRepository.FindOne(l.ctx, conn, req.Id); err != nil { 35 if appPage, err = l.svcCtx.AppPageRepository.FindOne(l.ctx, conn, req.Id); err != nil {
36 return nil, xerr.NewErr(err) 36 return nil, xerr.NewErr(err)
37 } 37 }
38 - resp = &types.AppPageCreateShareResponse{} 38 + resp = &types.AppPageCreateShareResponse{
  39 + Key: appPage.Key,
  40 + }
39 if appPage.Key != "" { 41 if appPage.Key != "" {
40 return 42 return
41 } 43 }
@@ -31,6 +31,7 @@ func (l *GetTableDetailLogic) GetTableDetail(req *types.GetTableDetailRequest) ( @@ -31,6 +31,7 @@ func (l *GetTableDetailLogic) GetTableDetail(req *types.GetTableDetailRequest) (
31 logx.Error(err) 31 logx.Error(err)
32 return resp, xerr.NewErr(err) 32 return resp, xerr.NewErr(err)
33 } 33 }
  34 + response.Fields = removeIdField(response.Fields)
34 resp = response 35 resp = response
35 return 36 return
36 } 37 }
@@ -40,9 +40,10 @@ func (l *SearchTableByModuleLogic) SearchTableByModule(req *types.SearchTableByM @@ -40,9 +40,10 @@ func (l *SearchTableByModuleLogic) SearchTableByModule(req *types.SearchTableByM
40 result["导入模块"] = newList(list) 40 result["导入模块"] = newList(list)
41 }, func() { 41 }, func() {
42 list, err := l.svcCtx.ByteMetadataService.ObjectTableSearch(l.ctx, bytelib.ObjectTableSearchRequest{ 42 list, err := l.svcCtx.ByteMetadataService.ObjectTableSearch(l.ctx, bytelib.ObjectTableSearchRequest{
43 - Token: req.Token,  
44 - TableTypes: []string{bytelib.SchemaTable},  
45 - Module: bytelib.ModuleQuerySetCenter, 43 + Token: req.Token,
  44 + TableTypes: []string{bytelib.SchemaTable},
  45 + Module: bytelib.ModuleQuerySetCenter,
  46 + ReturnGroupItem: true,
46 }) 47 })
47 if err != nil { 48 if err != nil {
48 batchError.Add(err) 49 batchError.Add(err)
@@ -50,9 +51,10 @@ func (l *SearchTableByModuleLogic) SearchTableByModule(req *types.SearchTableByM @@ -50,9 +51,10 @@ func (l *SearchTableByModuleLogic) SearchTableByModule(req *types.SearchTableByM
50 result["拆解模块"] = newList(list) 51 result["拆解模块"] = newList(list)
51 }, func() { 52 }, func() {
52 list, err := l.svcCtx.ByteMetadataService.ObjectTableSearch(l.ctx, bytelib.ObjectTableSearchRequest{ 53 list, err := l.svcCtx.ByteMetadataService.ObjectTableSearch(l.ctx, bytelib.ObjectTableSearchRequest{
53 - Token: req.Token,  
54 - TableTypes: []string{bytelib.CalculateItem, bytelib.CalculateSet},  
55 - Module: bytelib.ModuleCalculateCenter, 54 + Token: req.Token,
  55 + TableTypes: []string{bytelib.CalculateItem, bytelib.CalculateSet},
  56 + Module: bytelib.ModuleCalculateCenter,
  57 + ReturnGroupItem: true,
56 }) 58 })
57 if err != nil { 59 if err != nil {
58 batchError.Add(err) 60 batchError.Add(err)
@@ -41,9 +41,20 @@ func (l *SearchTableDataLogic) SearchTableData(req *types.SearchTableDataRequest @@ -41,9 +41,20 @@ func (l *SearchTableDataLogic) SearchTableData(req *types.SearchTableDataRequest
41 } 41 }
42 resp = map[string]interface{}{ 42 resp = map[string]interface{}{
43 "objectId": response.ObjectId, 43 "objectId": response.ObjectId,
44 - "fields": response.Fields, 44 + "fields": removeIdField(response.Fields),
45 "total": response.Grid.Total, 45 "total": response.Grid.Total,
46 "data": response.Grid.List, 46 "data": response.Grid.List,
47 } 47 }
48 return 48 return
49 } 49 }
  50 +
  51 +func removeIdField(fields []*bytelib.Field) []*bytelib.Field {
  52 + var result = make([]*bytelib.Field, 0)
  53 + for _, f := range fields {
  54 + if f.SQLName == "id" {
  55 + continue
  56 + }
  57 + result = append(result, f)
  58 + }
  59 + return result
  60 +}
@@ -31,8 +31,7 @@ func (l *SearchTableFieldOptionalValuesLogic) SearchTableFieldOptionalValues(req @@ -31,8 +31,7 @@ func (l *SearchTableFieldOptionalValuesLogic) SearchTableFieldOptionalValues(req
31 ObjectType: bytelib.ObjectMetaTable, 31 ObjectType: bytelib.ObjectMetaTable,
32 ObjectId: req.ObjectId, 32 ObjectId: req.ObjectId,
33 Field: bytelib.Field{ 33 Field: bytelib.Field{
34 - Name: req.Field,  
35 - SQLName: req.SqlName, 34 + Name: req.Field,
36 }, 35 },
37 //PageSize: req.PageSize, 36 //PageSize: req.PageSize,
38 //PageNumber: req.PageNumber, 37 //PageNumber: req.PageNumber,
@@ -61,8 +60,7 @@ func newWhere(conditions []*types.Condition) *bytelib.TableQueryWhere { @@ -61,8 +60,7 @@ func newWhere(conditions []*types.Condition) *bytelib.TableQueryWhere {
61 } 60 }
62 where.Conditions = append(where.Conditions, &bytelib.TableQueryCondition{ 61 where.Conditions = append(where.Conditions, &bytelib.TableQueryCondition{
63 Field: &bytelib.Field{ 62 Field: &bytelib.Field{
64 - Name: c.FieldName,  
65 - SQLName: c.SqlName, 63 + Name: c.FieldName,
66 }, 64 },
67 Like: c.Like, 65 Like: c.Like,
68 In: c.In, 66 In: c.In,
@@ -10,11 +10,12 @@ type ChartGetResponse struct { @@ -10,11 +10,12 @@ type ChartGetResponse struct {
10 } 10 }
11 11
12 type ChartSaveRequest struct { 12 type ChartSaveRequest struct {
13 - Pid int64 `json:"pid,optional"` // 父级ID  
14 - Type string `json:"type"` // 类型 report:报表 group:分组 chart:图表  
15 - Name string `json:"name,optional"` // 名称  
16 - ChartType string `json:"chartType"` // 图表类型  
17 - ChartProperty ChartProperty `json:"property"` // 图表属性 13 + Pid int64 `json:"pid,optional"` // 父级ID
  14 + Type string `json:"type"` // 类型 report:报表 group:分组 chart:图表
  15 + Name string `json:"name,optional"` // 名称
  16 + ChartType string `json:"chartType"` // 图表类型
  17 + Cover string `json:"cover,optional"` // 封面
  18 + ChartProperty ChartProperty `json:"property"` // 图表属性
18 } 19 }
19 20
20 type ChartSaveResponse struct { 21 type ChartSaveResponse struct {
@@ -39,8 +40,8 @@ type ChartDeleteResponse struct { @@ -39,8 +40,8 @@ type ChartDeleteResponse struct {
39 40
40 type ChartUpdateRequest struct { 41 type ChartUpdateRequest struct {
41 Id int64 `path:"id"` 42 Id int64 `path:"id"`
42 - ChartType string `json:"chartType"` // 图表类型  
43 - ChartProperty ChartProperty `json:"property"` // 图表属性 43 + Cover string `json:"cover,optional"` // 封面
  44 + ChartProperty ChartProperty `json:"property"` // 图表属性
44 } 45 }
45 46
46 type ChartUpdateResponse struct { 47 type ChartUpdateResponse struct {
@@ -79,6 +80,8 @@ type ChartItem struct { @@ -79,6 +80,8 @@ type ChartItem struct {
79 Type string `json:"type,optional"` // 类型 report:报表 group:分组 chart:图表 80 Type string `json:"type,optional"` // 类型 report:报表 group:分组 chart:图表
80 Sort int64 `json:"sort,optional"` // 排序 81 Sort int64 `json:"sort,optional"` // 排序
81 Name string `json:"name,optional"` // 名称 82 Name string `json:"name,optional"` // 名称
  83 + Cover string `json:"cover,optional"` // 封面
  84 + ChartType string `json:"chartType,optional"` // 图表类型
82 ChartProperty *ChartProperty `json:"property,optional,omitempty"` //属性 85 ChartProperty *ChartProperty `json:"property,optional,omitempty"` //属性
83 } 86 }
84 87
@@ -109,7 +112,6 @@ type ChartProperty struct { @@ -109,7 +112,6 @@ type ChartProperty struct {
109 Title Title `json:"title,optional"` // 标题 112 Title Title `json:"title,optional"` // 标题
110 TableAbility TableAbility `json:"table,optional"` // 表筛选功能 113 TableAbility TableAbility `json:"table,optional"` // 表筛选功能
111 Series []Series `json:"series,optional"` // 系列(数据源) 114 Series []Series `json:"series,optional"` // 系列(数据源)
112 - Cover string `json:"cover,optional"` // 封面  
113 Other Other `json:"other,optional"` // 其他额外配置 115 Other Other `json:"other,optional"` // 其他额外配置
114 } 116 }
115 117
@@ -118,24 +120,29 @@ type Other struct { @@ -118,24 +120,29 @@ type Other struct {
118 } 120 }
119 121
120 type Quarter struct { 122 type Quarter struct {
121 - XAxisLabel string `json:"xAxisLabel"` // x轴标签名  
122 - XAxisLabelList []string `json:"xAxisLabelList"` // 标签名  
123 - YAxisLabel string `json:"yAxisLabel"` // x轴标签名  
124 - YAxisLabelList []string `json:"yAxisLabelList"` // 标签名  
125 - Area string `json:"area"` // 图形面积  
126 - SeriesList string `json:"seriesList"` // 图形系列 123 + XAxisLabel string `json:"xAxisLabel"` // x轴标签名
  124 + XAxisFirstLabel string `json:"xAxisFirstLabel"` // 签名1
  125 + XAxisSecondLabel string `json:"xAxisSecondLabel"` // 签名2
  126 + YAxisLabel string `json:"yAxisLabel"` // y轴标签名
  127 + YAxisFirstLabel string `json:"yAxisFirstLabel"` // y标签1
  128 + YAxisSecondLabel string `json:"yAxisSecondLabel"` // y标签2
  129 + Area string `json:"area"` // 图形面积
  130 + SeriesList []QuarterSeries `json:"seriesList"` // 图形系列
  131 +}
  132 +
  133 +type QuarterSeries struct {
127 } 134 }
128 135
129 type Title struct { 136 type Title struct {
130 - TitleSwitch bool `json:"titleSwitch,optional"` // 组件标题开关  
131 - IntroduceSwitch bool `json:"introduceSwitch,optional"` // 组件说明开关  
132 - TitleType string `json:"titleType"` // 标题类型  
133 - Heading string `json:"heading,optional"` // 主标题  
134 - SubTitle string `json:"subTitle,optional"` // 副标题  
135 - ExplainType string `json:"explainType,optional,options=[text,file]"` // text file  
136 - ExplainTxt string `json:"explainTxt,optional"` // 文字说明  
137 - FileUrl string `json:"fileUrl,optional"` // 组件图片/视频  
138 - Align string `json:"align,optional"` // 文本对齐方式 left center right 137 + TitleSwitch bool `json:"titleSwitch,optional"` // 组件标题开关
  138 + IntroduceSwitch bool `json:"introduceSwitch,optional"` // 组件说明开关
  139 + TitleType string `json:"titleType"` // 标题类型
  140 + Heading string `json:"heading,optional"` // 主标题
  141 + SubTitle string `json:"subTitle,optional"` // 副标题
  142 + ExplainType string `json:"explainType,optional,options=[text,,file]"` // text file ,options=text||file
  143 + ExplainTxt string `json:"explainTxt,optional"` // 文字说明
  144 + FileUrl string `json:"fileUrl,optional"` // 组件图片/视频
  145 + Align string `json:"align,optional"` // 文本对齐方式 left center right
139 } 146 }
140 147
141 type TableAbility struct { 148 type TableAbility struct {
@@ -161,8 +168,8 @@ type Expression struct { @@ -161,8 +168,8 @@ type Expression struct {
161 } 168 }
162 169
163 type Dimension struct { 170 type Dimension struct {
164 - Name string `json:"name"`  
165 - Value string `json:"value"` 171 + Name string `json:"name,optional,omitempty"`
  172 + Value string `json:"dimensionVal,optional"`
166 } 173 }
167 174
168 type SearchTableByModuleRequest struct { 175 type SearchTableByModuleRequest struct {
@@ -176,7 +183,6 @@ type SearchTableFieldOptionalValuesRequest struct { @@ -176,7 +183,6 @@ type SearchTableFieldOptionalValuesRequest struct {
176 Token string `header:"x-mmm-accesstoken,optional"` 183 Token string `header:"x-mmm-accesstoken,optional"`
177 ObjectId int `json:"objectId"` // 对象ID 184 ObjectId int `json:"objectId"` // 对象ID
178 Field string `json:"field"` // 当前选择的字段 185 Field string `json:"field"` // 当前选择的字段
179 - SqlName string `json:"sqlName"` // 字段SqlName  
180 Condition []*Condition `json:"conditions,optional"` // 条件 186 Condition []*Condition `json:"conditions,optional"` // 条件
181 } 187 }
182 188
@@ -186,11 +192,10 @@ type SearchTableFieldOptionalValuesResponse struct { @@ -186,11 +192,10 @@ type SearchTableFieldOptionalValuesResponse struct {
186 } 192 }
187 193
188 type Condition struct { 194 type Condition struct {
189 - FieldName string `json:"field"` // 条件字段  
190 - SqlName string `json:"sqlName"` // 字段SqlName  
191 - Like string `json:"like,optional"` // 模糊匹配  
192 - In []string `json:"in,optional"` // 匹配多个值  
193 - Order string `json:"order,optional,options=[ASC,DESC]"` // 排序 ASC DESC 默认ASC 195 + FieldName string `json:"field"` // 条件字段
  196 + Like string `json:"like,optional"` // 模糊匹配
  197 + In []string `json:"in,optional"` // 匹配多个值
  198 + Order string `json:"order,optional,options=ASC||DESC"` // 排序 ASC DESC 默认ASC
194 } 199 }
195 200
196 type GetTableDetailRequest struct { 201 type GetTableDetailRequest struct {
@@ -5,6 +5,7 @@ import ( @@ -5,6 +5,7 @@ import (
5 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/interanl/pkg/domain" 5 "gitlab.fjmaimaimai.com/allied-creation/sumifcc-bchart/cmd/chart-server/interanl/pkg/domain"
6 "gorm.io/gorm" 6 "gorm.io/gorm"
7 "gorm.io/plugin/soft_delete" 7 "gorm.io/plugin/soft_delete"
  8 + "time"
8 ) 9 )
9 10
10 type AppPage struct { 11 type AppPage struct {
@@ -13,7 +14,7 @@ type AppPage struct { @@ -13,7 +14,7 @@ type AppPage struct {
13 Charts []int64 `gorm:"serializer:json"` // 图表 14 Charts []int64 `gorm:"serializer:json"` // 图表
14 Key string // 分享,预览时绑定映射到Id 15 Key string // 分享,预览时绑定映射到Id
15 16
16 - TenantId int64 // 租户ID 17 + TenantId int64 `gorm:"index:idx_app_page_t_id"` // 租户ID
17 CreatedAt int64 `json:",omitempty"` 18 CreatedAt int64 `json:",omitempty"`
18 UpdatedAt int64 `json:",omitempty"` 19 UpdatedAt int64 `json:",omitempty"`
19 DeletedAt int64 `json:",omitempty"` 20 DeletedAt int64 `json:",omitempty"`
@@ -26,13 +27,13 @@ func (m *AppPage) TableName() string { @@ -26,13 +27,13 @@ func (m *AppPage) TableName() string {
26 } 27 }
27 28
28 func (m *AppPage) BeforeCreate(tx *gorm.DB) (err error) { 29 func (m *AppPage) BeforeCreate(tx *gorm.DB) (err error) {
29 - // m.CreatedAt = time.Now().Unix()  
30 - // m.UpdatedAt = time.Now().Unix() 30 + m.CreatedAt = time.Now().Unix()
  31 + m.UpdatedAt = time.Now().Unix()
31 return 32 return
32 } 33 }
33 34
34 func (m *AppPage) BeforeUpdate(tx *gorm.DB) (err error) { 35 func (m *AppPage) BeforeUpdate(tx *gorm.DB) (err error) {
35 - // m.UpdatedAt = time.Now().Unix() 36 + m.UpdatedAt = time.Now().Unix()
36 return 37 return
37 } 38 }
38 39
@@ -2,6 +2,7 @@ package repository @@ -2,6 +2,7 @@ package repository
2 2
3 import ( 3 import (
4 "context" 4 "context"
  5 + "fmt"
5 "github.com/jinzhu/copier" 6 "github.com/jinzhu/copier"
6 "github.com/pkg/errors" 7 "github.com/pkg/errors"
7 "github.com/tiptok/gocomm/pkg/cache" 8 "github.com/tiptok/gocomm/pkg/cache"
@@ -132,6 +133,27 @@ func (repository *AppPageRepository) FindOneByKey(ctx context.Context, conn tran @@ -132,6 +133,27 @@ func (repository *AppPageRepository) FindOneByKey(ctx context.Context, conn tran
132 return repository.ModelToDomainModel(m) 133 return repository.ModelToDomainModel(m)
133 } 134 }
134 135
  136 +func (repository *AppPageRepository) FindOneByChartId(ctx context.Context, conn transaction.Conn, tenantId, chartId int64) (*domain.AppPage, error) {
  137 + var (
  138 + err error
  139 + tx = conn.DB()
  140 + m = new(models.AppPage)
  141 + )
  142 + queryFunc := func() (interface{}, error) {
  143 + tx = tx.Raw(fmt.Sprintf("SELECT * FROM app_page WHERE tenant_id = ? AND charts::jsonb@>'[%v]' AND is_del = 0 limit 1", chartId),
  144 + tenantId,
  145 + ).Scan(m)
  146 + if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
  147 + return nil, domain.ErrNotFound
  148 + }
  149 + return m, tx.Error
  150 + }
  151 + if _, err = repository.Query(queryFunc); err != nil {
  152 + return nil, err
  153 + }
  154 + return repository.ModelToDomainModel(m)
  155 +}
  156 +
135 func (repository *AppPageRepository) Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*domain.AppPage, error) { 157 func (repository *AppPageRepository) Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*domain.AppPage, error) {
136 var ( 158 var (
137 tx = conn.DB() 159 tx = conn.DB()
@@ -141,9 +163,18 @@ func (repository *AppPageRepository) Find(ctx context.Context, conn transaction. @@ -141,9 +163,18 @@ func (repository *AppPageRepository) Find(ctx context.Context, conn transaction.
141 ) 163 )
142 queryFunc := func() (interface{}, error) { 164 queryFunc := func() (interface{}, error) {
143 tx = tx.Model(&ms).Order("id desc") 165 tx = tx.Model(&ms).Order("id desc")
  166 + if v, ok := queryOptions["tenantId"]; ok {
  167 + tx.Where("tenant_id = ?", v)
  168 + }
  169 +
144 if v, ok := queryOptions["ids"]; ok { 170 if v, ok := queryOptions["ids"]; ok {
145 tx.Where("id in (?)", v) 171 tx.Where("id in (?)", v)
146 } 172 }
  173 + //if v, ok := queryOptions["chartId"]; ok {
  174 + // tx.Where(domain.JSONQuery("charts").Contains(v))
  175 + // tx.Where(datatypes.JSONArrayQuery("charts").Contains(v))
  176 + // tx.Where(fmt.Sprintf("charts::jsonb @>'[%v]'", v))
  177 + //}
147 if total, tx = transaction.PaginationAndCount(ctx, tx, queryOptions, &ms); tx.Error != nil { 178 if total, tx = transaction.PaginationAndCount(ctx, tx, queryOptions, &ms); tx.Error != nil {
148 return dms, tx.Error 179 return dms, tx.Error
149 } 180 }
@@ -24,6 +24,7 @@ type AppPageRepository interface { @@ -24,6 +24,7 @@ type AppPageRepository interface {
24 Delete(ctx context.Context, conn transaction.Conn, dm *AppPage) (*AppPage, error) 24 Delete(ctx context.Context, conn transaction.Conn, dm *AppPage) (*AppPage, error)
25 FindOne(ctx context.Context, conn transaction.Conn, id int64) (*AppPage, error) 25 FindOne(ctx context.Context, conn transaction.Conn, id int64) (*AppPage, error)
26 FindOneByKey(ctx context.Context, conn transaction.Conn, key string) (*AppPage, error) 26 FindOneByKey(ctx context.Context, conn transaction.Conn, key string) (*AppPage, error)
  27 + FindOneByChartId(ctx context.Context, conn transaction.Conn, tenantId, chartId int64) (*AppPage, error)
27 Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*AppPage, error) 28 Find(ctx context.Context, conn transaction.Conn, queryOptions map[string]interface{}) (int64, []*AppPage, error)
28 } 29 }
29 30
1 package domain 1 package domain
2 2
3 -import "strconv" 3 +import (
  4 + "github.com/samber/lo"
  5 + "strconv"
  6 +)
4 7
5 const ( 8 const (
6 SourceFromByteBank = "ByteBank" 9 SourceFromByteBank = "ByteBank"
@@ -8,10 +11,10 @@ const ( @@ -8,10 +11,10 @@ const (
8 ) 11 )
9 12
10 var ( 13 var (
11 - RecordTable1 = "RecordTable"  
12 - MetricsCard1 = "MetricsCard"  
13 - ContainerCard1 = "ContainerCard"  
14 - QuarterChart1 = "QuarterChart" 14 + RecordTable1 = "RecordTable-1"
  15 + MetricsCard1 = "MetricsCard-1"
  16 + ContainerCard1 = "ContainerCard-1"
  17 + QuarterChart1 = "QuarterChart-1"
15 ) 18 )
16 19
17 type ChartProperty struct { 20 type ChartProperty struct {
@@ -27,13 +30,18 @@ type Other struct { @@ -27,13 +30,18 @@ type Other struct {
27 Quarter *Quarter `json:"quarter,optional,omitempty"` // 四分图 30 Quarter *Quarter `json:"quarter,optional,omitempty"` // 四分图
28 } 31 }
29 type Quarter struct { 32 type Quarter struct {
30 - XAxisLabel string `json:"xAxisLabel"` // x轴标签名  
31 - XAxisLabelList []string `json:"xAxisLabelList"` // 标签名  
32 - YAxisLabel string `json:"yAxisLabel"` // x轴标签名  
33 - YAxisLabelList []string `json:"yAxisLabelList"` // 标签名  
34 - Area string `json:"area"` // 图形面积  
35 - SeriesList string `json:"seriesList"` // 图形系列 33 + XAxisLabel string `json:"xAxisLabel"` // x轴标签名
  34 + XAxisFirstLabel string `json:"xAxisFirstLabel"` // 签名1
  35 + XAxisSecondLabel string `json:"xAxisSecondLabel"` // 签名2
  36 + YAxisLabel string `json:"yAxisLabel"` // y轴标签名
  37 + YAxisFirstLabel string `json:"yAxisFirstLabel"` // y标签1
  38 + YAxisSecondLabel string `json:"yAxisSecondLabel"` // y标签2
  39 + Area string `json:"area"` // 图形面积
  40 + SeriesList []QuarterSeries `json:"seriesList"` // 图形系列
  41 +}
  42 +type QuarterSeries struct {
36 } 43 }
  44 +
37 type Title struct { 45 type Title struct {
38 TitleSwitch bool `json:"titleSwitch,optional"` // 组件标题开关 46 TitleSwitch bool `json:"titleSwitch,optional"` // 组件标题开关
39 IntroduceSwitch bool `json:"introduceSwitch,optional"` // 组件说明开关 47 IntroduceSwitch bool `json:"introduceSwitch,optional"` // 组件说明开关
@@ -141,5 +149,5 @@ func (e ChartProperty) GetAllDataSourceId() []int64 { @@ -141,5 +149,5 @@ func (e ChartProperty) GetAllDataSourceId() []int64 {
141 idList = append(idList, s.DataSourceId) 149 idList = append(idList, s.DataSourceId)
142 } 150 }
143 } 151 }
144 - return idList 152 + return lo.Uniq(idList)
145 } 153 }
1 package domain 1 package domain
2 2
3 -import "reflect" 3 +import (
  4 + "fmt"
  5 + "gorm.io/gorm"
  6 + "gorm.io/gorm/clause"
  7 + "reflect"
  8 +)
4 9
5 func OffsetLimit(page, size int) (offset int, limit int) { 10 func OffsetLimit(page, size int) (offset int, limit int) {
6 if page == 0 { 11 if page == 0 {
@@ -48,3 +53,45 @@ func (options QueryOptions) Copy() QueryOptions { @@ -48,3 +53,45 @@ func (options QueryOptions) Copy() QueryOptions {
48 } 53 }
49 54
50 type IndexQueryOptionFunc func() QueryOptions 55 type IndexQueryOptionFunc func() QueryOptions
  56 +
  57 +type JSONQueryContainExpression struct {
  58 + column string
  59 + contain bool
  60 + containValue interface{}
  61 +}
  62 +
  63 +func JSONQuery(column string) *JSONQueryContainExpression {
  64 + return &JSONQueryContainExpression{
  65 + column: column,
  66 + }
  67 +}
  68 +
  69 +func (jsonQuery *JSONQueryContainExpression) Contains(value interface{}) *JSONQueryContainExpression {
  70 + jsonQuery.containValue = value
  71 + jsonQuery.contain = true
  72 + return jsonQuery
  73 +}
  74 +
  75 +func (jsonQuery *JSONQueryContainExpression) Build(builder clause.Builder) {
  76 + if stmt, ok := builder.(*gorm.Statement); ok {
  77 + switch stmt.Dialector.Name() {
  78 + case "mysql", "sqlite":
  79 + switch {
  80 + case jsonQuery.contain:
  81 + }
  82 + case "postgres":
  83 + switch {
  84 + case jsonQuery.contain:
  85 + builder.WriteString(fmt.Sprintf("%v::jsonb ", stmt.Quote(jsonQuery.column)))
  86 +
  87 + builder.WriteString("@>'[")
  88 + if _, ok := jsonQuery.containValue.(string); ok {
  89 + builder.AddVar(builder, jsonQuery.containValue)
  90 + } else {
  91 + builder.AddVar(builder, jsonQuery.containValue)
  92 + }
  93 + builder.WriteString("]'")
  94 + }
  95 + }
  96 + }
  97 +}
@@ -73,7 +73,7 @@ type TableData struct { @@ -73,7 +73,7 @@ type TableData struct {
73 //表名 73 //表名
74 Name string `json:"name"` 74 Name string `json:"name"`
75 //数据 75 //数据
76 - Grid *TableDataGrid `json:"grid"` 76 + Grid *TableDataGrid `json:"grid,optional"`
77 //字段 77 //字段
78 Fields []*Field `json:"fields"` 78 Fields []*Field `json:"fields"`
79 } 79 }
@@ -50,7 +50,7 @@ type FilterRule struct { @@ -50,7 +50,7 @@ type FilterRule struct {
50 50
51 type Table struct { 51 type Table struct {
52 // 序号 52 // 序号
53 - // Id int `json:"id"` 53 + Id int `json:"id"`
54 // 表Id 54 // 表Id
55 TableId int `json:"tableId"` 55 TableId int `json:"tableId"`
56 // 表类型 MainTable:主表 SideTable:副表 SubTable:分表 ExcelTable:Excel表 56 // 表类型 MainTable:主表 SideTable:副表 SubTable:分表 ExcelTable:Excel表
@@ -64,7 +64,7 @@ type Table struct { @@ -64,7 +64,7 @@ type Table struct {
64 // 模块 应用于模块 1:数控中心 2:拆解模块 4:计算模块 64 // 模块 应用于模块 1:数控中心 2:拆解模块 4:计算模块
65 // Module int `json:"module"` 65 // Module int `json:"module"`
66 // 标识 66 // 标识
67 - // Flag string `json:"flag,omitempty"` 67 + Flag string `json:"flag,omitempty"`
68 // 启用状态 68 // 启用状态
69 // Status int `json:"status"` 69 // Status int `json:"status"`
70 // 冲突状态 70 // 冲突状态
@@ -71,6 +71,7 @@ type ( @@ -71,6 +71,7 @@ type (
71 Type string `json:"type"`// 类型 report:报表 group:分组 chart:图表 71 Type string `json:"type"`// 类型 report:报表 group:分组 chart:图表
72 Name string `json:"name,optional"`// 名称 72 Name string `json:"name,optional"`// 名称
73 ChartType string `json:"chartType"` // 图表类型 73 ChartType string `json:"chartType"` // 图表类型
  74 + Cover string `json:"cover,optional"` // 封面
74 ChartProperty ChartProperty `json:"property"` // 图表属性 75 ChartProperty ChartProperty `json:"property"` // 图表属性
75 } 76 }
76 ChartSaveResponse struct{ 77 ChartSaveResponse struct{
@@ -92,7 +93,7 @@ type ( @@ -92,7 +93,7 @@ type (
92 93
93 ChartUpdateRequest struct{ 94 ChartUpdateRequest struct{
94 Id int64 `path:"id"` 95 Id int64 `path:"id"`
95 - ChartType string `json:"chartType"` // 图表类型 96 + Cover string `json:"cover,optional"` // 封面
96 ChartProperty ChartProperty `json:"property"` // 图表属性 97 ChartProperty ChartProperty `json:"property"` // 图表属性
97 } 98 }
98 ChartUpdateResponse struct{} 99 ChartUpdateResponse struct{}
@@ -122,7 +123,8 @@ type ( @@ -122,7 +123,8 @@ type (
122 Type string `json:"type,optional"`// 类型 report:报表 group:分组 chart:图表 123 Type string `json:"type,optional"`// 类型 report:报表 group:分组 chart:图表
123 Sort int64 `json:"sort,optional"`// 排序 124 Sort int64 `json:"sort,optional"`// 排序
124 Name string `json:"name,optional"`// 名称 125 Name string `json:"name,optional"`// 名称
125 - //Charts []ChartItem `json:"charts,optional"` 126 + Cover string `json:"cover,optional"` // 封面
  127 + ChartType string `json:"chartType,optional"` // 图表类型
126 ChartProperty *ChartProperty `json:"property,optional,omitempty"` //属性 128 ChartProperty *ChartProperty `json:"property,optional,omitempty"` //属性
127 } 129 }
128 LoadChartDataRequest struct{ 130 LoadChartDataRequest struct{
@@ -154,19 +156,23 @@ type( @@ -154,19 +156,23 @@ type(
154 Title Title `json:"title,optional"` // 标题 156 Title Title `json:"title,optional"` // 标题
155 TableAbility TableAbility `json:"table,optional"` // 表筛选功能 157 TableAbility TableAbility `json:"table,optional"` // 表筛选功能
156 Series []Series `json:"series,optional"` // 系列(数据源) 158 Series []Series `json:"series,optional"` // 系列(数据源)
157 - Cover string `json:"cover,optional"` // 封面 159 + //Cover string `json:"cover,optional"` // 封面
158 Other Other `json:"other,optional"` // 其他额外配置 160 Other Other `json:"other,optional"` // 其他额外配置
159 } 161 }
160 Other struct { 162 Other struct {
161 Quarter *Quarter `json:"quarter,optional,omitempty"` // 四分图 163 Quarter *Quarter `json:"quarter,optional,omitempty"` // 四分图
162 } 164 }
163 Quarter struct { 165 Quarter struct {
164 - XAxisLabel string `json:"xAxisLabel"` // x轴标签名  
165 - XAxisLabelList []string `json:"xAxisLabelList"` // 标签名  
166 - YAxisLabel string `json:"yAxisLabel"` // x轴标签名  
167 - YAxisLabelList []string `json:"yAxisLabelList"` // 标签名  
168 - Area string `json:"area"` // 图形面积  
169 - SeriesList string `json:"seriesList"` // 图形系列 166 + XAxisLabel string `json:"xAxisLabel"` // x轴标签名
  167 + XAxisFirstLabel string `json:"xAxisFirstLabel"` // 签名1
  168 + XAxisSecondLabel string `json:"xAxisSecondLabel"` // 签名2
  169 + YAxisLabel string `json:"yAxisLabel"` // y轴标签名
  170 + YAxisFirstLabel string `json:"yAxisFirstLabel"` // y标签1
  171 + YAxisSecondLabel string `json:"yAxisSecondLabel"` // y标签2
  172 + Area string `json:"area"` // 图形面积
  173 + SeriesList []QuarterSeries `json:"seriesList"` // 图形系列
  174 + }
  175 + QuarterSeries struct {
170 } 176 }
171 Title struct { 177 Title struct {
172 TitleSwitch bool `json:"titleSwitch,optional"` // 组件标题开关 178 TitleSwitch bool `json:"titleSwitch,optional"` // 组件标题开关
@@ -174,7 +180,7 @@ type( @@ -174,7 +180,7 @@ type(
174 TitleType string `json:"titleType"` // 标题类型 180 TitleType string `json:"titleType"` // 标题类型
175 Heading string `json:"heading,optional"` // 主标题 181 Heading string `json:"heading,optional"` // 主标题
176 SubTitle string `json:"subTitle,optional"` // 副标题 182 SubTitle string `json:"subTitle,optional"` // 副标题
177 - ExplainType string `json:"explainType,optional,options=[text,file]"` // text file 183 + ExplainType string `json:"explainType,optional,options=[text,,file]"` // text file ,options=text||file
178 ExplainTxt string `json:"explainTxt,optional"` // 文字说明 184 ExplainTxt string `json:"explainTxt,optional"` // 文字说明
179 FileUrl string `json:"fileUrl,optional"` // 组件图片/视频 185 FileUrl string `json:"fileUrl,optional"` // 组件图片/视频
180 Align string `json:"align,optional"` // 文本对齐方式 left center right 186 Align string `json:"align,optional"` // 文本对齐方式 left center right
@@ -199,7 +205,7 @@ type( @@ -199,7 +205,7 @@ type(
199 ToValue string `json:"toValue"` // 显示值(转为) 205 ToValue string `json:"toValue"` // 显示值(转为)
200 } 206 }
201 Dimension struct{ 207 Dimension struct{
202 - Name string `json:"name"`  
203 - Value string `json:"value"` 208 + Name string `json:"name,optional,omitempty"`
  209 + Value string `json:"dimensionVal,optional"`
204 } 210 }
205 ) 211 )
@@ -52,7 +52,7 @@ type ( @@ -52,7 +52,7 @@ type (
52 Token string `header:"x-mmm-accesstoken,optional"` 52 Token string `header:"x-mmm-accesstoken,optional"`
53 ObjectId int `json:"objectId"` // 对象ID 53 ObjectId int `json:"objectId"` // 对象ID
54 Field string `json:"field"` // 当前选择的字段 54 Field string `json:"field"` // 当前选择的字段
55 - SqlName string `json:"sqlName"` // 字段SqlName 55 + //SqlName string `json:"sqlName,optional"` // 字段SqlName
56 // Match string `json:"match"` 56 // Match string `json:"match"`
57 //PageNumber int `json:"pageNumber,optional"` // 分页数 57 //PageNumber int `json:"pageNumber,optional"` // 分页数
58 //PageSize int `json:"pageSize,optional"` // 页码 58 //PageSize int `json:"pageSize,optional"` // 页码
@@ -64,10 +64,10 @@ type ( @@ -64,10 +64,10 @@ type (
64 } 64 }
65 Condition struct { 65 Condition struct {
66 FieldName string `json:"field"` // 条件字段 66 FieldName string `json:"field"` // 条件字段
67 - SqlName string `json:"sqlName"` // 字段SqlName 67 + //SqlName string `json:"sqlName,optional"` // 字段SqlName
68 Like string `json:"like,optional"` // 模糊匹配 68 Like string `json:"like,optional"` // 模糊匹配
69 In []string `json:"in,optional"` // 匹配多个值 69 In []string `json:"in,optional"` // 匹配多个值
70 - Order string `json:"order,optional,options=[ASC,DESC]"` // 排序 ASC DESC 默认ASC 70 + Order string `json:"order,optional,options=ASC||DESC"` // 排序 ASC DESC 默认ASC
71 } 71 }
72 72
73 GetTableDetailRequest struct { 73 GetTableDetailRequest struct {
@@ -137,6 +137,7 @@ require ( @@ -137,6 +137,7 @@ require (
137 gopkg.in/inf.v0 v0.9.1 // indirect 137 gopkg.in/inf.v0 v0.9.1 // indirect
138 gopkg.in/yaml.v2 v2.4.0 // indirect 138 gopkg.in/yaml.v2 v2.4.0 // indirect
139 gopkg.in/yaml.v3 v3.0.1 // indirect 139 gopkg.in/yaml.v3 v3.0.1 // indirect
  140 + gorm.io/datatypes v1.2.0 // indirect
140 k8s.io/api v0.26.3 // indirect 141 k8s.io/api v0.26.3 // indirect
141 k8s.io/apimachinery v0.27.0-alpha.3 // indirect 142 k8s.io/apimachinery v0.27.0-alpha.3 // indirect
142 k8s.io/client-go v0.26.3 // indirect 143 k8s.io/client-go v0.26.3 // indirect
@@ -42,7 +42,13 @@ func OpenGormPGDB(source string) *gorm.DB { @@ -42,7 +42,13 @@ func OpenGormPGDB(source string) *gorm.DB {
42 }, 42 },
43 ) 43 )
44 fmt.Println("starting db...") 44 fmt.Println("starting db...")
45 - db, err := gorm.Open(postgres.Open(source), &gorm.Config{ 45 + //db, err := gorm.Open(postgres.Open(source), &gorm.Config{
  46 + // Logger: newLogger,
  47 + //})
  48 + db, err := gorm.Open(postgres.New(postgres.Config{
  49 + DSN: source,
  50 + PreferSimpleProtocol: true, // disables implicit prepared statement usage
  51 + }), &gorm.Config{
46 Logger: newLogger, 52 Logger: newLogger,
47 }) 53 })
48 if err != nil { 54 if err != nil {
@@ -44,7 +44,7 @@ func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err er @@ -44,7 +44,7 @@ func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err er
44 logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err) 44 logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err)
45 response := Error(errcode, errmsg) 45 response := Error(errcode, errmsg)
46 response.Error = internalErr 46 response.Error = internalErr
47 - httpx.WriteJson(w, http.StatusBadRequest, response) 47 + httpx.WriteJson(w, http.StatusOK, response)
48 } 48 }
49 } 49 }
50 50