github.com/erda-project/erda-infra@v1.0.9/providers/component-protocol/protocol/render.go (about) 1 // Copyright (c) 2021 Terminus, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package protocol 16 17 import ( 18 "context" 19 "fmt" 20 "reflect" 21 "strings" 22 "time" 23 24 "github.com/sirupsen/logrus" 25 26 "github.com/erda-project/erda-infra/pkg/strutil" 27 "github.com/erda-project/erda-infra/providers/component-protocol/cptype" 28 "github.com/erda-project/erda-infra/providers/component-protocol/protocol/posthook" 29 "github.com/erda-project/erda-infra/providers/component-protocol/utils/cputil" 30 ) 31 32 // RunScenarioRender . 33 func RunScenarioRender(ctx context.Context, req *cptype.ComponentProtocolRequest) error { 34 // precheck request 35 if err := precheckRenderRequest(ctx, req); err != nil { 36 return err 37 } 38 39 // get scenario key 40 sk, err := getScenarioKey(req.Scenario) 41 if err != nil { 42 return err 43 } 44 45 // get scenario renders 46 sr, err := getScenarioRenders(sk) 47 if err != nil { 48 logrus.Errorf("failed to get scenario render, err: %v", err) 49 return err 50 } 51 52 // calculate renderingItems 53 renderingItems, err := calcCompsRendering(ctx, req, *sr, sk) 54 if err != nil { 55 logrus.Errorf("failed to calculate rendering items, err: %v", err) 56 return err 57 } 58 59 // prehook 60 if err := prehookForCompsRendering(ctx, req, *sr, renderingItems); err != nil { 61 return err 62 } 63 64 // do render 65 if err := doCompsRendering(ctx, req, *sr, renderingItems); err != nil { 66 return err 67 } 68 69 // posthook 70 posthookForCompsRendering(renderingItems, req) 71 72 return nil 73 } 74 75 func precheckRenderRequest(ctx context.Context, req *cptype.ComponentProtocolRequest) error { 76 // check debug options 77 if err := checkDebugOptions(ctx, req.DebugOptions); err != nil { 78 return err 79 } 80 return nil 81 } 82 83 func calcCompsRendering(ctx context.Context, req *cptype.ComponentProtocolRequest, sr ScenarioRender, sk string) ([]cptype.RendingItem, error) { 84 var useDefaultProtocol bool 85 if req.Protocol == nil || req.Event.Component == "" { 86 useDefaultProtocol = true 87 p, err := getDefaultProtocol(ctx, sk) 88 if err != nil { 89 return nil, err 90 } 91 var tmp cptype.ComponentProtocol 92 if err := cputil.ObjJSONTransfer(&p, &tmp); err != nil { 93 logrus.Errorf("deep copy failed, err: %v", err) 94 return nil, err 95 96 } 97 req.Protocol = &tmp 98 } 99 100 var renderingItems []cptype.RendingItem 101 if useDefaultProtocol { 102 crs, ok := req.Protocol.Rendering[cptype.DefaultRenderingKey] 103 if !ok { 104 orders, err := calculateDefaultRenderOrderByHierarchy(req.Protocol) 105 if err != nil { 106 logrus.Errorf("failed to calculate default render order by hierarchy: %v", err) 107 return nil, err 108 } 109 for _, compName := range orders { 110 renderingItems = append(renderingItems, cptype.RendingItem{Name: compName}) 111 } 112 } else { 113 renderingItems = append(renderingItems, crs...) 114 } 115 116 } else { 117 // root is always rendered 118 if req.Event.Component != req.Protocol.Hierarchy.Root { 119 renderingItems = append(renderingItems, cptype.RendingItem{Name: req.Protocol.Hierarchy.Root}) 120 } 121 // 如果是前端触发一个组件操作,则先渲染该组件; 122 // 再根据定义的渲染顺序,依次完成其他组件的state注入和渲染; 123 compName := req.Event.Component 124 renderingItems = append(renderingItems, cptype.RendingItem{Name: compName}) 125 126 crs, ok := req.Protocol.Rendering[compName] 127 if !ok { 128 logrus.Infof("empty protocol rending for component: %s, use hierarchy and start from %s", compName, compName) 129 orders, err := calculateDefaultRenderOrderByHierarchy(req.Protocol) 130 if err != nil { 131 logrus.Errorf("failed to calculate default render order by hierarchy for empty rendering: %v", err) 132 return nil, err 133 } 134 subRenderingOrders := getDefaultHierarchyRenderOrderFromCompExclude(orders, compName) 135 for _, comp := range subRenderingOrders { 136 renderingItems = append(renderingItems, cptype.RendingItem{Name: comp}) 137 } 138 } else { 139 renderingItems = append(renderingItems, crs...) 140 } 141 } 142 143 return renderingItems, nil 144 } 145 146 func prehookForCompsRendering(ctx context.Context, req *cptype.ComponentProtocolRequest, sr ScenarioRender, renderingItems []cptype.RendingItem) error { 147 renderingItems = polishComponentRendering(req.DebugOptions, renderingItems) 148 renderingItems = polishComponentRenderingByInitOp(req.Protocol, req.Event, renderingItems) 149 renderingItems = polishComponentRenderingByAsyncAtInitOp(req.Protocol, req.Event, renderingItems) 150 151 if req.Protocol.GlobalState == nil { 152 gs := make(cptype.GlobalStateData) 153 req.Protocol.GlobalState = &gs 154 } 155 156 // clean pre render error 157 setGlobalStateKV(req.Protocol, cptype.GlobalInnerKeyError.String(), nil) 158 159 polishProtocol(req.Protocol) 160 161 return nil 162 } 163 164 func doCompsRendering(ctx context.Context, req *cptype.ComponentProtocolRequest, sr ScenarioRender, renderingItems []cptype.RendingItem) error { 165 // parallel 166 // if hierarchy.Parallel specified, use new rendering 167 if len(req.Protocol.Hierarchy.Parallel) > 0 { 168 return doParallelCompsRendering(ctx, req, sr, renderingItems) 169 } 170 171 // serial 172 return doSerialCompsRendering(ctx, req, sr, renderingItems) 173 } 174 175 func doParallelCompsRendering(ctx context.Context, req *cptype.ComponentProtocolRequest, sr ScenarioRender, renderingItems []cptype.RendingItem) error { 176 rootNode, err := parseParallelRendering(req.Protocol, renderingItems) 177 if err != nil { 178 logrus.Errorf("failed to parse parallel rendering, err: %v", err) 179 return err 180 } 181 fmt.Println(rootNode.String()) 182 if err := renderFromNode(ctx, req, sr, rootNode); err != nil { 183 return err 184 } 185 return nil 186 } 187 188 func doSerialCompsRendering(ctx context.Context, req *cptype.ComponentProtocolRequest, sr ScenarioRender, renderingItems []cptype.RendingItem) error { 189 // rendering in order 190 for _, v := range renderingItems { 191 if err := renderOneComp(ctx, req, sr, v); err != nil { 192 return err 193 } 194 } 195 return nil 196 } 197 198 func posthookForCompsRendering(renderingItems []cptype.RendingItem, req *cptype.ComponentProtocolRequest) { 199 posthook.HandleContinueRender(renderingItems, req.Protocol) 200 posthook.OnlyReturnRenderingComps(renderingItems, req.Protocol) 201 posthook.HandleURLQuery(renderingItems, req.Protocol) 202 posthook.SimplifyProtocol(renderingItems, req.Protocol) 203 } 204 205 func polishComponentRendering(debugOptions *cptype.ComponentProtocolDebugOptions, compRendering []cptype.RendingItem) []cptype.RendingItem { 206 if debugOptions == nil || debugOptions.ComponentKey == "" { 207 return compRendering 208 } 209 var result []cptype.RendingItem 210 for _, item := range compRendering { 211 if item.Name == debugOptions.ComponentKey { 212 result = append(result, item) 213 break 214 } 215 } 216 return result 217 } 218 219 func polishComponentRenderingByInitOp(protocol *cptype.ComponentProtocol, event cptype.ComponentEvent, compRendering []cptype.RendingItem) []cptype.RendingItem { 220 // judge event 221 if event.Component != cptype.InitializeOperation.String() { 222 return compRendering 223 } 224 // judge async comps 225 asyncCompsByName := make(map[string]struct{}) 226 for _, comp := range protocol.Components { 227 if comp.Options != nil && comp.Options.AsyncAtInit { 228 asyncCompsByName[comp.Name] = struct{}{} 229 } 230 } 231 // skip comp with option: asyncAtInit 232 var result []cptype.RendingItem 233 for _, item := range compRendering { 234 if _, needAsyncAtInit := asyncCompsByName[item.Name]; needAsyncAtInit { 235 continue 236 } 237 result = append(result, item) 238 } 239 return result 240 } 241 242 func polishComponentRenderingByAsyncAtInitOp(protocol *cptype.ComponentProtocol, event cptype.ComponentEvent, compRendering []cptype.RendingItem) []cptype.RendingItem { 243 // judge event 244 if event.Component != cptype.AsyncAtInitOperation.String() { 245 return compRendering 246 } 247 asyncCompsByName := make(map[string]struct{}) 248 if len(event.OperationData) > 0 { 249 v, ok := event.OperationData["components"] 250 if ok { 251 for _, vv := range v.([]interface{}) { 252 asyncCompsByName[strutil.String(vv)] = struct{}{} 253 } 254 } 255 } 256 if len(asyncCompsByName) == 0 { 257 // analyze from protocol 258 for _, comp := range protocol.Components { 259 if comp.Options != nil && comp.Options.AsyncAtInit { 260 asyncCompsByName[comp.Name] = struct{}{} 261 } 262 } 263 } 264 // only render async comps 265 var result []cptype.RendingItem 266 for _, item := range compRendering { 267 if _, needAsyncAtInit := asyncCompsByName[item.Name]; needAsyncAtInit { 268 result = append(result, item) 269 } 270 } 271 return result 272 } 273 274 func getCompNameAndInstanceName(name string) (compName, instanceName string) { 275 ss := strings.SplitN(name, "@", 2) 276 if len(ss) == 2 { 277 compName = ss[0] 278 instanceName = ss[1] 279 return 280 } 281 compName = name 282 // use name as instance name 283 instanceName = name 284 return 285 } 286 287 func calculateDefaultRenderOrderByHierarchy(p *cptype.ComponentProtocol) ([]string, error) { 288 allCompSubMap := make(map[string][]string) 289 for k, v := range p.Hierarchy.Structure { 290 switch subs := v.(type) { 291 case []interface{}: 292 for i := range subs { 293 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, subs[i])...) 294 } 295 case map[string]interface{}: 296 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, subs["slot"])...) 297 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, subs["left"])...) 298 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, subs["right"])...) 299 childrenComps := *recursiveGetSubComps(nil, subs["children"]) 300 footerComps := *recursiveGetSubComps(nil, subs["footer"]) 301 // recognized structKey: left, right, children, footer, slot 302 for structKey, compName := range subs { 303 if structKey == "left" || structKey == "right" || 304 structKey == "children" || structKey == "footer" || structKey == "slot" { 305 continue 306 } 307 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, compName)...) 308 } 309 for _, comp := range childrenComps { 310 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, comp)...) 311 } 312 for _, comp := range footerComps { 313 allCompSubMap[k] = append(allCompSubMap[k], *recursiveGetSubComps(nil, comp)...) 314 } 315 } 316 allCompSubMap[k] = strutil.DedupSlice(allCompSubMap[k], true) 317 } 318 319 root := p.Hierarchy.Root 320 var results []string 321 if walkErr := recursiveWalkCompOrder(root, &results, allCompSubMap); walkErr != nil { 322 return nil, walkErr 323 } 324 results = strutil.DedupSlice(results, true) 325 return results, nil 326 } 327 328 func recursiveGetSubComps(result *[]string, subs interface{}) *[]string { 329 if result == nil { 330 result = &[]string{} 331 } 332 if subs == nil { 333 return result 334 } 335 switch v := subs.(type) { 336 case []interface{}: 337 for _, vv := range v { 338 recursiveGetSubComps(result, vv) 339 } 340 case map[string]interface{}: 341 for _, vv := range v { 342 recursiveGetSubComps(result, vv) 343 } 344 case string: 345 *result = append(*result, v) 346 case float64: 347 *result = append(*result, strutil.String(v)) 348 default: 349 panic(fmt.Errorf("not supported type: %v, subs: %v", reflect.TypeOf(subs), subs)) 350 } 351 return result 352 } 353 354 // recursiveWalkCompOrder 355 // TODO check cycle visited 356 func recursiveWalkCompOrder(current string, orders *[]string, allCompSubMap map[string][]string) error { 357 *orders = append(*orders, current) 358 subs := allCompSubMap[current] 359 for _, sub := range subs { 360 if err := recursiveWalkCompOrder(sub, orders, allCompSubMap); err != nil { 361 return err 362 } 363 } 364 *orders = strutil.DedupSlice(*orders, true) 365 366 return nil 367 } 368 369 func getDefaultHierarchyRenderOrderFromCompExclude(fullOrders []string, startFromCompExclude string) []string { 370 fromIdx := -1 371 for i, comp := range fullOrders { 372 if startFromCompExclude == comp { 373 fromIdx = i 374 break 375 } 376 } 377 if fromIdx == -1 { 378 return []string{} 379 } 380 return fullOrders[fromIdx+1:] 381 } 382 383 func renderOneComp(ctx context.Context, req *cptype.ComponentProtocolRequest, sr ScenarioRender, v cptype.RendingItem) error { 384 // 组件状态渲染 385 err := protoCompStateRending(ctx, req.Protocol, v) 386 if err != nil { 387 logrus.Errorf("protocol component state rending failed, request: %+v, err: %v", v, err) 388 return err 389 } 390 // 获取协议中相关组件 391 c, err := getProtoComp(ctx, req.Protocol, v.Name) 392 if err != nil { 393 logrus.Errorf("get component from protocol failed, scenario: %s, component: %s", req.Scenario.ScenarioKey, req.Event.Component) 394 return nil 395 } 396 // 获取组件渲染函数 397 cr, err := getCompRender(ctx, sr, v.Name, c.Type) 398 if err != nil { 399 logrus.Errorf("get component render failed, scenario: %s, component: %s", req.Scenario.ScenarioKey, req.Event.Component) 400 return err 401 } 402 // 生成组件对应事件,如果不是组件自身事件则为默认事件 403 event := eventConvert(v.Name, req.Event) 404 // 运行组件渲染函数 405 start := time.Now() // 获取当前时间 406 _, instanceName := getCompNameAndInstanceName(v.Name) 407 c.Name = instanceName 408 err = wrapCompRender(cr.RenderC(), req.Protocol.Version).Render(ctx, c, req.Scenario, event, req.Protocol.GlobalState) 409 if err != nil { 410 logrus.Errorf("render component failed, err: %s, scenario: %+v, component: %s", err.Error(), req.Scenario, cr.CompName) 411 return err 412 } 413 elapsed := time.Since(start) 414 logrus.Infof("[component render time cost] scenario: %s, component: %s, cost: %s", req.Scenario.ScenarioKey, v.Name, elapsed) 415 return nil 416 }