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  }