Compare commits
2 Commits
hotfix/mea
...
develop
| Author | SHA1 | Date |
|---|---|---|
|
|
9c9e652765 | |
|
|
b05219ef4c |
|
|
@ -4,8 +4,6 @@ package constants
|
|||
const (
|
||||
// DefaultScore define the default score for redissearch suggestion
|
||||
DefaultScore = 1.0
|
||||
// ComponentConfigKey define component config token used at token6
|
||||
ComponentConfigKey = "component"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -44,9 +42,6 @@ const (
|
|||
|
||||
// RedisSpecCompTagMeasSetKey define redis set key which store all measurement tag keys under specific component tag
|
||||
RedisSpecCompTagMeasSetKey = "%s_measurement_tag_keys"
|
||||
|
||||
// RedisSpecCompNSPathMeasSetKey define redis set key which store all measurement tag keys under specific component nspath
|
||||
RedisSpecCompNSPathMeasSetKey = "%s_nspath_measurement_tag_keys"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
// Package database define database operation functions
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// QueryComponentColumnNames returns all column names from the component table.
|
||||
func QueryComponentColumnNames(ctx context.Context, db *gorm.DB) ([]string, error) {
|
||||
columnTypes, err := db.WithContext(ctx).Migrator().ColumnTypes((&orm.Component{}).TableName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
columnNames := make([]string, 0, len(columnTypes))
|
||||
for _, columnType := range columnTypes {
|
||||
columnName := columnType.Name()
|
||||
if columnName == "" {
|
||||
continue
|
||||
}
|
||||
columnNames = append(columnNames, columnName)
|
||||
}
|
||||
return columnNames, nil
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe
|
|||
StationToCompNSPaths: make(map[string][]string),
|
||||
CompNSPathToCompTags: make(map[string][]string),
|
||||
CompTagToMeasTags: make(map[string][]string),
|
||||
CompNSPathToMeasTags: make(map[string][]string),
|
||||
}
|
||||
|
||||
g, gctx := errgroup.WithContext(ctx)
|
||||
|
|
@ -115,13 +114,10 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe
|
|||
var measurements []struct {
|
||||
orm.Measurement
|
||||
CompTag string `gorm:"column:comp_tag"`
|
||||
CompNSPath string `gorm:"column:comp_nspath"`
|
||||
BayTag string `gorm:"column:bay_tag"`
|
||||
}
|
||||
if err := db.Table("measurement").
|
||||
Select("measurement.*, component.tag as comp_tag, component.nspath as comp_nspath, bay.tag as bay_tag").
|
||||
Select("measurement.*, component.tag as comp_tag").
|
||||
Joins("left join component on measurement.component_uuid = component.global_uuid").
|
||||
Joins("left join bay on measurement.bay_uuid = bay.bay_uuid").
|
||||
Scan(&measurements).Error; err != nil {
|
||||
return fmt.Errorf("query measurements: %w", err)
|
||||
}
|
||||
|
|
@ -130,9 +126,6 @@ func GetFullMeasurementSet(ctx context.Context, db *gorm.DB) (*orm.MeasurementSe
|
|||
if m.CompTag != "" {
|
||||
mSet.CompTagToMeasTags[m.CompTag] = append(mSet.CompTagToMeasTags[m.CompTag], m.Tag)
|
||||
}
|
||||
if m.CompNSPath != "" && m.CompNSPath == m.BayTag {
|
||||
mSet.CompNSPathToMeasTags[m.CompNSPath] = append(mSet.CompNSPathToMeasTags[m.CompNSPath], m.Tag)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,11 +3,8 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
"modelRT/logger"
|
||||
"modelRT/orm"
|
||||
"modelRT/sql"
|
||||
|
|
@ -24,16 +21,19 @@ func QueryTopologic(ctx context.Context, tx *gorm.DB) ([]orm.Topologic, error) {
|
|||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := tx.WithContext(cancelCtx).Clauses(clause.Locking{Strength: "UPDATE"}).Raw(sql.RecursiveSQL, constants.UUIDNilStr).Scan(&topologics)
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Find(&topologics)
|
||||
if result.Error != nil {
|
||||
logger.Error(ctx, "query circuit diagram topologic info by start node uuid failed", "start_node_uuid", constants.UUIDNilStr, "error", result.Error)
|
||||
logger.Error(ctx, "query circuit diagram topologic info failed", "error", result.Error)
|
||||
return nil, result.Error
|
||||
}
|
||||
return topologics, nil
|
||||
}
|
||||
|
||||
// QueryTopologicByStartUUID returns all edges reachable from startUUID following
|
||||
// directed uuid_from → uuid_to edges in the topologic table.
|
||||
// QueryTopologicByStartUUID returns all directed edges reachable from startUUID.
|
||||
// It is used by point-to-point topology reachability checks and intentionally
|
||||
// does not depend on the legacy all-zero UUID virtual root.
|
||||
func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.UUID) ([]orm.Topologic, error) {
|
||||
var topologics []orm.Topologic
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.
|
|||
|
||||
result := tx.WithContext(cancelCtx).
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Raw(sql.RecursiveSQL, startUUID).
|
||||
Raw(sql.RecursiveTopologicByStartSQL, startUUID).
|
||||
Scan(&topologics)
|
||||
if result.Error != nil {
|
||||
logger.Error(ctx, "query topologic by start uuid failed", "start_uuid", startUUID, "error", result.Error)
|
||||
|
|
@ -50,80 +50,3 @@ func QueryTopologicByStartUUID(ctx context.Context, tx *gorm.DB, startUUID uuid.
|
|||
}
|
||||
return topologics, nil
|
||||
}
|
||||
|
||||
// QueryTopologicFromDB return the result of query topologic info from DB.
|
||||
// Returns the root node and a flat nodeMap for O(1) lookup by UUID.
|
||||
func QueryTopologicFromDB(ctx context.Context, tx *gorm.DB) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
|
||||
topologicInfos, err := QueryTopologic(ctx, tx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query topologic info failed", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tree, nodeMap, err := BuildMultiBranchTree(topologicInfos)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "init topologic failed", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return tree, nodeMap, nil
|
||||
}
|
||||
|
||||
// BuildMultiBranchTree return the multi branch tree by topologic info.
|
||||
// Returns the root node and a flat nodeMap for O(1) lookup by UUID.
|
||||
func BuildMultiBranchTree(topologics []orm.Topologic) (*diagram.MultiBranchTreeNode, map[uuid.UUID]*diagram.MultiBranchTreeNode, error) {
|
||||
nodeMap := make(map[uuid.UUID]*diagram.MultiBranchTreeNode, len(topologics)*2)
|
||||
|
||||
for _, topo := range topologics {
|
||||
if _, exists := nodeMap[topo.UUIDFrom]; !exists {
|
||||
// UUIDNil is the virtual root sentinel — skip creating a regular node for it
|
||||
if topo.UUIDFrom != constants.UUIDNil {
|
||||
nodeMap[topo.UUIDFrom] = &diagram.MultiBranchTreeNode{
|
||||
ID: topo.UUIDFrom,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := nodeMap[topo.UUIDTo]; !exists {
|
||||
if topo.UUIDTo != constants.UUIDNil {
|
||||
nodeMap[topo.UUIDTo] = &diagram.MultiBranchTreeNode{
|
||||
ID: topo.UUIDTo,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, topo := range topologics {
|
||||
var parent *diagram.MultiBranchTreeNode
|
||||
if topo.UUIDFrom == constants.UUIDNil {
|
||||
if _, exists := nodeMap[constants.UUIDNil]; !exists {
|
||||
nodeMap[constants.UUIDNil] = &diagram.MultiBranchTreeNode{
|
||||
ID: constants.UUIDNil,
|
||||
Children: make([]*diagram.MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
parent = nodeMap[constants.UUIDNil]
|
||||
} else {
|
||||
parent = nodeMap[topo.UUIDFrom]
|
||||
}
|
||||
|
||||
var child *diagram.MultiBranchTreeNode
|
||||
if topo.UUIDTo == constants.UUIDNil {
|
||||
child = &diagram.MultiBranchTreeNode{
|
||||
ID: topo.UUIDTo,
|
||||
}
|
||||
} else {
|
||||
child = nodeMap[topo.UUIDTo]
|
||||
}
|
||||
child.Parent = parent
|
||||
parent.Children = append(parent.Children, child)
|
||||
}
|
||||
|
||||
// return root vertex
|
||||
root, exists := nodeMap[constants.UUIDNil]
|
||||
if !exists {
|
||||
return nil, nil, fmt.Errorf("root node not found")
|
||||
}
|
||||
return root, nodeMap, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,125 +0,0 @@
|
|||
// Package diagram provide diagram data structure and operation
|
||||
package diagram
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
var GlobalTree *MultiBranchTreeNode
|
||||
|
||||
// MultiBranchTreeNode represents a topological structure using an multi branch tree
|
||||
type MultiBranchTreeNode struct {
|
||||
ID uuid.UUID // 节点唯一标识
|
||||
Parent *MultiBranchTreeNode // 指向父节点的指针
|
||||
Children []*MultiBranchTreeNode // 指向所有子节点的指针切片
|
||||
}
|
||||
|
||||
func NewMultiBranchTree(id uuid.UUID) *MultiBranchTreeNode {
|
||||
return &MultiBranchTreeNode{
|
||||
ID: id,
|
||||
Children: make([]*MultiBranchTreeNode, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *MultiBranchTreeNode) AddChild(child *MultiBranchTreeNode) {
|
||||
child.Parent = n
|
||||
n.Children = append(n.Children, child)
|
||||
}
|
||||
|
||||
func (n *MultiBranchTreeNode) RemoveChild(childID uuid.UUID) bool {
|
||||
for i, child := range n.Children {
|
||||
if child.ID == childID {
|
||||
n.Children = append(n.Children[:i], n.Children[i+1:]...)
|
||||
child.Parent = nil
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *MultiBranchTreeNode) FindNodeByID(id uuid.UUID) *MultiBranchTreeNode {
|
||||
if n.ID == id {
|
||||
return n
|
||||
}
|
||||
|
||||
for _, child := range n.Children {
|
||||
if found := child.FindNodeByID(id); found != nil {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *MultiBranchTreeNode) PrintTree(level int) {
|
||||
for range level {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
|
||||
fmt.Printf("-ID: %s\n", n.ID)
|
||||
|
||||
for _, child := range n.Children {
|
||||
child.PrintTree(level + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// FindPath returns the ordered node sequence from startID to endID using the
|
||||
// supplied nodeMap for O(1) lookup. It walks each node up to the root to find
|
||||
// the LCA, then stitches the two half-paths together.
|
||||
// Returns nil when either node is absent from nodeMap or no path exists.
|
||||
func FindPath(startID, endID uuid.UUID, nodeMap map[uuid.UUID]*MultiBranchTreeNode) []*MultiBranchTreeNode {
|
||||
startNode, ok := nodeMap[startID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
endNode, ok := nodeMap[endID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// collect ancestors (inclusive) from a node up to the root sentinel
|
||||
ancestors := func(n *MultiBranchTreeNode) []*MultiBranchTreeNode {
|
||||
var chain []*MultiBranchTreeNode
|
||||
for n != nil {
|
||||
chain = append(chain, n)
|
||||
n = n.Parent
|
||||
}
|
||||
return chain
|
||||
}
|
||||
|
||||
startChain := ancestors(startNode) // [start, ..., root]
|
||||
endChain := ancestors(endNode) // [end, ..., root]
|
||||
|
||||
// index startChain by ID for fast LCA detection
|
||||
startIdx := make(map[uuid.UUID]int, len(startChain))
|
||||
for i, node := range startChain {
|
||||
startIdx[node.ID] = i
|
||||
}
|
||||
|
||||
// find LCA: first node in endChain that also appears in startChain
|
||||
lcaEndPos := -1
|
||||
lcaStartPos := -1
|
||||
for i, node := range endChain {
|
||||
if j, found := startIdx[node.ID]; found {
|
||||
lcaEndPos = i
|
||||
lcaStartPos = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if lcaEndPos < 0 {
|
||||
return nil // disconnected
|
||||
}
|
||||
|
||||
// path = startChain[0..lcaStartPos] reversed + endChain[lcaEndPos..0] reversed
|
||||
path := make([]*MultiBranchTreeNode, 0, lcaStartPos+lcaEndPos+1)
|
||||
for i := 0; i <= lcaStartPos; i++ {
|
||||
path = append(path, startChain[i])
|
||||
}
|
||||
// append end-side (skip LCA to avoid duplication), reversed
|
||||
for i := lcaEndPos - 1; i >= 0; i-- {
|
||||
path = append(path, endChain[i])
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package diagram
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// TopologyGraph represents directed topologic links with adjacency lists.
|
||||
// It preserves multiple parents for one node.
|
||||
type TopologyGraph struct {
|
||||
Nodes map[uuid.UUID]struct{}
|
||||
OutEdges map[uuid.UUID][]uuid.UUID
|
||||
InEdges map[uuid.UUID][]uuid.UUID
|
||||
StartNodes []uuid.UUID
|
||||
EndNodes []uuid.UUID
|
||||
}
|
||||
|
||||
var (
|
||||
globalTopologyGraphMu sync.RWMutex
|
||||
GlobalTopologyGraph *TopologyGraph
|
||||
)
|
||||
|
||||
// NewTopologyGraph builds a directed graph cache from topologic edges.
|
||||
func NewTopologyGraph(edges []orm.Topologic) *TopologyGraph {
|
||||
graph := &TopologyGraph{
|
||||
Nodes: make(map[uuid.UUID]struct{}, len(edges)*2),
|
||||
OutEdges: make(map[uuid.UUID][]uuid.UUID, len(edges)),
|
||||
InEdges: make(map[uuid.UUID][]uuid.UUID, len(edges)),
|
||||
}
|
||||
|
||||
for _, edge := range edges {
|
||||
from := edge.UUIDFrom
|
||||
to := edge.UUIDTo
|
||||
|
||||
graph.Nodes[from] = struct{}{}
|
||||
graph.Nodes[to] = struct{}{}
|
||||
graph.OutEdges[from] = append(graph.OutEdges[from], to)
|
||||
graph.InEdges[to] = append(graph.InEdges[to], from)
|
||||
}
|
||||
|
||||
graph.StartNodes = graph.findStartNodes()
|
||||
graph.EndNodes = graph.findEndNodes()
|
||||
return graph
|
||||
}
|
||||
|
||||
// SetGlobalTopologyGraph replaces the process-wide topology graph cache.
|
||||
func SetGlobalTopologyGraph(graph *TopologyGraph) {
|
||||
globalTopologyGraphMu.Lock()
|
||||
defer globalTopologyGraphMu.Unlock()
|
||||
GlobalTopologyGraph = graph
|
||||
}
|
||||
|
||||
// GetGlobalTopologyGraph returns the process-wide topology graph cache.
|
||||
func GetGlobalTopologyGraph() *TopologyGraph {
|
||||
globalTopologyGraphMu.RLock()
|
||||
defer globalTopologyGraphMu.RUnlock()
|
||||
return GlobalTopologyGraph
|
||||
}
|
||||
|
||||
func (g *TopologyGraph) findStartNodes() []uuid.UUID {
|
||||
startNodes := make([]uuid.UUID, 0)
|
||||
for id := range g.Nodes {
|
||||
if len(g.InEdges[id]) == 0 && len(g.OutEdges[id]) > 0 {
|
||||
startNodes = append(startNodes, id)
|
||||
}
|
||||
}
|
||||
return startNodes
|
||||
}
|
||||
|
||||
func (g *TopologyGraph) findEndNodes() []uuid.UUID {
|
||||
endNodes := make([]uuid.UUID, 0)
|
||||
for id := range g.Nodes {
|
||||
if len(g.InEdges[id]) > 0 && len(g.OutEdges[id]) == 0 {
|
||||
endNodes = append(endNodes, id)
|
||||
}
|
||||
}
|
||||
return endNodes
|
||||
}
|
||||
|
||||
// IsReachable reports whether end can be reached from start following directed
|
||||
// uuid_from -> uuid_to edges.
|
||||
func (g *TopologyGraph) IsReachable(start, end uuid.UUID) bool {
|
||||
return len(g.FindPath(start, end)) > 0
|
||||
}
|
||||
|
||||
// FindPath returns one shortest directed path from start to end, or nil when
|
||||
// no directed path exists.
|
||||
func (g *TopologyGraph) FindPath(start, end uuid.UUID) []uuid.UUID {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
if start == end {
|
||||
if _, exists := g.Nodes[start]; exists {
|
||||
return []uuid.UUID{start}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
visited := map[uuid.UUID]struct{}{start: {}}
|
||||
parent := make(map[uuid.UUID]uuid.UUID)
|
||||
queue := []uuid.UUID{start}
|
||||
|
||||
for len(queue) > 0 {
|
||||
cur := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
for _, next := range g.OutEdges[cur] {
|
||||
if _, seen := visited[next]; seen {
|
||||
continue
|
||||
}
|
||||
visited[next] = struct{}{}
|
||||
parent[next] = cur
|
||||
|
||||
if next == end {
|
||||
return reconstructTopologyGraphPath(parent, start, end)
|
||||
}
|
||||
|
||||
queue = append(queue, next)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func reconstructTopologyGraphPath(parent map[uuid.UUID]uuid.UUID, start, end uuid.UUID) []uuid.UUID {
|
||||
path := make([]uuid.UUID, 0)
|
||||
for cur := end; cur != start; cur = parent[cur] {
|
||||
path = append(path, cur)
|
||||
}
|
||||
path = append(path, start)
|
||||
for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
|
||||
path[i], path[j] = path[j], path[i]
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package diagram
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"modelRT/orm"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
func TestTopologyGraphSupportsMultiParentReachability(t *testing.T) {
|
||||
startA := uuid.Must(uuid.NewV4())
|
||||
startB := uuid.Must(uuid.NewV4())
|
||||
shared := uuid.Must(uuid.NewV4())
|
||||
end := uuid.Must(uuid.NewV4())
|
||||
|
||||
graph := NewTopologyGraph([]orm.Topologic{
|
||||
{UUIDFrom: startA, UUIDTo: shared},
|
||||
{UUIDFrom: startB, UUIDTo: shared},
|
||||
{UUIDFrom: shared, UUIDTo: end},
|
||||
})
|
||||
|
||||
if len(graph.StartNodes) != 2 {
|
||||
t.Fatalf("expected 2 start nodes, got %d", len(graph.StartNodes))
|
||||
}
|
||||
if len(graph.InEdges[shared]) != 2 {
|
||||
t.Fatalf("expected shared node to keep 2 parents, got %d", len(graph.InEdges[shared]))
|
||||
}
|
||||
if !graph.IsReachable(startA, end) {
|
||||
t.Fatalf("expected %s to reach %s", startA, end)
|
||||
}
|
||||
if !graph.IsReachable(startB, end) {
|
||||
t.Fatalf("expected %s to reach %s", startB, end)
|
||||
}
|
||||
}
|
||||
|
|
@ -524,6 +524,10 @@ const docTemplate = `{
|
|||
" \"I_B_rms\"",
|
||||
"\"I_C_rms\"]"
|
||||
]
|
||||
},
|
||||
"recommended_type": {
|
||||
"type": "string",
|
||||
"example": "grid_tag"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -518,6 +518,10 @@
|
|||
" \"I_B_rms\"",
|
||||
"\"I_C_rms\"]"
|
||||
]
|
||||
},
|
||||
"recommended_type": {
|
||||
"type": "string",
|
||||
"example": "grid_tag"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ definitions:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
recommended_type:
|
||||
example: grid_tag
|
||||
type: string
|
||||
type: object
|
||||
network.RealTimeDataPayload:
|
||||
properties:
|
||||
|
|
|
|||
|
|
@ -15,14 +15,13 @@ import (
|
|||
|
||||
// QueryAlertEventHandler define query alert event process API
|
||||
func QueryAlertEventHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var targetLevel constants.AlertLevel
|
||||
|
||||
alertManger := alert.GetAlertMangerInstance()
|
||||
levelStr := c.Query("level")
|
||||
level, err := strconv.Atoi(levelStr)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "convert alert level string to int failed", "error", err)
|
||||
logger.Error(c, "convert alert level string to int failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: -1,
|
||||
|
|
|
|||
|
|
@ -19,16 +19,15 @@ import (
|
|||
|
||||
// ComponentAnchorReplaceHandler define component anchor point replace process API
|
||||
func ComponentAnchorReplaceHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var uuid, anchorName string
|
||||
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
cancelCtx, cancel := context.WithTimeout(c, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var request network.ComponetAnchorReplaceRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "unmarshal component anchor point replace info failed", "error", err)
|
||||
logger.Error(c, "unmarshal component anchor point replace info failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -43,7 +42,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
|
|||
var componentInfo orm.Component
|
||||
result := pgClient.WithContext(cancelCtx).Model(&orm.Component{}).Where("global_uuid = ?", uuid).Find(&componentInfo)
|
||||
if result.Error != nil {
|
||||
logger.Error(ctx, "query component detail info failed", "error", result.Error)
|
||||
logger.Error(c, "query component detail info failed", "error", result.Error)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -55,7 +54,7 @@ func ComponentAnchorReplaceHandler(c *gin.Context) {
|
|||
|
||||
if result.RowsAffected == 0 {
|
||||
err := fmt.Errorf("query component detail info by uuid failed:%w", errcode.ErrQueryRowZero)
|
||||
logger.Error(ctx, "query component detail info from table is empty", "table_name", "component")
|
||||
logger.Error(c, "query component detail info from table is empty", "table_name", "component")
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/database"
|
||||
"modelRT/logger"
|
||||
|
|
@ -154,8 +152,6 @@ func validateBatchImportParams(params map[string]any) bool {
|
|||
func validateTestTaskParams(params map[string]any) bool {
|
||||
// Test task has optional parameters, all are valid
|
||||
// sleep_duration defaults to 60 seconds if not provided
|
||||
// TODO Add more validation logic for test task parameters if needed
|
||||
fmt.Println(params)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ import (
|
|||
|
||||
// AttrDeleteHandler deletes a data attribute
|
||||
func AttrDeleteHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.AttrDeleteRequest
|
||||
clientToken := c.GetString("client_token")
|
||||
if clientToken == "" {
|
||||
err := common.ErrGetClientToken
|
||||
|
||||
logger.Error(ctx, "failed to get client token from context", "error", err)
|
||||
logger.Error(c, "failed to get client token from context", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -28,7 +27,7 @@ func AttrDeleteHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal attribute delete request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal attribute delete request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -36,9 +35,9 @@ func AttrDeleteHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
rs := diagram.NewRedisString(ctx, request.AttrToken, clientToken, 10, true)
|
||||
rs := diagram.NewRedisString(c, request.AttrToken, clientToken, 10, true)
|
||||
if err := rs.GETDEL(request.AttrToken); err != nil {
|
||||
logger.Error(ctx, "failed to delete attribute from Redis", "attr_token", request.AttrToken, "error", err)
|
||||
logger.Error(c, "failed to delete attribute from Redis", "attr_token", request.AttrToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ import (
|
|||
|
||||
// AttrGetHandler retrieves the value of a data attribute
|
||||
func AttrGetHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.AttrGetRequest
|
||||
|
||||
clientToken := c.GetString("client_token")
|
||||
if clientToken == "" {
|
||||
err := common.ErrGetClientToken
|
||||
|
||||
logger.Error(ctx, "failed to get client token from context", "error", err)
|
||||
logger.Error(c, "failed to get client token from context", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -29,7 +28,7 @@ func AttrGetHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal attribute get request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal attribute get request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -38,12 +37,12 @@ func AttrGetHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
tx := pgClient.WithContext(ctx).Begin()
|
||||
tx := pgClient.Begin()
|
||||
|
||||
attrModel, err := database.ParseAttrToken(ctx, tx, request.AttrToken, clientToken)
|
||||
attrModel, err := database.ParseAttrToken(c, tx, request.AttrToken, clientToken)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
logger.Error(ctx, "failed to parse attribute token", "attr_token", request.AttrToken, "error", err)
|
||||
logger.Error(c, "failed to parse attribute token", "attr_token", request.AttrToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
|
|||
|
|
@ -13,14 +13,13 @@ import (
|
|||
|
||||
// AttrSetHandler sets the value of a data attribute
|
||||
func AttrSetHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.AttrSetRequest
|
||||
|
||||
clientToken := c.GetString("client_token")
|
||||
if clientToken == "" {
|
||||
err := common.ErrGetClientToken
|
||||
|
||||
logger.Error(ctx, "failed to get client token from context", "error", err)
|
||||
logger.Error(c, "failed to get client token from context", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -29,7 +28,7 @@ func AttrSetHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal attribute set request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal attribute set request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -38,9 +37,9 @@ func AttrSetHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// The logic for handling Redis operations directly from the handler
|
||||
rs := diagram.NewRedisString(ctx, request.AttrToken, clientToken, 10, true)
|
||||
rs := diagram.NewRedisString(c, request.AttrToken, clientToken, 10, true)
|
||||
if err := rs.Set(request.AttrToken, request.AttrValue); err != nil {
|
||||
logger.Error(ctx, "failed to set attribute value in Redis", "attr_token", request.AttrToken, "error", err)
|
||||
logger.Error(c, "failed to set attribute value in Redis", "attr_token", request.AttrToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@ import (
|
|||
|
||||
// CircuitDiagramCreateHandler define circuit diagram create process API
|
||||
func CircuitDiagramCreateHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
|
||||
var request network.CircuitDiagramCreateRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "unmarshal circuit diagram create info failed", "error", err)
|
||||
logger.Error(c, "unmarshal circuit diagram create info failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -33,7 +32,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
|
||||
graph, err := diagram.GetGraphMap(request.PageID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
|
||||
logger.Error(c, "get topologic data from set by pageID failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -61,7 +60,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
err = fmt.Errorf("convert uuid from string failed:%w:%w", err1, err2)
|
||||
}
|
||||
|
||||
logger.Error(ctx, "format uuid from string failed", "error", err)
|
||||
logger.Error(c, "format uuid from string failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -79,13 +78,13 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// open transaction
|
||||
tx := pgClient.WithContext(ctx).Begin()
|
||||
tx := pgClient.Begin()
|
||||
|
||||
err = database.CreateTopologicIntoDB(ctx, tx, request.PageID, topologicCreateInfos)
|
||||
err = database.CreateTopologicIntoDB(c, tx, request.PageID, topologicCreateInfos)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "create topologic info into DB failed", "topologic_info", topologicCreateInfos, "error", err)
|
||||
logger.Error(c, "create topologic info into DB failed", "topologic_info", topologicCreateInfos, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -103,11 +102,11 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
for index, info := range request.ComponentInfos {
|
||||
componentUUID, err := database.CreateComponentIntoDB(ctx, tx, info)
|
||||
componentUUID, err := database.CreateComponentIntoDB(c, tx, info)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "insert component info into DB failed", "error", err)
|
||||
logger.Error(c, "insert component info into DB failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -126,7 +125,7 @@ func CircuitDiagramCreateHandler(c *gin.Context) {
|
|||
// TODO 修复赋值问题
|
||||
component, err := network.ConvertComponentCreateInfosToComponents(info)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "convert component params info failed", "component_info", info, "error", err)
|
||||
logger.Error(c, "convert component params info failed", "component_info", info, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
|
|||
|
|
@ -21,12 +21,11 @@ import (
|
|||
|
||||
// CircuitDiagramDeleteHandler define circuit diagram delete process API
|
||||
func CircuitDiagramDeleteHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
|
||||
var request network.CircuitDiagramDeleteRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "unmarshal circuit diagram del info failed", "error", err)
|
||||
logger.Error(c, "unmarshal circuit diagram del info failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -38,7 +37,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
|
||||
graph, err := diagram.GetGraphMap(request.PageID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
|
||||
logger.Error(c, "get topologic data from set by pageID failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -66,7 +65,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
err = fmt.Errorf("convert uuid from string failed:%w:%w", err1, err2)
|
||||
}
|
||||
|
||||
logger.Error(ctx, "format uuid from string failed", "error", err)
|
||||
logger.Error(c, "format uuid from string failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -84,14 +83,14 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// open transaction
|
||||
tx := pgClient.WithContext(ctx).Begin()
|
||||
tx := pgClient.Begin()
|
||||
|
||||
for _, topologicDelInfo := range topologicDelInfos {
|
||||
err = database.DeleteTopologicIntoDB(ctx, tx, request.PageID, topologicDelInfo)
|
||||
err = database.DeleteTopologicIntoDB(c, tx, request.PageID, topologicDelInfo)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "delete topologic info into DB failed", "topologic_info", topologicDelInfo, "error", err)
|
||||
logger.Error(c, "delete topologic info into DB failed", "topologic_info", topologicDelInfo, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -108,7 +107,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "delete topologic info failed", "topologic_info", topologicDelInfo, "error", err)
|
||||
logger.Error(c, "delete topologic info failed", "topologic_info", topologicDelInfo, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -127,14 +126,14 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
for _, componentInfo := range request.ComponentInfos {
|
||||
cancelCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
cancelCtx, cancel := context.WithTimeout(c, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
globalUUID, err := uuid.FromString(componentInfo.UUID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "format uuid from string failed", "error", err)
|
||||
logger.Error(c, "format uuid from string failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -158,7 +157,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero)
|
||||
}
|
||||
|
||||
logger.Error(ctx, "query component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err)
|
||||
logger.Error(c, "query component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -180,7 +179,7 @@ func CircuitDiagramDeleteHandler(c *gin.Context) {
|
|||
err = fmt.Errorf("%w:please check uuid conditions", errcode.ErrDeleteRowZero)
|
||||
}
|
||||
|
||||
logger.Error(ctx, "delete component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err)
|
||||
logger.Error(c, "delete component info into postgresDB failed", "component_global_uuid", componentInfo.UUID, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
|
|||
|
|
@ -24,12 +24,11 @@ import (
|
|||
// @Failure 400 {object} network.FailureResponse "request process failed"
|
||||
// @Router /model/diagram_load/{page_id} [get]
|
||||
func CircuitDiagramLoadHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
|
||||
pageID, err := strconv.ParseInt(c.Query("page_id"), 10, 64)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get pageID from url param failed", "error", err)
|
||||
logger.Error(c, "get pageID from url param failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -44,7 +43,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
|
||||
topologicInfo, err := diagram.GetGraphMap(pageID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
|
||||
logger.Error(c, "get topologic data from set by pageID failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -63,9 +62,9 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
componentParamMap := make(map[string]any)
|
||||
for _, VerticeLink := range topologicInfo.VerticeLinks {
|
||||
for _, componentUUID := range VerticeLink {
|
||||
component, err := database.QueryComponentByUUID(ctx, pgClient, componentUUID)
|
||||
component, err := database.QueryComponentByUUID(c, pgClient, componentUUID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get component id info from DB by uuid failed", "error", err)
|
||||
logger.Error(c, "get component id info from DB by uuid failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -80,7 +79,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
|
||||
componentParams, err := diagram.GetComponentMap(component.GlobalUUID.String())
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get component data from set by uuid failed", "error", err)
|
||||
logger.Error(c, "get component data from set by uuid failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -97,9 +96,9 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
rootVertexUUID := topologicInfo.RootVertex.String()
|
||||
rootComponent, err := database.QueryComponentByUUID(ctx, pgClient, topologicInfo.RootVertex)
|
||||
rootComponent, err := database.QueryComponentByUUID(c, pgClient, topologicInfo.RootVertex)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get component id info from DB by uuid failed", "error", err)
|
||||
logger.Error(c, "get component id info from DB by uuid failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -114,7 +113,7 @@ func CircuitDiagramLoadHandler(c *gin.Context) {
|
|||
|
||||
rootComponentParam, err := diagram.GetComponentMap(rootComponent.GlobalUUID.String())
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get component data from set by uuid failed", "error", err)
|
||||
logger.Error(c, "get component data from set by uuid failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
|
|||
|
|
@ -14,12 +14,11 @@ import (
|
|||
|
||||
// CircuitDiagramUpdateHandler define circuit diagram update process API
|
||||
func CircuitDiagramUpdateHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
|
||||
var request network.CircuitDiagramUpdateRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "unmarshal circuit diagram update info failed", "error", err)
|
||||
logger.Error(c, "unmarshal circuit diagram update info failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -31,7 +30,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
|
||||
graph, err := diagram.GetGraphMap(request.PageID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "get topologic data from set by pageID failed", "error", err)
|
||||
logger.Error(c, "get topologic data from set by pageID failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -48,7 +47,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
for _, topologicLink := range request.TopologicLinks {
|
||||
changeInfo, err := network.ParseUUID(topologicLink)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "format uuid from string failed", "error", err)
|
||||
logger.Error(c, "format uuid from string failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -64,14 +63,14 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// open transaction
|
||||
tx := pgClient.WithContext(ctx).Begin()
|
||||
tx := pgClient.Begin()
|
||||
|
||||
for _, topologicChangeInfo := range topologicChangeInfos {
|
||||
err = database.UpdateTopologicIntoDB(ctx, tx, request.PageID, topologicChangeInfo)
|
||||
err = database.UpdateTopologicIntoDB(c, tx, request.PageID, topologicChangeInfo)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "update topologic info into DB failed", "topologic_info", topologicChangeInfo, "error", err)
|
||||
logger.Error(c, "update topologic info into DB failed", "topologic_info", topologicChangeInfo, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -88,7 +87,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
logger.Error(ctx, "update topologic info failed", "topologic_info", topologicChangeInfo, "error", err)
|
||||
logger.Error(c, "update topologic info failed", "topologic_info", topologicChangeInfo, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -103,9 +102,9 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
for index, componentInfo := range request.ComponentInfos {
|
||||
componentUUID, err := database.UpdateComponentIntoDB(ctx, tx, componentInfo)
|
||||
componentUUID, err := database.UpdateComponentIntoDB(c, tx, componentInfo)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "udpate component info into DB failed", "error", err)
|
||||
logger.Error(c, "udpate component info into DB failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -125,7 +124,7 @@ func CircuitDiagramUpdateHandler(c *gin.Context) {
|
|||
// TODO 修复赋值问题
|
||||
component, err := network.ConvertComponentUpdateInfosToComponents(info)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "convert component params info failed", "component_info", info, "error", err)
|
||||
logger.Error(c, "convert component params info failed", "component_info", info, "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
|
|||
|
|
@ -22,13 +22,12 @@ import (
|
|||
|
||||
// ComponentAttributeQueryHandler define circuit diagram component attribute value query process API
|
||||
func ComponentAttributeQueryHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
|
||||
tokens := c.Param("tokens")
|
||||
if tokens == "" {
|
||||
err := fmt.Errorf("tokens is missing from the path")
|
||||
logger.Error(ctx, "query tokens from path failed", "error", err, "url", c.Request.RequestURI)
|
||||
logger.Error(c, "query tokens from path failed", "error", err, "url", c.Request.RequestURI)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -55,10 +54,10 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
|
|||
dbQueryMap := make(map[string][]cacheQueryItem)
|
||||
var secondaryQueryCount int
|
||||
for hSetKey, items := range cacheQueryMap {
|
||||
hset := diagram.NewRedisHash(ctx, hSetKey, 5000, false)
|
||||
hset := diagram.NewRedisHash(c, hSetKey, 5000, false)
|
||||
cacheData, err := hset.HGetAll()
|
||||
if err != nil {
|
||||
logger.Warn(ctx, "redis hgetall failed", "key", hSetKey, "err", err)
|
||||
logger.Warn(c, "redis hgetall failed", "key", hSetKey, "err", err)
|
||||
}
|
||||
for _, item := range items {
|
||||
if val, ok := cacheData[item.attributeName]; ok {
|
||||
|
|
@ -76,9 +75,9 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
tx := pgClient.WithContext(ctx).Begin()
|
||||
tx := pgClient.WithContext(c).Begin()
|
||||
if tx.Error != nil {
|
||||
logger.Error(ctx, "begin postgres transaction failed", "error", tx.Error)
|
||||
logger.Error(c, "begin postgres transaction failed", "error", tx.Error)
|
||||
fillRemainingErrors(queryResults, tokenSlice, errcode.ErrBeginTxFailed)
|
||||
payload := genQueryRespPayload(queryResults, tokenSlice)
|
||||
renderRespFailure(c, constants.RespCodeServerError, "begin postgres database transaction failed", payload)
|
||||
|
|
@ -87,9 +86,9 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
|
|||
defer tx.Rollback()
|
||||
|
||||
allCompTags := slices.Collect(maps.Keys(dbQueryMap))
|
||||
compModelMap, err := database.QueryComponentByCompTags(ctx, tx, allCompTags)
|
||||
compModelMap, err := database.QueryComponentByCompTags(c, tx, allCompTags)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query component info from postgres database failed", "error", err)
|
||||
logger.Error(c, "query component info from postgres database failed", "error", err)
|
||||
fillRemainingErrors(queryResults, tokenSlice, errcode.ErrDBQueryFailed)
|
||||
payload := genQueryRespPayload(queryResults, tokenSlice)
|
||||
renderRespFailure(c, constants.RespCodeServerError, "query component meta failed", payload)
|
||||
|
|
@ -117,7 +116,7 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
|
|||
|
||||
tableNameMap, err := database.BatchGetProjectNames(tx, identifiers)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "batch get table names from postgres database failed", "error", err)
|
||||
logger.Error(c, "batch get table names from postgres database failed", "error", err)
|
||||
fillRemainingErrors(queryResults, tokenSlice, errcode.ErrRetrieveFailed)
|
||||
payload := genQueryRespPayload(queryResults, tokenSlice)
|
||||
renderRespFailure(c, constants.RespCodeServerError, "batch get table names from postgres database failed", payload)
|
||||
|
|
@ -152,11 +151,10 @@ func ComponentAttributeQueryHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
logger.Warn(ctx, "postgres transaction commit failed, but returning scanned data", "error", err)
|
||||
logger.Warn(c, "postgres transaction commit failed, but returning scanned data", "error", err)
|
||||
} else {
|
||||
backfillCtx := context.WithoutCancel(ctx)
|
||||
for hKey, items := range redisSyncMap {
|
||||
go backfillRedis(backfillCtx, hKey, items)
|
||||
go backfillRedis(c.Copy(), hKey, items)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,10 @@ import (
|
|||
|
||||
// ComponentAttributeUpdateHandler define circuit diagram component attribute value update process API
|
||||
func ComponentAttributeUpdateHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
var request network.ComponentAttributeUpdateInfo
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "unmarshal request params failed", "error", err)
|
||||
logger.Error(c, "unmarshal request params failed", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -55,16 +54,16 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// open transaction
|
||||
tx := pgClient.WithContext(ctx).Begin()
|
||||
tx := pgClient.WithContext(c).Begin()
|
||||
if tx.Error != nil {
|
||||
logger.Error(ctx, "begin postgres transaction failed", "error", tx.Error)
|
||||
logger.Error(c, "begin postgres transaction failed", "error", tx.Error)
|
||||
renderRespFailure(c, constants.RespCodeServerError, "begin postgres transaction failed", nil)
|
||||
return
|
||||
}
|
||||
|
||||
compInfo, err := database.QueryComponentByCompTag(ctx, tx, attributeComponentTag)
|
||||
compInfo, err := database.QueryComponentByCompTag(c, tx, attributeComponentTag)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query component info by component tag failed", "error", err, "tag", attributeComponentTag)
|
||||
logger.Error(c, "query component info by component tag failed", "error", err, "tag", attributeComponentTag)
|
||||
|
||||
for _, attribute := range request.AttributeConfigs {
|
||||
if _, exists := updateResults[attribute.AttributeToken]; !exists {
|
||||
|
|
@ -140,7 +139,7 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
for key, items := range redisUpdateMap {
|
||||
hset := diagram.NewRedisHash(ctx, key, 5000, false)
|
||||
hset := diagram.NewRedisHash(c, key, 5000, false)
|
||||
|
||||
fields := make(map[string]any, len(items))
|
||||
for _, item := range items {
|
||||
|
|
@ -148,7 +147,7 @@ func ComponentAttributeUpdateHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := hset.SetRedisHashByMap(fields); err != nil {
|
||||
logger.Error(ctx, "batch sync redis failed", "hash_key", key, "error", err)
|
||||
logger.Error(c, "batch sync redis failed", "hash_key", key, "error", err)
|
||||
|
||||
for _, item := range items {
|
||||
if _, exists := updateResults[item.token]; exists {
|
||||
|
|
|
|||
|
|
@ -41,12 +41,11 @@ var linkSetConfigs = map[int]linkSetConfig{
|
|||
|
||||
// DiagramNodeLinkHandler defines the diagram node link process api
|
||||
func DiagramNodeLinkHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.DiagramNodeLinkRequest
|
||||
clientToken := c.GetString("client_token")
|
||||
if clientToken == "" {
|
||||
err := common.ErrGetClientToken
|
||||
logger.Error(ctx, "failed to get client token from context", "error", err)
|
||||
logger.Error(c, "failed to get client token from context", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -55,7 +54,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal diagram node process request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal diagram node process request", "error", err)
|
||||
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -69,9 +68,9 @@ func DiagramNodeLinkHandler(c *gin.Context) {
|
|||
nodeID := request.NodeID
|
||||
nodeLevel := request.NodeLevel
|
||||
action := request.Action
|
||||
prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(ctx, pgClient, nodeID, nodeLevel)
|
||||
prevNodeInfo, currNodeInfo, err := database.QueryNodeInfoByID(c, pgClient, nodeID, nodeLevel)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to query diagram node info by nodeID and level from postgres", "node_id", nodeID, "level", nodeLevel, "error", err)
|
||||
logger.Error(c, "failed to query diagram node info by nodeID and level from postgres", "node_id", nodeID, "level", nodeLevel, "error", err)
|
||||
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -85,8 +84,8 @@ func DiagramNodeLinkHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
prevLinkSet, currLinkSet := generateLinkSet(ctx, nodeLevel, prevNodeInfo)
|
||||
err = processLinkSetData(ctx, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo)
|
||||
prevLinkSet, currLinkSet := generateLinkSet(c, nodeLevel, prevNodeInfo)
|
||||
err = processLinkSetData(c, action, nodeLevel, prevLinkSet, currLinkSet, prevNodeInfo, currNodeInfo)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -100,7 +99,7 @@ func DiagramNodeLinkHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
logger.Info(ctx, "process diagram node link success", "node_id", nodeID, "level", nodeLevel, "action", request.Action)
|
||||
logger.Info(c, "process diagram node link success", "node_id", nodeID, "level", nodeLevel, "action", request.Action)
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@ import (
|
|||
|
||||
// QueryHistoryDataHandler define query history data process API
|
||||
func QueryHistoryDataHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
token := c.Query("token")
|
||||
beginStr := c.Query("begin")
|
||||
begin, err := strconv.Atoi(beginStr)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "convert begin param from string to int failed", "error", err)
|
||||
logger.Error(c, "convert begin param from string to int failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -32,7 +31,7 @@ func QueryHistoryDataHandler(c *gin.Context) {
|
|||
endStr := c.Query("end")
|
||||
end, err := strconv.Atoi(endStr)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "convert end param from string to int failed", "error", err)
|
||||
logger.Error(c, "convert end param from string to int failed", "error", err)
|
||||
|
||||
resp := network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
|
|||
|
|
@ -15,14 +15,13 @@ import (
|
|||
|
||||
// MeasurementGetHandler define measurement query API
|
||||
func MeasurementGetHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.MeasurementGetRequest
|
||||
|
||||
clientToken := c.GetString("client_token")
|
||||
if clientToken == "" {
|
||||
err := common.ErrGetClientToken
|
||||
|
||||
logger.Error(ctx, "failed to get client token from context", "error", err)
|
||||
logger.Error(c, "failed to get client token from context", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -31,7 +30,7 @@ func MeasurementGetHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal measurement get request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal measurement get request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -39,10 +38,10 @@ func MeasurementGetHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
zset := diagram.NewRedisZSet(ctx, request.MeasurementToken, 0, false)
|
||||
zset := diagram.NewRedisZSet(c, request.MeasurementToken, 0, false)
|
||||
points, err := zset.ZRANGE(request.MeasurementToken, 0, -1)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to get measurement data from redis", "measurement_token", request.MeasurementToken, "error", err)
|
||||
logger.Error(c, "failed to get measurement data from redis", "measurement_token", request.MeasurementToken, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -55,9 +54,9 @@ func MeasurementGetHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
pgClient := database.GetPostgresDBClient()
|
||||
measurementInfo, err := database.QueryMeasurementByID(ctx, pgClient, request.MeasurementID)
|
||||
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, request.MeasurementID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to query measurement by id", "measurement_id", request.MeasurementID, "error", err)
|
||||
logger.Error(c, "failed to query measurement by id", "measurement_id", request.MeasurementID, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/logger"
|
||||
"modelRT/model"
|
||||
"modelRT/network"
|
||||
"modelRT/util"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
@ -45,81 +43,79 @@ import (
|
|||
//
|
||||
// @Router /measurement/recommend [get]
|
||||
func MeasurementRecommendHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.MeasurementRecommendRequest
|
||||
|
||||
if err := c.ShouldBindQuery(&request); err != nil {
|
||||
logger.Error(ctx, "failed to bind measurement recommend request", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
if err := validateMeasurementRecommendInput(request.Input); err != nil {
|
||||
logger.Warn(ctx, "invalid measurement recommend input", "input", request.Input, "error", err)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), map[string]any{
|
||||
"input": request.Input,
|
||||
logger.Error(c, "failed to bind measurement recommend request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
recommendResults := model.RedisSearchRecommend(ctx, request.Input)
|
||||
payload := network.MeasurementRecommendPayload{
|
||||
Input: request.Input,
|
||||
RecommendedList: make([]string, 0),
|
||||
}
|
||||
seen := make(map[string]struct{})
|
||||
orderedResults := orderedRecommendResults(recommendResults)
|
||||
|
||||
for _, recommendResult := range orderedResults {
|
||||
recommendResults := model.RedisSearchRecommend(c, request.Input)
|
||||
payloads := make([]network.MeasurementRecommendPayload, 0, len(recommendResults))
|
||||
for _, recommendResult := range recommendResults {
|
||||
if recommendResult.Err != nil {
|
||||
err := recommendResult.Err
|
||||
logger.Error(ctx, "failed to get recommend data from redis", "input", request.Input, "error", err)
|
||||
renderRespFailure(c, constants.RespCodeServerError, err.Error(), map[string]any{
|
||||
logger.Error(c, "failed to get recommend data from redis", "input", request.Input, "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusInternalServerError,
|
||||
Msg: err.Error(),
|
||||
Payload: map[string]any{
|
||||
"input": request.Input,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if recommendResult.Offset > payload.Offset {
|
||||
payload.Offset = recommendResult.Offset
|
||||
var finalOffset int
|
||||
recommends := recommendResult.QueryDatas
|
||||
if recommendResult.IsFuzzy {
|
||||
var maxOffset int
|
||||
for index, recommend := range recommends {
|
||||
offset := util.GetLongestCommonPrefixLength(request.Input, recommend)
|
||||
if index == 0 || offset > maxOffset {
|
||||
maxOffset = offset
|
||||
}
|
||||
}
|
||||
finalOffset = maxOffset
|
||||
} else {
|
||||
var minOffset int
|
||||
for index, recommend := range recommends {
|
||||
offset := util.GetLongestCommonPrefixLength(request.Input, recommend)
|
||||
if index == 0 || offset < minOffset {
|
||||
minOffset = offset
|
||||
}
|
||||
}
|
||||
finalOffset = minOffset
|
||||
}
|
||||
|
||||
for _, recommendResult := range orderedResults {
|
||||
for _, recommend := range recommendResult.QueryDatas {
|
||||
if _, exists := seen[recommend]; !exists {
|
||||
seen[recommend] = struct{}{}
|
||||
payload.RecommendedList = append(payload.RecommendedList, recommend)
|
||||
resultRecommends := make([]string, 0, len(recommends))
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for _, recommend := range recommends {
|
||||
recommendTerm := recommend[finalOffset:]
|
||||
if len(recommendTerm) != 0 {
|
||||
if _, exists := seen[recommendTerm]; !exists {
|
||||
seen[recommendTerm] = struct{}{}
|
||||
resultRecommends = append(resultRecommends, recommendTerm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderRespSuccess(c, constants.RespCodeSuccess, "success", payload)
|
||||
}
|
||||
|
||||
func orderedRecommendResults(recommendResults map[string]model.SearchResult) []model.SearchResult {
|
||||
orderedTypes := []string{
|
||||
constants.CompNSPathRecommendHierarchyType.String(),
|
||||
constants.GridRecommendHierarchyType.String(),
|
||||
constants.ZoneRecommendHierarchyType.String(),
|
||||
constants.StationRecommendHierarchyType.String(),
|
||||
constants.CompTagRecommendHierarchyType.String(),
|
||||
constants.ConfigRecommendHierarchyType.String(),
|
||||
constants.MeasTagRecommendHierarchyType.String(),
|
||||
}
|
||||
|
||||
results := make([]model.SearchResult, 0, len(recommendResults))
|
||||
for _, recommendType := range orderedTypes {
|
||||
result, ok := recommendResults[recommendType]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func validateMeasurementRecommendInput(input string) error {
|
||||
if strings.Contains(input, "..") {
|
||||
return errors.New("input contains continuous dots")
|
||||
}
|
||||
return nil
|
||||
payloads = append(payloads, network.MeasurementRecommendPayload{
|
||||
Input: request.Input,
|
||||
Offset: finalOffset,
|
||||
RecommendType: recommendResult.RecommendType.String(),
|
||||
RecommendedList: resultRecommends,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
Msg: "success",
|
||||
Payload: &payloads,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
package handler
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateMeasurementRecommendInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
name: "continuous dots",
|
||||
input: "G..",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "single separator",
|
||||
input: "G.zone",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "trailing dot",
|
||||
input: "G.",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "empty input",
|
||||
input: "",
|
||||
valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validateMeasurementRecommendInput(tt.input)
|
||||
if tt.valid && err != nil {
|
||||
t.Fatalf("expected valid input, got error %v", err)
|
||||
}
|
||||
if !tt.valid && err == nil {
|
||||
t.Fatalf("expected invalid input")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -18,12 +18,11 @@ import (
|
|||
|
||||
// MeasurementLinkHandler defines the measurement link process api
|
||||
func MeasurementLinkHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.MeasurementLinkRequest
|
||||
clientToken := c.GetString("client_token")
|
||||
if clientToken == "" {
|
||||
err := common.ErrGetClientToken
|
||||
logger.Error(ctx, "failed to get client token from context", "error", err)
|
||||
logger.Error(c, "failed to get client token from context", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -32,7 +31,7 @@ func MeasurementLinkHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal measurement process request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal measurement process request", "error", err)
|
||||
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -45,9 +44,9 @@ func MeasurementLinkHandler(c *gin.Context) {
|
|||
pgClient := database.GetPostgresDBClient()
|
||||
measurementID := request.MeasurementID
|
||||
action := request.Action
|
||||
measurementInfo, err := database.QueryMeasurementByID(ctx, pgClient, measurementID)
|
||||
measurementInfo, err := database.QueryMeasurementByID(c, pgClient, measurementID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to query measurement info by measurement id from postgres", "meauserement_id", measurementID, "error", err)
|
||||
logger.Error(c, "failed to query measurement info by measurement id from postgres", "meauserement_id", measurementID, "error", err)
|
||||
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -60,9 +59,9 @@ func MeasurementLinkHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
componentInfo, err := database.QueryComponentByUUID(ctx, pgClient, measurementInfo.ComponentUUID)
|
||||
componentInfo, err := database.QueryComponentByUUID(c, pgClient, measurementInfo.ComponentUUID)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to query component info by component uuid from postgres", "component_uuid", measurementInfo.ComponentUUID, "error", err)
|
||||
logger.Error(c, "failed to query component info by component uuid from postgres", "component_uuid", measurementInfo.ComponentUUID, "error", err)
|
||||
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
|
@ -75,9 +74,9 @@ func MeasurementLinkHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
allMeasSet := diagram.NewRedisSet(ctx, constants.RedisAllMeasTagSetKey, 0, false)
|
||||
allMeasSet := diagram.NewRedisSet(c, constants.RedisAllMeasTagSetKey, 0, false)
|
||||
compMeasLinkKey := fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, componentInfo.Tag)
|
||||
compMeasLinkSet := diagram.NewRedisSet(ctx, compMeasLinkKey, 0, false)
|
||||
compMeasLinkSet := diagram.NewRedisSet(c, compMeasLinkKey, 0, false)
|
||||
|
||||
switch action {
|
||||
case constants.SearchLinkAddAction:
|
||||
|
|
@ -85,18 +84,18 @@ func MeasurementLinkHandler(c *gin.Context) {
|
|||
err2 := compMeasLinkSet.SADD(measurementInfo.Tag)
|
||||
err = processActionError(err1, err2, action)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "add measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err)
|
||||
logger.Error(c, "add measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err)
|
||||
}
|
||||
case constants.SearchLinkDelAction:
|
||||
err1 := allMeasSet.SREM(measurementInfo.Tag)
|
||||
err2 := compMeasLinkSet.SREM(measurementInfo.Tag)
|
||||
err = processActionError(err1, err2, action)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "del measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err)
|
||||
logger.Error(c, "del measurement link process operation failed", "measurement_id", measurementID, "action", action, "error", err)
|
||||
}
|
||||
default:
|
||||
err = common.ErrUnsupportedLinkAction
|
||||
logger.Error(ctx, "unsupport measurement link process action", "measurement_id", measurementID, "action", action, "error", err)
|
||||
logger.Error(c, "unsupport measurement link process action", "measurement_id", measurementID, "action", action, "error", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -111,7 +110,7 @@ func MeasurementLinkHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
logger.Info(ctx, "process measurement link success", "measurement_id", measurementID, "action", request.Action)
|
||||
logger.Info(c, "process measurement link success", "measurement_id", measurementID, "action", request.Action)
|
||||
|
||||
c.JSON(http.StatusOK, network.SuccessResponse{
|
||||
Code: http.StatusOK,
|
||||
|
|
|
|||
|
|
@ -35,28 +35,27 @@ var pullUpgrader = websocket.Upgrader{
|
|||
// @Tags RealTime Component Websocket
|
||||
// @Router /monitors/data/realtime/stream/:clientID [get]
|
||||
func PullRealTimeDataHandler(c *gin.Context) {
|
||||
requestCtx := c.Request.Context()
|
||||
clientID := c.Param("clientID")
|
||||
if clientID == "" {
|
||||
err := fmt.Errorf("clientID is missing from the path")
|
||||
logger.Error(requestCtx, "query clientID from path failed", "error", err, "url", c.Request.RequestURI)
|
||||
logger.Error(c, "query clientID from path failed", "error", err, "url", c.Request.RequestURI)
|
||||
renderWSRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := pullUpgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
logger.Error(requestCtx, "upgrade http protocol to websocket protocol failed", "error", err)
|
||||
logger.Error(c, "upgrade http protocol to websocket protocol failed", "error", err)
|
||||
renderWSRespFailure(c, constants.RespCodeServerError, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(requestCtx)
|
||||
ctx, cancel := context.WithCancel(c.Request.Context())
|
||||
defer cancel()
|
||||
|
||||
conn.SetCloseHandler(func(code int, text string) error {
|
||||
logger.Info(requestCtx, "websocket processor shutdown trigger",
|
||||
logger.Info(c.Request.Context(), "websocket processor shutdown trigger",
|
||||
"clientID", clientID, "code", code, "reason", text)
|
||||
|
||||
// call cancel to notify other goroutines to stop working
|
||||
|
|
|
|||
|
|
@ -60,11 +60,10 @@ var wsUpgrader = websocket.Upgrader{
|
|||
//
|
||||
// @Router /data/realtime [get]
|
||||
func QueryRealTimeDataHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.RealTimeQueryRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal real time query request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal real time query request", "error", err)
|
||||
c.JSON(http.StatusOK, network.FailureResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
Msg: err.Error(),
|
||||
|
|
@ -74,7 +73,7 @@ func QueryRealTimeDataHandler(c *gin.Context) {
|
|||
|
||||
conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "upgrade http protocol to websocket protocal failed", "error", err)
|
||||
logger.Error(c, "upgrade http protocol to websocket protocal failed", "error", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
|
@ -88,29 +87,29 @@ func QueryRealTimeDataHandler(c *gin.Context) {
|
|||
case data := <-transportChannel:
|
||||
respByte, err := jsoniter.Marshal(data)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "marshal real time data to bytes failed", "error", err)
|
||||
logger.Error(c, "marshal real time data to bytes failed", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(websocket.TextMessage, respByte)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "write message to websocket connection failed", "error", err)
|
||||
logger.Error(c, "write message to websocket connection failed", "error", err)
|
||||
continue
|
||||
}
|
||||
case <-closeChannel:
|
||||
logger.Info(ctx, "data receiving goroutine has been closed")
|
||||
logger.Info(c, "data receiving goroutine has been closed")
|
||||
// TODO 优化时间控制
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
err := conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "the session ended normally"), deadline)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "sending close control message failed", "error", err)
|
||||
logger.Error(c, "sending close control message failed", "error", err)
|
||||
}
|
||||
// gracefully close session processing
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
logger.Error(ctx, "websocket conn closed failed", "error", err)
|
||||
logger.Error(c, "websocket conn closed failed", "error", err)
|
||||
}
|
||||
logger.Info(ctx, "websocket connection closed successfully.")
|
||||
logger.Info(c, "websocket connection closed successfully.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,9 @@ var upgrader = websocket.Upgrader{
|
|||
|
||||
// RealTimeDataReceivehandler define real time data receive and process API
|
||||
func RealTimeDataReceivehandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "upgrade http protocol to websocket protocal failed", "error", err)
|
||||
logger.Error(c, "upgrade http protocol to websocket protocal failed", "error", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
|
@ -28,17 +27,17 @@ func RealTimeDataReceivehandler(c *gin.Context) {
|
|||
for {
|
||||
messageType, p, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
logger.Error(ctx, "read message from websocket connection failed", "error", err)
|
||||
logger.Error(c, "read message from websocket connection failed", "error", err)
|
||||
|
||||
respByte := processResponse(-1, "read message from websocket connection failed", nil)
|
||||
if len(respByte) == 0 {
|
||||
logger.Error(ctx, "process message from byte failed", "error", err)
|
||||
logger.Error(c, "process message from byte failed", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(messageType, respByte)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "write message to websocket connection failed", "error", err)
|
||||
logger.Error(c, "write message to websocket connection failed", "error", err)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
|
|
@ -47,17 +46,17 @@ func RealTimeDataReceivehandler(c *gin.Context) {
|
|||
var request network.RealTimeDataReceiveRequest
|
||||
err = jsoniter.Unmarshal([]byte(p), &request)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "unmarshal message from byte failed", "error", err)
|
||||
logger.Error(c, "unmarshal message from byte failed", "error", err)
|
||||
|
||||
respByte := processResponse(-1, "unmarshal message from byte failed", nil)
|
||||
if len(respByte) == 0 {
|
||||
logger.Error(ctx, "process message from byte failed", "error", err)
|
||||
logger.Error(c, "process message from byte failed", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(messageType, respByte)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "write message to websocket connection failed", "error", err)
|
||||
logger.Error(c, "write message to websocket connection failed", "error", err)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
|
|
@ -71,13 +70,13 @@ func RealTimeDataReceivehandler(c *gin.Context) {
|
|||
}
|
||||
respByte := processResponse(0, "success", payload)
|
||||
if len(respByte) == 0 {
|
||||
logger.Error(ctx, "process message from byte failed", "error", err)
|
||||
logger.Error(c, "process message from byte failed", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(messageType, respByte)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "write message to websocket connection failed", "error", err)
|
||||
logger.Error(c, "write message to websocket connection failed", "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,13 +77,12 @@ func init() {
|
|||
//
|
||||
// @Router /monitors/data/subscriptions [post]
|
||||
func RealTimeSubHandler(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
var request network.RealTimeSubRequest
|
||||
var subAction string
|
||||
var clientID string
|
||||
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
logger.Error(ctx, "failed to unmarshal real time query request", "error", err)
|
||||
logger.Error(c, "failed to unmarshal real time query request", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -92,7 +91,7 @@ func RealTimeSubHandler(c *gin.Context) {
|
|||
subAction = request.Action
|
||||
id, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to generate client id", "error", err)
|
||||
logger.Error(c, "failed to generate client id", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
|
@ -115,9 +114,9 @@ func RealTimeSubHandler(c *gin.Context) {
|
|||
|
||||
switch subAction {
|
||||
case constants.SubStartAction:
|
||||
results, err := globalSubState.CreateConfig(ctx, tx, clientID, request.Measurements)
|
||||
results, err := globalSubState.CreateConfig(c, tx, clientID, request.Measurements)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "create real time data subscription config failed", "error", err)
|
||||
logger.Error(c, "create real time data subscription config failed", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
|
||||
ClientID: clientID,
|
||||
TargetResults: results,
|
||||
|
|
@ -131,9 +130,9 @@ func RealTimeSubHandler(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
case constants.SubStopAction:
|
||||
results, err := globalSubState.RemoveTargets(ctx, clientID, request.Measurements)
|
||||
results, err := globalSubState.RemoveTargets(c, clientID, request.Measurements)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "remove target to real time data subscription config failed", "error", err)
|
||||
logger.Error(c, "remove target to real time data subscription config failed", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
|
||||
ClientID: clientID,
|
||||
TargetResults: results,
|
||||
|
|
@ -147,9 +146,9 @@ func RealTimeSubHandler(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
case constants.SubAppendAction:
|
||||
results, err := globalSubState.AppendTargets(ctx, tx, clientID, request.Measurements)
|
||||
results, err := globalSubState.AppendTargets(c, tx, clientID, request.Measurements)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "append target to real time data subscription config failed", "error", err)
|
||||
logger.Error(c, "append target to real time data subscription config failed", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
|
||||
ClientID: clientID,
|
||||
TargetResults: results,
|
||||
|
|
@ -163,9 +162,9 @@ func RealTimeSubHandler(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
case constants.SubUpdateAction:
|
||||
results, err := globalSubState.UpdateTargets(ctx, tx, clientID, request.Measurements)
|
||||
results, err := globalSubState.UpdateTargets(c, tx, clientID, request.Measurements)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "update target to real time data subscription config failed", "error", err)
|
||||
logger.Error(c, "update target to real time data subscription config failed", "error", err)
|
||||
renderRespFailure(c, constants.RespCodeFailed, err.Error(), network.RealTimeSubPayload{
|
||||
ClientID: clientID,
|
||||
TargetResults: results,
|
||||
|
|
@ -180,7 +179,7 @@ func RealTimeSubHandler(c *gin.Context) {
|
|||
return
|
||||
default:
|
||||
err := fmt.Errorf("%w: request action is %s", common.ErrUnsupportedSubAction, request.Action)
|
||||
logger.Error(ctx, "unsupported action of real time data subscription request", "error", err)
|
||||
logger.Error(c, "unsupported action of real time data subscription request", "error", err)
|
||||
requestTargetsCount := processRealTimeRequestCount(request.Measurements)
|
||||
results := processRealTimeRequestTargets(request.Measurements, requestTargetsCount, constants.CodeUnsupportSubOperation, err)
|
||||
renderRespFailure(c, constants.RespCodeInvalidParams, err.Error(), network.RealTimeSubPayload{
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ type facade struct {
|
|||
_logger *zap.Logger
|
||||
}
|
||||
|
||||
const facadeCallerSkip = 2
|
||||
|
||||
// Debug define facade func of debug level log
|
||||
func Debug(ctx context.Context, msg string, kv ...any) {
|
||||
logFacade().log(ctx, zapcore.DebugLevel, msg, kv...)
|
||||
|
|
@ -41,16 +39,12 @@ func Error(ctx context.Context, msg string, kv ...any) {
|
|||
}
|
||||
|
||||
func (f *facade) log(ctx context.Context, lvl zapcore.Level, msg string, kv ...any) {
|
||||
f.logSkip(ctx, lvl, 1, msg, kv...)
|
||||
f.logSkip(ctx, lvl, 0, msg, kv...)
|
||||
}
|
||||
|
||||
func (f *facade) logSkip(ctx context.Context, lvl zapcore.Level, extraSkip int, msg string, kv ...any) {
|
||||
fields := makeLogFieldsSkip(ctx, extraSkip, kv...)
|
||||
logger := f._logger
|
||||
if extraSkip > 0 {
|
||||
logger = logger.WithOptions(zap.AddCallerSkip(extraSkip))
|
||||
}
|
||||
ce := logger.Check(lvl, msg)
|
||||
ce := f._logger.Check(lvl, msg)
|
||||
ce.Write(fields...)
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +66,7 @@ func InfoSkip(ctx context.Context, extraSkip int, msg string, kv ...any) {
|
|||
func logFacade() *facade {
|
||||
fOnce.Do(func() {
|
||||
f = &facade{
|
||||
_logger: GetLoggerInstance().WithOptions(zap.AddCallerSkip(facadeCallerSkip)),
|
||||
_logger: GetLoggerInstance(),
|
||||
}
|
||||
})
|
||||
return f
|
||||
|
|
|
|||
16
main.go
16
main.go
|
|
@ -235,18 +235,6 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
componentColumnNames, err := database.QueryComponentColumnNames(ctx, tx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "query component table column names failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = model.StoreComponentColumnRecommend(ctx, fullParentPath, isLocalParentPath, componentColumnNames)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "store component column recommend content failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
allMeasurement, err := database.GetAllMeasurements(ctx, tx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "load topologic info from postgres failed", "error", err)
|
||||
|
|
@ -254,12 +242,12 @@ func main() {
|
|||
}
|
||||
go realtimedata.StartComputingRealTimeDataLimit(ctx, allMeasurement)
|
||||
|
||||
tree, _, err := database.QueryTopologicFromDB(ctx, tx)
|
||||
topologics, err := database.QueryTopologic(ctx, tx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "load topologic info from postgres failed", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
diagram.GlobalTree = tree
|
||||
diagram.SetGlobalTopologyGraph(diagram.NewTopologyGraph(topologics))
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"modelRT/orm"
|
||||
|
||||
"github.com/RediSearch/redisearch-go/v2/redisearch"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
|
@ -21,13 +20,6 @@ type columnParam struct {
|
|||
AttributeGroup map[string]any
|
||||
}
|
||||
|
||||
type attributeGroupRecommendJob struct {
|
||||
AttributeSet orm.AttributeSet
|
||||
FullPath string
|
||||
IsLocalFullPath string
|
||||
ColumnParam columnParam
|
||||
}
|
||||
|
||||
// TraverseAttributeGroupTables define func to traverse component attribute group tables
|
||||
func TraverseAttributeGroupTables(ctx context.Context, db *gorm.DB, compTagToFullPath map[string]string, isLocalCompTagToFullPath map[string]string, compAttrSet map[string]orm.AttributeSet) error {
|
||||
var tableNames []string
|
||||
|
|
@ -46,7 +38,6 @@ func TraverseAttributeGroupTables(ctx context.Context, db *gorm.DB, compTagToFul
|
|||
return nil
|
||||
}
|
||||
|
||||
jobs := make([]attributeGroupRecommendJob, 0)
|
||||
for _, tableName := range tableNames {
|
||||
var records []map[string]any
|
||||
err := db.Table(tableName).Find(&records).Error
|
||||
|
|
@ -111,27 +102,13 @@ func TraverseAttributeGroupTables(ctx context.Context, db *gorm.DB, compTagToFul
|
|||
AttributeType: attributeType,
|
||||
AttributeGroup: attributeGroup,
|
||||
}
|
||||
jobs = append(jobs, attributeGroupRecommendJob{
|
||||
AttributeSet: attrSet,
|
||||
FullPath: fullPath,
|
||||
IsLocalFullPath: isLocalfullPath,
|
||||
ColumnParam: columnParam,
|
||||
})
|
||||
go storeAttributeGroup(ctx, attrSet, fullPath, isLocalfullPath, columnParam)
|
||||
}
|
||||
}
|
||||
|
||||
group, groupCtx := errgroup.WithContext(ctx)
|
||||
group.SetLimit(16)
|
||||
for _, job := range jobs {
|
||||
job := job
|
||||
group.Go(func() error {
|
||||
return storeAttributeGroup(groupCtx, job.AttributeSet, job.FullPath, job.IsLocalFullPath, job.ColumnParam)
|
||||
})
|
||||
}
|
||||
return group.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeAttributeGroup(ctx context.Context, attributeSet orm.AttributeSet, fullPath string, isLocalFullPath string, colParams columnParam) error {
|
||||
func storeAttributeGroup(ctx context.Context, attributeSet orm.AttributeSet, fullPath string, isLocalFullPath string, colParams columnParam) {
|
||||
rdb := diagram.GetRedisClientInstance()
|
||||
pipe := rdb.Pipeline()
|
||||
|
||||
|
|
@ -144,25 +121,18 @@ func storeAttributeGroup(ctx context.Context, attributeSet orm.AttributeSet, ful
|
|||
attrbutesGroups := make([]any, 0, len(colParams.AttributeGroup)*2)
|
||||
attributeGroupKey := fmt.Sprintf("%s_%s", attributeSet.CompTag, colParams.AttributeType)
|
||||
|
||||
sug := make([]redisearch.Suggestion, 0, len(colParams.AttributeGroup)*2+2)
|
||||
configTerm := fmt.Sprintf("%s.%s", fullPath, colParams.AttributeType)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: configTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
if isLocalFullPath != "" {
|
||||
configTerm = fmt.Sprintf("%s.%s", isLocalFullPath, colParams.AttributeType)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: configTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
}
|
||||
|
||||
sug := make([]redisearch.Suggestion, 0, len(colParams.AttributeGroup)*4)
|
||||
for attrName, attrValue := range colParams.AttributeGroup {
|
||||
attrbutesGroups = append(attrbutesGroups, attrName, attrValue)
|
||||
attrNameMembers = append(attrNameMembers, attrName)
|
||||
|
||||
// add redis fuzzy search suggestion for token1-token7 type
|
||||
configTerm := fmt.Sprintf("%s.%s", fullPath, colParams.AttributeType)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: configTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
|
||||
measTerm := fmt.Sprintf("%s.%s.%s", fullPath, colParams.AttributeType, attrName)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: measTerm,
|
||||
|
|
@ -170,9 +140,11 @@ func storeAttributeGroup(ctx context.Context, attributeSet orm.AttributeSet, ful
|
|||
})
|
||||
|
||||
// add redis fuzzy search suggestion for token4-token7 type
|
||||
if isLocalFullPath == "" {
|
||||
continue
|
||||
}
|
||||
configTerm = fmt.Sprintf("%s.%s", isLocalFullPath, colParams.AttributeType)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: configTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
|
||||
measTerm = fmt.Sprintf("%s.%s.%s", isLocalFullPath, colParams.AttributeType, attrName)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
|
|
@ -191,24 +163,11 @@ func storeAttributeGroup(ctx context.Context, attributeSet orm.AttributeSet, ful
|
|||
}
|
||||
|
||||
if len(sug) > 0 {
|
||||
if err := ac.AddTerms(sug...); err != nil {
|
||||
logger.Error(ctx, "add attribute group recommend suggestions failed",
|
||||
"component_tag", attributeSet.CompTag,
|
||||
"attribute_type", colParams.AttributeType,
|
||||
"error", err,
|
||||
)
|
||||
return fmt.Errorf("add attribute group recommend suggestions: %w", err)
|
||||
}
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
_, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "init component attribute group recommend content failed",
|
||||
"component_tag", attributeSet.CompTag,
|
||||
"attribute_type", colParams.AttributeType,
|
||||
"error", err,
|
||||
)
|
||||
return fmt.Errorf("init component attribute group recommend content: %w", err)
|
||||
logger.Error(ctx, "init component attribute group recommend content failed", "error", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,6 @@ func (s *ShortAttrInfo) IsLocal() bool {
|
|||
}
|
||||
|
||||
// GetAttrValue define return the attribute value
|
||||
func (s *ShortAttrInfo) GetAttrValue() any {
|
||||
return s.AttrValue
|
||||
func (l *ShortAttrInfo) GetAttrValue() any {
|
||||
return l.AttrValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
// Package model define model struct of model runtime service
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"modelRT/constants"
|
||||
"modelRT/diagram"
|
||||
|
||||
"github.com/RediSearch/redisearch-go/v2/redisearch"
|
||||
)
|
||||
|
||||
// StoreComponentColumnRecommend binds token6 component config to component table column names.
|
||||
func StoreComponentColumnRecommend(ctx context.Context, compTagToFullPath map[string]string, isLocalCompTagToFullPath map[string]string, componentColumnNames []string) error {
|
||||
rdb := diagram.GetRedisClientInstance()
|
||||
pipe := rdb.Pipeline()
|
||||
|
||||
pipe.SAdd(ctx, constants.RedisAllConfigSetKey, constants.ComponentConfigKey)
|
||||
|
||||
if len(componentColumnNames) == 0 {
|
||||
_, err := pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
componentColumnMembers := stringSliceToAny(componentColumnNames)
|
||||
pipe.SAdd(ctx, constants.RedisAllMeasTagSetKey, componentColumnMembers...)
|
||||
|
||||
sug := make([]redisearch.Suggestion, 0, len(compTagToFullPath)*len(componentColumnNames)*4)
|
||||
for compTag, fullPath := range compTagToFullPath {
|
||||
specCompMeasKey := fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, compTag)
|
||||
pipe.SAdd(ctx, specCompMeasKey, componentColumnMembers...)
|
||||
pipe.HSet(ctx, componentColumnGroupKey(compTag), componentColumnHashFields(componentColumnNames)...)
|
||||
|
||||
isLocalFullPath := isLocalCompTagToFullPath[compTag]
|
||||
fullConfigTerm := fmt.Sprintf("%s.%s", fullPath, constants.ComponentConfigKey)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: fullConfigTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
|
||||
if isLocalFullPath != "" {
|
||||
localConfigTerm := fmt.Sprintf("%s.%s", isLocalFullPath, constants.ComponentConfigKey)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: localConfigTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
}
|
||||
|
||||
for _, columnName := range componentColumnNames {
|
||||
fullColumnTerm := fmt.Sprintf("%s.%s.%s", fullPath, constants.ComponentConfigKey, columnName)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: fullColumnTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
|
||||
if isLocalFullPath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
localColumnTerm := fmt.Sprintf("%s.%s.%s", isLocalFullPath, constants.ComponentConfigKey, columnName)
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: localColumnTerm,
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(sug) > 0 {
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
_, err := pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func stringSliceToAny(values []string) []any {
|
||||
members := make([]any, 0, len(values))
|
||||
for _, value := range values {
|
||||
members = append(members, value)
|
||||
}
|
||||
return members
|
||||
}
|
||||
|
||||
func componentColumnGroupKey(compTag string) string {
|
||||
return recommendGroupKey(compTag, constants.ComponentConfigKey)
|
||||
}
|
||||
|
||||
func componentColumnHashFields(columnNames []string) []any {
|
||||
return recommendHashFields(columnNames)
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComponentColumnGroupKey(t *testing.T) {
|
||||
got := componentColumnGroupKey("cable_26-demoProject110kV_TV")
|
||||
want := "cable_26-demoProject110kV_TV_component"
|
||||
if got != want {
|
||||
t.Fatalf("expected key %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponentColumnHashFields(t *testing.T) {
|
||||
got := componentColumnHashFields([]string{"global_uuid", "nspath"})
|
||||
want := []any{"global_uuid", true, "nspath", true}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("expected fields %#v, got %#v", want, got)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
|
||||
compTagToFullPath := make(map[string]string)
|
||||
isLocalCompTagToFullPath := make(map[string]string)
|
||||
var allErrs []error
|
||||
|
||||
zoneToGridPath := make(map[string]string)
|
||||
for gridTag, zoneTags := range measSet.GridToZoneTags {
|
||||
|
|
@ -63,21 +62,12 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
pipe.SAdd(ctx, key, members)
|
||||
}
|
||||
}
|
||||
safeAddTerms := func(sug []redisearch.Suggestion) {
|
||||
if len(sug) == 0 {
|
||||
return
|
||||
}
|
||||
if err := ac.AddTerms(sug...); err != nil {
|
||||
logger.Error(ctx, "add measurement group recommend suggestions failed", "error", err)
|
||||
allErrs = append(allErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
safeSAdd(constants.RedisAllGridSetKey, measSet.AllGridTags)
|
||||
gridSug := util.MapSlice(measSet.AllGridTags, func(gridTag string) redisearch.Suggestion {
|
||||
return redisearch.Suggestion{Term: gridTag, Score: constants.DefaultScore}
|
||||
})
|
||||
safeAddTerms(gridSug)
|
||||
ac.AddTerms(gridSug...)
|
||||
|
||||
safeSAdd(constants.RedisAllZoneSetKey, measSet.AllZoneTags)
|
||||
safeSAdd(constants.RedisAllStationSetKey, measSet.AllStationTags)
|
||||
|
|
@ -93,7 +83,7 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
return redisearch.Suggestion{Term: fmt.Sprintf("%s.%s", gridTag, zoneTag), Score: constants.DefaultScore}
|
||||
})
|
||||
safeSAdd(fmt.Sprintf(constants.RedisSpecGridZoneSetKey, gridTag), zoneTags)
|
||||
safeAddTerms(sug)
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
// building the zone -> stations hierarchy
|
||||
|
|
@ -111,7 +101,7 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
})
|
||||
|
||||
safeSAdd(fmt.Sprintf(constants.RedisSpecZoneStationSetKey, zoneTag), stationTags)
|
||||
safeAddTerms(sug)
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
// building the station -> component nspaths hierarchy
|
||||
|
|
@ -132,7 +122,7 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
sug = append(sug, redisearch.Suggestion{Term: nsPath, Score: constants.DefaultScore})
|
||||
}
|
||||
safeSAdd(fmt.Sprintf(constants.RedisSpecStationCompNSPATHSetKey, stationTag), compNSPaths)
|
||||
safeAddTerms(sug)
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
// building the component nspath -> component tags hierarchy
|
||||
|
|
@ -146,17 +136,20 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
}
|
||||
|
||||
for _, compTag := range compTags {
|
||||
fullTerm, localTerm := compTagSuggestionTerms(parentPath, compNSPath, compTag)
|
||||
compTagToFullPath[compTag] = fullTerm
|
||||
isLocalCompTagToFullPath[compTag] = localTerm
|
||||
fullPath := fmt.Sprintf("%s.%s.%s", parentPath, compNSPath, compTag)
|
||||
compTagToFullPath[compTag] = fullPath
|
||||
fullPath = fmt.Sprintf("%s.%s", compNSPath, compTag)
|
||||
isLocalCompTagToFullPath[compTag] = fullPath
|
||||
|
||||
// add redis fuzzy search suggestion for token1-token7 type
|
||||
sug = append(sug, redisearch.Suggestion{Term: fullTerm, Score: constants.DefaultScore})
|
||||
term := fullPath
|
||||
sug = append(sug, redisearch.Suggestion{Term: term, Score: constants.DefaultScore})
|
||||
// add redis fuzzy search suggestion for token4-token7 type
|
||||
sug = append(sug, redisearch.Suggestion{Term: localTerm, Score: constants.DefaultScore})
|
||||
term = fmt.Sprintf("%s.%s", compNSPath, compTag)
|
||||
sug = append(sug, redisearch.Suggestion{Term: term, Score: constants.DefaultScore})
|
||||
}
|
||||
safeSAdd(fmt.Sprintf(constants.RedisSpecCompNSPathCompTagSetKey, compNSPath), compTags)
|
||||
safeAddTerms(sug)
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
// building the component tag -> measurement tags hierarchy
|
||||
|
|
@ -191,25 +184,10 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
sug = append(sug, redisearch.Suggestion{Term: measTerm, Score: constants.DefaultScore})
|
||||
}
|
||||
safeSAdd(fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, compTag), measTags)
|
||||
if len(measTags) > 0 {
|
||||
pipe.HSet(ctx, recommendGroupKey(compTag, "bay"), recommendHashFields(measTags)...)
|
||||
}
|
||||
safeAddTerms(sug)
|
||||
}
|
||||
|
||||
// building the component nspath -> measurement tags hierarchy for token4-token7 shorthand
|
||||
for compNSPath, measTags := range measSet.CompNSPathToMeasTags {
|
||||
sug := make([]redisearch.Suggestion, 0, len(measTags))
|
||||
for _, measTag := range measTags {
|
||||
sug = append(sug, redisearch.Suggestion{
|
||||
Term: fmt.Sprintf("%s.%s", compNSPath, measTag),
|
||||
Score: constants.DefaultScore,
|
||||
})
|
||||
}
|
||||
safeSAdd(fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, compNSPath), measTags)
|
||||
safeAddTerms(sug)
|
||||
ac.AddTerms(sug...)
|
||||
}
|
||||
|
||||
var allErrs []error
|
||||
cmders, execErr := pipe.Exec(ctx)
|
||||
if execErr != nil {
|
||||
logger.Error(ctx, "pipeline execution failed", "error", execErr)
|
||||
|
|
@ -233,7 +211,3 @@ func TraverseMeasurementGroupTables(ctx context.Context, measSet orm.Measurement
|
|||
|
||||
return compTagToFullPath, isLocalCompTagToFullPath, nil
|
||||
}
|
||||
|
||||
func compTagSuggestionTerms(parentPath string, compNSPath string, compTag string) (string, string) {
|
||||
return fmt.Sprintf("%s.%s.%s", parentPath, compNSPath, compTag), fmt.Sprintf("%s.%s", compNSPath, compTag)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package model
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCompTagSuggestionTermsKeepFullAndLocalPaths(t *testing.T) {
|
||||
parentPath := "grid000.zone000.station000"
|
||||
compNSPath := "110kV_TV-demoProject"
|
||||
compTag := "cable_22-testProject1110kV_TV"
|
||||
|
||||
fullTerm, localTerm := compTagSuggestionTerms(parentPath, compNSPath, compTag)
|
||||
|
||||
wantFullTerm := "grid000.zone000.station000.110kV_TV-demoProject.cable_22-testProject1110kV_TV"
|
||||
if fullTerm != wantFullTerm {
|
||||
t.Fatalf("expected full suggestion term %q, got %q", wantFullTerm, fullTerm)
|
||||
}
|
||||
|
||||
wantLocalTerm := "110kV_TV-demoProject.cable_22-testProject1110kV_TV"
|
||||
if localTerm != wantLocalTerm {
|
||||
t.Fatalf("expected local suggestion term %q, got %q", wantLocalTerm, localTerm)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
// Package model define model struct of model runtime service
|
||||
package model
|
||||
|
||||
import (
|
||||
|
|
@ -8,12 +7,11 @@ import (
|
|||
|
||||
// SelectModelByType define select the data structure for parsing based on the input model type
|
||||
func SelectModelByType(modelType int) BasicModelInterface {
|
||||
switch modelType {
|
||||
case constants.BusbarType:
|
||||
if modelType == constants.BusbarType {
|
||||
return &orm.BusbarSection{}
|
||||
case constants.AsyncMotorType:
|
||||
} else if modelType == constants.AsyncMotorType {
|
||||
return &orm.AsyncMotor{}
|
||||
case constants.DemoType:
|
||||
} else if modelType == constants.DemoType {
|
||||
return &orm.Demo{}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ func CleanupRecommendRedisCache(ctx context.Context) error {
|
|||
"*_component_nspath_keys", // correspond RedisSpecStationCompNSPATHSetKey
|
||||
"*_component_tag_keys", // correspond RedisSpecCompNSPathCompTagSetKey
|
||||
"*_measurement_tag_keys", // correspond RedisSpecCompTagMeasSetKey
|
||||
"*_nspath_measurement_tag_keys", // correspond RedisSpecCompNSPathMeasSetKey
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
func recommendGroupKey(compTag string, configToken string) string {
|
||||
return fmt.Sprintf("%s_%s", compTag, configToken)
|
||||
}
|
||||
|
||||
func recommendHashFields(values []string) []any {
|
||||
fields := make([]any, 0, len(values)*2)
|
||||
for _, value := range values {
|
||||
fields = append(fields, value, true)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,854 +0,0 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"modelRT/constants"
|
||||
)
|
||||
|
||||
func TestFuzzyRecommendOffsetUsesFullMatchedInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
searchPrefix string
|
||||
searchInput string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "zone fuzzy prefix",
|
||||
searchPrefix: "grid000",
|
||||
searchInput: "z",
|
||||
want: len([]rune("grid000.z")),
|
||||
},
|
||||
{
|
||||
name: "level one fuzzy",
|
||||
searchPrefix: "",
|
||||
searchInput: "g",
|
||||
want: len([]rune("g")),
|
||||
},
|
||||
{
|
||||
name: "measurement fuzzy preserves config token",
|
||||
searchPrefix: "grid.zone.station.nspath.comp.config",
|
||||
searchInput: "m",
|
||||
want: len([]rune("grid.zone.station.nspath.comp.config.m")),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := fuzzyRecommendOffset(tt.searchPrefix, tt.searchInput)
|
||||
if got != tt.want {
|
||||
t.Fatalf("expected offset %d, got %d", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuzzyRecommendMemberSetKeyUsesLocalNSPathSet(t *testing.T) {
|
||||
setKey, ok := fuzzyRecommendMemberSetKey(constants.CompNSPathRecommendHierarchyType, constants.IsLocalRecommendLength, "")
|
||||
if !ok {
|
||||
t.Fatalf("expected local nspath fuzzy member check to use a redis set")
|
||||
}
|
||||
if setKey != constants.RedisAllCompNSPathSetKey {
|
||||
t.Fatalf("expected set key %q, got %q", constants.RedisAllCompNSPathSetKey, setKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsFuzzyExactContinuation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
recommendType constants.RecommendHierarchyType
|
||||
offset int
|
||||
}{
|
||||
{
|
||||
name: "local nspath typo completes current level",
|
||||
input: "110kV_TV-demoProjectx",
|
||||
recommendType: constants.CompNSPathRecommendHierarchyType,
|
||||
offset: len([]rune("110kV_TV-demoProject")),
|
||||
},
|
||||
{
|
||||
name: "grid typo completes current level",
|
||||
input: "grid000x",
|
||||
recommendType: constants.GridRecommendHierarchyType,
|
||||
offset: len([]rune("grid000")),
|
||||
},
|
||||
{
|
||||
name: "zone typo completes current level",
|
||||
input: "grid000.zone000x",
|
||||
recommendType: constants.ZoneRecommendHierarchyType,
|
||||
offset: len([]rune("grid000.zone000")),
|
||||
},
|
||||
{
|
||||
name: "station typo completes current level",
|
||||
input: "grid000.zone000.station000x",
|
||||
recommendType: constants.StationRecommendHierarchyType,
|
||||
offset: len([]rune("grid000.zone000.station000")),
|
||||
},
|
||||
{
|
||||
name: "full nspath typo completes current level",
|
||||
input: "grid000.zone000.station000.110kV_TV-demoProjectx",
|
||||
recommendType: constants.CompNSPathRecommendHierarchyType,
|
||||
offset: len([]rune("grid000.zone000.station000.110kV_TV-demoProject")),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := map[string]SearchResult{
|
||||
tt.recommendType.String(): {
|
||||
RecommendType: tt.recommendType,
|
||||
QueryDatas: []string{"."},
|
||||
IsFuzzy: true,
|
||||
Offset: tt.offset,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(tt.input, results)
|
||||
result := got[tt.recommendType.String()]
|
||||
if result.Offset != tt.offset {
|
||||
t.Fatalf("expected offset %d, got %d", tt.offset, result.Offset)
|
||||
}
|
||||
if !reflect.DeepEqual(result.QueryDatas, []string{"."}) {
|
||||
t.Fatalf("expected fuzzy exact continuation '.', got %#v", result.QueryDatas)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsUsesInputLengthForExactCompletion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
}{
|
||||
{
|
||||
name: "full token1 to token7 structure",
|
||||
input: "grid000.zone000.station000.220kV_学府路1-testProject1.compTag.config.IA_rms_CTA-testProject1",
|
||||
},
|
||||
{
|
||||
name: "local token4 to token7 structure",
|
||||
input: "220kV_学府路1-testProject1.IA_rms_CTA-testProject1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: []string{""},
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(tt.input, results)
|
||||
result := got[constants.MeasTagRecommendHierarchyType.String()]
|
||||
wantOffset := len([]rune(tt.input))
|
||||
if result.Offset != wantOffset {
|
||||
t.Fatalf("expected offset %d, got %d", wantOffset, result.Offset)
|
||||
}
|
||||
if len(result.QueryDatas) != 0 {
|
||||
t.Fatalf("expected exact completion to return no suffix, got %v", result.QueryDatas)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsUsesInputLengthForLevelContinuation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
recommendType constants.RecommendHierarchyType
|
||||
}{
|
||||
{
|
||||
name: "token1 grid can continue",
|
||||
input: "grid000",
|
||||
recommendType: constants.GridRecommendHierarchyType,
|
||||
},
|
||||
{
|
||||
name: "token1 token2 zone can continue",
|
||||
input: "grid000.zone000",
|
||||
recommendType: constants.ZoneRecommendHierarchyType,
|
||||
},
|
||||
{
|
||||
name: "token1 token2 token3 station can continue",
|
||||
input: "grid000.zone000.station000",
|
||||
recommendType: constants.StationRecommendHierarchyType,
|
||||
},
|
||||
{
|
||||
name: "token1 token2 token3 token4 nspath can continue",
|
||||
input: "grid000.zone000.station000.110kV_TV-demoProject",
|
||||
recommendType: constants.CompNSPathRecommendHierarchyType,
|
||||
},
|
||||
{
|
||||
name: "token1 token2 token3 token4 token5 compTag can continue",
|
||||
input: "grid000.zone000.station000.110kV_TV-demoProject.cable_26-demoProject110kV_TV",
|
||||
recommendType: constants.CompTagRecommendHierarchyType,
|
||||
},
|
||||
{
|
||||
name: "token1 through token6 config can continue",
|
||||
input: "grid000.zone000.station000.110kV_TV-demoProject.cable_26-demoProject110kV_TV.base_extend",
|
||||
recommendType: constants.ConfigRecommendHierarchyType,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
results := map[string]SearchResult{
|
||||
tt.recommendType.String(): {
|
||||
RecommendType: tt.recommendType,
|
||||
QueryDatas: []string{"."},
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(tt.input, results)
|
||||
result := got[tt.recommendType.String()]
|
||||
wantOffset := len([]rune(tt.input))
|
||||
if result.Offset != wantOffset {
|
||||
t.Fatalf("expected offset %d, got %d", wantOffset, result.Offset)
|
||||
}
|
||||
if len(result.QueryDatas) != 1 || result.QueryDatas[0] != "." {
|
||||
t.Fatalf("expected level continuation '.', got %v", result.QueryDatas)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsFallbackOffsetZero(t *testing.T) {
|
||||
input := "x"
|
||||
results := map[string]SearchResult{
|
||||
constants.GridRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.GridRecommendHierarchyType,
|
||||
QueryDatas: []string{"grid000", "grid001"},
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: 0,
|
||||
},
|
||||
constants.CompNSPathRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.CompNSPathRecommendHierarchyType,
|
||||
QueryDatas: []string{"nspath000", "nspath001"},
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: 0,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(input, results)
|
||||
for key, result := range got {
|
||||
if result.Offset != 0 {
|
||||
t.Fatalf("expected fallback offset 0 for %s, got %d", key, result.Offset)
|
||||
}
|
||||
if len(result.QueryDatas) != 2 {
|
||||
t.Fatalf("expected fallback recommends to remain intact for %s, got %v", key, result.QueryDatas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsParentPrefixOffsetForSpecificFallback(t *testing.T) {
|
||||
nsPath := "220kV_学府路1-testProject1"
|
||||
input := nsPath + ".x"
|
||||
offset := recommendPrefixOffset([]string{nsPath})
|
||||
results := map[string]SearchResult{
|
||||
constants.CompTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.CompTagRecommendHierarchyType,
|
||||
QueryDatas: []string{
|
||||
nsPath + ".CTA-testProject1220kV_学府路1",
|
||||
nsPath + ".CB-testProject1220kV_学府路1",
|
||||
},
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: offset,
|
||||
},
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: []string{
|
||||
nsPath + ".IA_rms_CTA-testProject1",
|
||||
nsPath + ".IB_rms_CTA-testProject1",
|
||||
},
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: offset,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(input, results)
|
||||
if offset != 24 {
|
||||
t.Fatalf("expected sample parent prefix offset 24, got %d", offset)
|
||||
}
|
||||
for key, result := range got {
|
||||
if result.Offset != 24 {
|
||||
t.Fatalf("expected specific fallback offset 24 for %s, got %d", key, result.Offset)
|
||||
}
|
||||
for _, recommend := range result.QueryDatas {
|
||||
if strings.HasPrefix(recommend, nsPath+".") {
|
||||
t.Fatalf("expected trimmed fallback recommend for %s, got %s", key, recommend)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsTrimsConfigFuzzySuffixes(t *testing.T) {
|
||||
input := "110kV_TV-testProject1.cable_22-testProject1110kV_TV.base_extend.c"
|
||||
offset := len([]rune(input))
|
||||
results := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: []string{
|
||||
input + "apacity",
|
||||
input + "ategory",
|
||||
input + "urrent",
|
||||
input + "ode",
|
||||
},
|
||||
IsFuzzy: true,
|
||||
Offset: offset,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(input, results)
|
||||
result := got[constants.MeasTagRecommendHierarchyType.String()]
|
||||
want := []string{"apacity", "ategory", "urrent", "ode"}
|
||||
if result.Offset != offset {
|
||||
t.Fatalf("expected offset %d, got %d", offset, result.Offset)
|
||||
}
|
||||
if !reflect.DeepEqual(result.QueryDatas, want) {
|
||||
t.Fatalf("expected trimmed recommends %#v, got %#v", want, result.QueryDatas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsTrimsToken4FallbackCandidates(t *testing.T) {
|
||||
nsPath := "110kV_TV-testProject1"
|
||||
input := nsPath + ".x"
|
||||
offset := recommendPrefixOffset([]string{nsPath})
|
||||
results := map[string]SearchResult{
|
||||
constants.CompTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.CompTagRecommendHierarchyType,
|
||||
QueryDatas: []string{
|
||||
nsPath + ".cable_22-testProject1110kV_TV",
|
||||
nsPath + ".cable_23-testProject1110kV_TV",
|
||||
},
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: offset,
|
||||
},
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: []string{
|
||||
nsPath + ".IA_rms_CTA-testProject1",
|
||||
nsPath + ".IB_rms_CTA-testProject1",
|
||||
},
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: offset,
|
||||
},
|
||||
}
|
||||
|
||||
got := normalizeRecommendResults(input, results)
|
||||
for key, result := range got {
|
||||
if result.Offset != offset {
|
||||
t.Fatalf("expected offset %d for %s, got %d", offset, key, result.Offset)
|
||||
}
|
||||
for _, recommend := range result.QueryDatas {
|
||||
if strings.HasPrefix(recommend, nsPath+".") {
|
||||
t.Fatalf("expected trimmed token4 fallback recommend for %s, got %s", key, recommend)
|
||||
}
|
||||
}
|
||||
}
|
||||
if got[constants.CompTagRecommendHierarchyType.String()].QueryDatas[0] != "cable_22-testProject1110kV_TV" {
|
||||
t.Fatalf("expected token5 suffixes, got %v", got[constants.CompTagRecommendHierarchyType.String()].QueryDatas)
|
||||
}
|
||||
if got[constants.MeasTagRecommendHierarchyType.String()].QueryDatas[0] != "IA_rms_CTA-testProject1" {
|
||||
t.Fatalf("expected token7 suffixes, got %v", got[constants.MeasTagRecommendHierarchyType.String()].QueryDatas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsFullAndLocalConfigMeasurementSuffixesConsistent(t *testing.T) {
|
||||
fullPrefix := "grid000.zone000.station000.110kV_TV-demoProject.cable_22-testProject1110kV_TV.base_extend"
|
||||
localPrefix := "110kV_TV-demoProject.cable_22-testProject1110kV_TV.base_extend"
|
||||
members := []string{"capacity", "category", "current"}
|
||||
|
||||
fullResults := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: combineQueryResultByInput(constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, strings.Split(fullPrefix+".", "."), members),
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
localResults := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: combineQueryResultByInput(constants.MeasTagRecommendHierarchyType, constants.IsLocalRecommendLength, strings.Split(localPrefix+".", "."), members),
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
|
||||
fullGot := normalizeRecommendResults(fullPrefix+".", fullResults)
|
||||
localGot := normalizeRecommendResults(localPrefix+".", localResults)
|
||||
fullRecommends := fullGot[constants.MeasTagRecommendHierarchyType.String()].QueryDatas
|
||||
localRecommends := localGot[constants.MeasTagRecommendHierarchyType.String()].QueryDatas
|
||||
if !reflect.DeepEqual(fullRecommends, localRecommends) {
|
||||
t.Fatalf("expected full/local config measurement suffixes to match, full=%#v local=%#v", fullRecommends, localRecommends)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsFullAndLocalBayMeasurementSuffixesConsistent(t *testing.T) {
|
||||
fullPrefix := "grid000.zone000.station000.110kV_TV-demoProject.cable_22-testProject1110kV_TV.bay"
|
||||
localPrefix := "110kV_TV-demoProject.cable_22-testProject1110kV_TV.bay"
|
||||
members := []string{"IA_rms", "IB_rms", "IC_rms"}
|
||||
|
||||
fullResults := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: combineQueryResultByInput(constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, strings.Split(fullPrefix+".", "."), members),
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
localResults := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: combineQueryResultByInput(constants.MeasTagRecommendHierarchyType, constants.IsLocalRecommendLength, strings.Split(localPrefix+".", "."), members),
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
|
||||
fullGot := normalizeRecommendResults(fullPrefix+".", fullResults)
|
||||
localGot := normalizeRecommendResults(localPrefix+".", localResults)
|
||||
fullRecommends := fullGot[constants.MeasTagRecommendHierarchyType.String()].QueryDatas
|
||||
localRecommends := localGot[constants.MeasTagRecommendHierarchyType.String()].QueryDatas
|
||||
if !reflect.DeepEqual(fullRecommends, localRecommends) {
|
||||
t.Fatalf("expected full/local bay measurement suffixes to match, full=%#v local=%#v", fullRecommends, localRecommends)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsConfigMismatchFallbackConsistentWithEmptyInput(t *testing.T) {
|
||||
prefix := "110kV_TV-demoProject.cable_22-testProject1110kV_TV"
|
||||
members := []string{"bay", "base_extend", "model", "component"}
|
||||
groupResults := combineQueryResultByInput(constants.ConfigRecommendHierarchyType, constants.IsLocalRecommendLength, strings.Split(prefix+".", "."), members)
|
||||
offset := recommendPrefixOffset(strings.Split(prefix, "."))
|
||||
|
||||
emptyInputResults := map[string]SearchResult{
|
||||
constants.ConfigRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.ConfigRecommendHierarchyType,
|
||||
QueryDatas: append([]string{}, groupResults...),
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
mismatchFallbackResults := map[string]SearchResult{
|
||||
constants.ConfigRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.ConfigRecommendHierarchyType,
|
||||
QueryDatas: append([]string{}, groupResults...),
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: offset,
|
||||
},
|
||||
}
|
||||
|
||||
emptyGot := normalizeRecommendResults(prefix+".", emptyInputResults)
|
||||
mismatchGot := normalizeRecommendResults(prefix+".x", mismatchFallbackResults)
|
||||
emptyResult := emptyGot[constants.ConfigRecommendHierarchyType.String()]
|
||||
mismatchResult := mismatchGot[constants.ConfigRecommendHierarchyType.String()]
|
||||
|
||||
if mismatchResult.Offset != emptyResult.Offset {
|
||||
t.Fatalf("expected config mismatch fallback offset %d, got %d", emptyResult.Offset, mismatchResult.Offset)
|
||||
}
|
||||
if !reflect.DeepEqual(mismatchResult.QueryDatas, emptyResult.QueryDatas) {
|
||||
t.Fatalf("expected config mismatch fallback recommends to match empty input, empty=%#v mismatch=%#v", emptyResult.QueryDatas, mismatchResult.QueryDatas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecommendResultsKeepsMeasurementMismatchFallbackConsistentWithEmptyInput(t *testing.T) {
|
||||
prefix := "grid000.zone000.station000.110kV_TV-demoProject.cable_22-testProject1110kV_TV.bay"
|
||||
members := []string{"IA_rms_CTA-testProject1", "IB_rms_CTA-testProject1", "IC_rms_CTA-testProject1"}
|
||||
groupResults := combineQueryResultByInput(constants.MeasTagRecommendHierarchyType, constants.FullRecommendLength, strings.Split(prefix+".", "."), members)
|
||||
offset := recommendPrefixOffset(strings.Split(prefix, "."))
|
||||
|
||||
emptyInputResults := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: append([]string{}, groupResults...),
|
||||
IsFuzzy: false,
|
||||
},
|
||||
}
|
||||
mismatchFallbackResults := map[string]SearchResult{
|
||||
constants.MeasTagRecommendHierarchyType.String(): {
|
||||
RecommendType: constants.MeasTagRecommendHierarchyType,
|
||||
QueryDatas: append([]string{}, groupResults...),
|
||||
IsFuzzy: true,
|
||||
IsFallback: true,
|
||||
Offset: offset,
|
||||
},
|
||||
}
|
||||
|
||||
emptyGot := normalizeRecommendResults(prefix+".", emptyInputResults)
|
||||
mismatchGot := normalizeRecommendResults(prefix+".x", mismatchFallbackResults)
|
||||
emptyResult := emptyGot[constants.MeasTagRecommendHierarchyType.String()]
|
||||
mismatchResult := mismatchGot[constants.MeasTagRecommendHierarchyType.String()]
|
||||
|
||||
if mismatchResult.Offset != emptyResult.Offset {
|
||||
t.Fatalf("expected measurement mismatch fallback offset %d, got %d", emptyResult.Offset, mismatchResult.Offset)
|
||||
}
|
||||
if !reflect.DeepEqual(mismatchResult.QueryDatas, emptyResult.QueryDatas) {
|
||||
t.Fatalf("expected measurement mismatch fallback recommends to match empty input, empty=%#v mismatch=%#v", emptyResult.QueryDatas, mismatchResult.QueryDatas)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecommendGroupTokens(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputSlice []string
|
||||
wantCompTag string
|
||||
wantConfig string
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "full token1 to token7 input",
|
||||
inputSlice: []string{"grid000", "zone000", "station000", "nspath", "comp_tag", "base_extend", ""},
|
||||
wantCompTag: "comp_tag",
|
||||
wantConfig: "base_extend",
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "local token4 to token7 input",
|
||||
inputSlice: []string{"nspath", "comp_tag", "component", ""},
|
||||
wantCompTag: "comp_tag",
|
||||
wantConfig: "component",
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "missing config token",
|
||||
inputSlice: []string{"nspath", ""},
|
||||
wantOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCompTag, gotConfig, gotOK := recommendGroupTokens(tt.inputSlice)
|
||||
if gotOK != tt.wantOK {
|
||||
t.Fatalf("expected ok %t, got %t", tt.wantOK, gotOK)
|
||||
}
|
||||
if gotCompTag != tt.wantCompTag || gotConfig != tt.wantConfig {
|
||||
t.Fatalf("expected compTag/config %q/%q, got %q/%q", tt.wantCompTag, tt.wantConfig, gotCompTag, gotConfig)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigRecommendCompTag(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputSlice []string
|
||||
want string
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "full token1 to token6 input",
|
||||
inputSlice: []string{"grid000", "zone000", "station000", "nspath", "comp_tag", "b"},
|
||||
want: "comp_tag",
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "local token4 to token6 input",
|
||||
inputSlice: []string{"nspath", "comp_tag", "b"},
|
||||
want: "comp_tag",
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "missing comp tag",
|
||||
inputSlice: []string{"nspath"},
|
||||
wantOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, gotOK := configRecommendCompTag(tt.inputSlice)
|
||||
if gotOK != tt.wantOK {
|
||||
t.Fatalf("expected ok %t, got %t", tt.wantOK, gotOK)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("expected compTag %q, got %q", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterMembersByTrimmedPrefix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
members []string
|
||||
searchInput string
|
||||
wantMembers []string
|
||||
wantMatchInput string
|
||||
}{
|
||||
{
|
||||
name: "config typo falls back to previous rune",
|
||||
members: []string{"bay", "base_extend", "model", "rated", "stable", "component"},
|
||||
searchInput: "bx",
|
||||
wantMembers: []string{"bay", "base_extend"},
|
||||
wantMatchInput: "b",
|
||||
},
|
||||
{
|
||||
name: "measurement typo falls back to previous rune",
|
||||
members: []string{"I_A_rms", "I_B_rms", "U_A_rms"},
|
||||
searchInput: "Ix",
|
||||
wantMembers: []string{"I_A_rms", "I_B_rms"},
|
||||
wantMatchInput: "I",
|
||||
},
|
||||
{
|
||||
name: "no fallback to empty prefix",
|
||||
members: []string{"bay", "base_extend"},
|
||||
searchInput: "x",
|
||||
wantMembers: []string{},
|
||||
wantMatchInput: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotMembers, gotMatchInput := filterMembersByTrimmedPrefix(tt.members, tt.searchInput)
|
||||
if !reflect.DeepEqual(gotMembers, tt.wantMembers) {
|
||||
t.Fatalf("expected members %#v, got %#v", tt.wantMembers, gotMembers)
|
||||
}
|
||||
if gotMatchInput != tt.wantMatchInput {
|
||||
t.Fatalf("expected matched input %q, got %q", tt.wantMatchInput, gotMatchInput)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDotAndTypoInputsShareTrimmedPrefixFallback(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dotInput string
|
||||
typoInput string
|
||||
members []string
|
||||
wantSuffix []string
|
||||
}{
|
||||
{
|
||||
name: "token1 grid",
|
||||
dotInput: "g.",
|
||||
typoInput: "gx",
|
||||
members: []string{"grid000", "grid001", "zone000"},
|
||||
wantSuffix: []string{"grid000", "grid001"},
|
||||
},
|
||||
{
|
||||
name: "token2 zone",
|
||||
dotInput: "z.",
|
||||
typoInput: "zx",
|
||||
members: []string{"zone000", "zone001", "station000"},
|
||||
wantSuffix: []string{"zone000", "zone001"},
|
||||
},
|
||||
{
|
||||
name: "token3 station",
|
||||
dotInput: "s.",
|
||||
typoInput: "sx",
|
||||
members: []string{"station000", "station001", "zone000"},
|
||||
wantSuffix: []string{"station000", "station001"},
|
||||
},
|
||||
{
|
||||
name: "token4 nspath",
|
||||
dotInput: "1.",
|
||||
typoInput: "1x",
|
||||
members: []string{"110kV_TV-demoProject", "110kV_TV-testProject1", "220kV_TV-demoProject"},
|
||||
wantSuffix: []string{"110kV_TV-demoProject", "110kV_TV-testProject1"},
|
||||
},
|
||||
{
|
||||
name: "token5 component tag",
|
||||
dotInput: "c.",
|
||||
typoInput: "cx",
|
||||
members: []string{"cable_22-testProject1110kV_TV", "cable_26-demoProject110kV_TV", "bay"},
|
||||
wantSuffix: []string{"cable_22-testProject1110kV_TV", "cable_26-demoProject110kV_TV"},
|
||||
},
|
||||
{
|
||||
name: "token6 config",
|
||||
dotInput: "b.",
|
||||
typoInput: "bx",
|
||||
members: []string{"bay", "base_extend", "component"},
|
||||
wantSuffix: []string{"bay", "base_extend"},
|
||||
},
|
||||
{
|
||||
name: "token7 measurement",
|
||||
dotInput: "I.",
|
||||
typoInput: "Ix",
|
||||
members: []string{"IA_rms", "IB_rms", "UA_rms"},
|
||||
wantSuffix: []string{"IA_rms", "IB_rms"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
dotSearchInput := strings.TrimSuffix(tt.dotInput, ".")
|
||||
typoSearchInput := trimLastRune(tt.typoInput)
|
||||
dotMembers, dotMatchedInput := filterMembersByTrimmedPrefix(tt.members, dotSearchInput)
|
||||
typoMembers, typoMatchedInput := filterMembersByTrimmedPrefix(tt.members, typoSearchInput)
|
||||
|
||||
if dotMatchedInput != typoMatchedInput {
|
||||
t.Fatalf("expected dot and typo matched input to be equal, dot=%q typo=%q", dotMatchedInput, typoMatchedInput)
|
||||
}
|
||||
if !reflect.DeepEqual(dotMembers, typoMembers) {
|
||||
t.Fatalf("expected dot and typo members to be equal, dot=%#v typo=%#v", dotMembers, typoMembers)
|
||||
}
|
||||
if !reflect.DeepEqual(dotMembers, tt.wantSuffix) {
|
||||
t.Fatalf("expected members %#v, got %#v", tt.wantSuffix, dotMembers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExactMatchChecksForInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputSlice []string
|
||||
want []exactMatchCheck
|
||||
}{
|
||||
{
|
||||
name: "token1 can be grid or local nspath",
|
||||
inputSlice: []string{"g"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: constants.RedisAllGridSetKey, member: "g"},
|
||||
{setKey: constants.RedisAllCompNSPathSetKey, member: "g"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token1 token2 can be zone comp tag or nspath meas",
|
||||
inputSlice: []string{"grid000", "z"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecGridZoneSetKey, "grid000"), member: "z"},
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecCompNSPathCompTagSetKey, "grid000"), member: "z"},
|
||||
{setKey: constants.RedisAllCompTagSetKey, member: "z"},
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecCompNSPathMeasSetKey, "grid000"), member: "z"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token1 token2 token3 can be station or local config",
|
||||
inputSlice: []string{"grid000", "zone000", "s"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecZoneStationSetKey, "zone000"), member: "s"},
|
||||
{setKey: constants.RedisAllConfigSetKey, member: "s"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token1 token2 token3 token4 can be nspath or local meas",
|
||||
inputSlice: []string{"grid000", "zone000", "station000", "1"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecStationCompNSPATHSetKey, "station000"), member: "1"},
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, "zone000"), member: "1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token1 through token5 component tag",
|
||||
inputSlice: []string{"grid000", "zone000", "station000", "nspath", "c"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecCompNSPathCompTagSetKey, "nspath"), member: "c"},
|
||||
{setKey: constants.RedisAllCompTagSetKey, member: "c"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token1 through token6 config",
|
||||
inputSlice: []string{"grid000", "zone000", "station000", "nspath", "comp_tag", "b"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: constants.RedisAllConfigSetKey, member: "b"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token1 through token7 measurement",
|
||||
inputSlice: []string{"grid000", "zone000", "station000", "nspath", "comp_tag", "bay", "I"},
|
||||
want: []exactMatchCheck{
|
||||
{setKey: fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, "comp_tag"), member: "I"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := exactMatchChecksForInput(tt.inputSlice)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("expected checks %#v, got %#v", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackSpecificSetKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hierarchy constants.RecommendHierarchyType
|
||||
inputSlice []string
|
||||
want string
|
||||
wantOK bool
|
||||
}{
|
||||
{
|
||||
name: "zone fallback uses grid specific set",
|
||||
hierarchy: constants.ZoneRecommendHierarchyType,
|
||||
inputSlice: []string{"grid000", "z"},
|
||||
want: fmt.Sprintf(constants.RedisSpecGridZoneSetKey, "grid000"),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "component tag fallback uses previous nspath token",
|
||||
hierarchy: constants.CompTagRecommendHierarchyType,
|
||||
inputSlice: []string{"grid000", "I"},
|
||||
want: fmt.Sprintf(constants.RedisSpecCompNSPathCompTagSetKey, "grid000"),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "measurement fallback skips config token and uses component tag",
|
||||
hierarchy: constants.MeasTagRecommendHierarchyType,
|
||||
inputSlice: []string{"nspath", "comp_tag", "config", "m"},
|
||||
want: fmt.Sprintf(constants.RedisSpecCompTagMeasSetKey, "comp_tag"),
|
||||
wantOK: true,
|
||||
},
|
||||
{
|
||||
name: "config fallback has no parent specific set",
|
||||
hierarchy: constants.ConfigRecommendHierarchyType,
|
||||
inputSlice: []string{"nspath", "comp_tag", ""},
|
||||
wantOK: false,
|
||||
},
|
||||
{
|
||||
name: "missing previous token",
|
||||
hierarchy: constants.ZoneRecommendHierarchyType,
|
||||
inputSlice: []string{"z"},
|
||||
wantOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, ok := fallbackSpecificSetKey(tt.hierarchy, tt.inputSlice)
|
||||
if ok != tt.wantOK {
|
||||
t.Fatalf("expected ok %t, got %t", tt.wantOK, ok)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Fatalf("expected key %q, got %q", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldFallbackToInitialRecommend(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{input: "", want: false},
|
||||
{input: ".", want: true},
|
||||
{input: ".x", want: true},
|
||||
{input: "..x", want: true},
|
||||
{input: "...x", want: true},
|
||||
{input: "grid000", want: false},
|
||||
{input: "grid000.", want: false},
|
||||
{input: "grid000.zone000", want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := shouldFallbackToInitialRecommend(tt.input)
|
||||
if got != tt.want {
|
||||
t.Fatalf("expected %t, got %t", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ type WSResponse struct {
|
|||
type MeasurementRecommendPayload struct {
|
||||
Input string `json:"input" example:"transformfeeder1_220."`
|
||||
Offset int `json:"offset" example:"21"`
|
||||
RecommendType string `json:"recommended_type" example:"grid_tag"`
|
||||
RecommendedList []string `json:"recommended_list" example:"[\"I_A_rms\", \"I_B_rms\",\"I_C_rms\"]"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,5 +16,4 @@ type MeasurementSet struct {
|
|||
StationToCompNSPaths map[string][]string // Key: StationTag, Value: NSPaths
|
||||
CompNSPathToCompTags map[string][]string // Key: NSPaths, Value: CompTags
|
||||
CompTagToMeasTags map[string][]string // Key: CompTag, Value: MeasTags
|
||||
CompNSPathToMeasTags map[string][]string // Key: NSPaths, Value: MeasTags
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,3 +12,18 @@ var RecursiveSQL = `WITH RECURSIVE recursive_tree as (
|
|||
JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to
|
||||
)
|
||||
SELECT * FROM recursive_tree;`
|
||||
|
||||
// RecursiveTopologicByStartSQL returns every directed edge reachable from the
|
||||
// supplied start component. It tracks the visited node path inside PostgreSQL
|
||||
// so cycles in topologic data cannot recurse forever.
|
||||
var RecursiveTopologicByStartSQL = `WITH RECURSIVE recursive_tree as (
|
||||
SELECT uuid_from, uuid_to, flag, ARRAY[uuid_from, uuid_to] AS path
|
||||
FROM "topologic"
|
||||
WHERE uuid_from = ?
|
||||
UNION ALL
|
||||
SELECT t.uuid_from, t.uuid_to, t.flag, rt.path || t.uuid_to
|
||||
FROM "topologic" t
|
||||
JOIN recursive_tree rt ON t.uuid_from = rt.uuid_to
|
||||
WHERE NOT t.uuid_to = ANY(rt.path)
|
||||
)
|
||||
SELECT uuid_from, uuid_to, flag FROM recursive_tree;`
|
||||
|
|
|
|||
|
|
@ -98,10 +98,10 @@ func NewTopologyAnalysisHandler() *TopologyAnalysisHandler {
|
|||
}
|
||||
}
|
||||
|
||||
// Execute processes a topology analysis task.
|
||||
// Execute processes a point-to-point topology reachability task.
|
||||
// Params (all sourced from the MQ message, no DB lookup needed):
|
||||
// - start_component_uuid (string, required): BFS origin
|
||||
// - end_component_uuid (string, required): reachability target
|
||||
// - start_component_uuid (string, required): directed traversal origin
|
||||
// - end_component_uuid (string, required): directed reachability target
|
||||
// - check_in_service (bool, optional, default true): skip out-of-service components
|
||||
func (h *TopologyAnalysisHandler) Execute(ctx context.Context, taskID uuid.UUID, params map[string]any, db *gorm.DB) error {
|
||||
logger.Info(ctx, "topology analysis started", "task_id", taskID)
|
||||
|
|
@ -123,7 +123,8 @@ func (h *TopologyAnalysisHandler) Execute(ctx context.Context, taskID uuid.UUID,
|
|||
logger.Warn(ctx, "update progress failed", "task_id", taskID, "progress", 20, "error", err)
|
||||
}
|
||||
|
||||
// Phase 2: query topology edges from startComponentUUID, build adjacency list
|
||||
// Phase 2: query only edges reachable from startComponentUUID, then build
|
||||
// the adjacency list used for point-to-point directed reachability.
|
||||
topoEdges, err := database.QueryTopologicByStartUUID(ctx, db, startComponentUUID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("query topology from start node: %w", err)
|
||||
|
|
@ -159,7 +160,7 @@ func (h *TopologyAnalysisHandler) Execute(ctx context.Context, taskID uuid.UUID,
|
|||
// check the start node itself before BFS
|
||||
if !inServiceMap[startComponentUUID] {
|
||||
return persistTopologyResult(ctx, db, taskID, startComponentUUID, endComponentUUID,
|
||||
checkInService, false, nil, &startComponentUUID)
|
||||
checkInService, false, nil, &startComponentUUID, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +168,8 @@ func (h *TopologyAnalysisHandler) Execute(ctx context.Context, taskID uuid.UUID,
|
|||
logger.Warn(ctx, "update progress failed", "task_id", taskID, "progress", 60, "error", err)
|
||||
}
|
||||
|
||||
// Phase 4: BFS reachability check
|
||||
// Phase 4: point-to-point BFS reachability check. Multiple parents and
|
||||
// multiple paths to a node are valid; visited only prevents cycles/rework.
|
||||
visited := make(map[uuid.UUID]struct{})
|
||||
parent := make(map[uuid.UUID]uuid.UUID) // for path reconstruction
|
||||
queue := []uuid.UUID{startComponentUUID}
|
||||
|
|
@ -214,7 +216,7 @@ func (h *TopologyAnalysisHandler) Execute(ctx context.Context, taskID uuid.UUID,
|
|||
}
|
||||
|
||||
return persistTopologyResult(ctx, db, taskID, startComponentUUID, endComponentUUID,
|
||||
checkInService, isReachable, path, blockedBy)
|
||||
checkInService, isReachable, path, blockedBy, len(visited))
|
||||
}
|
||||
|
||||
// parseTopologyAnalysisParams extracts and validates the three required fields.
|
||||
|
|
@ -270,6 +272,7 @@ func persistTopologyResult(
|
|||
ctx context.Context, db *gorm.DB, taskID uuid.UUID,
|
||||
startID, endID uuid.UUID, checkInService, isReachable bool,
|
||||
path []uuid.UUID, blockedBy *uuid.UUID,
|
||||
visitedCount int,
|
||||
) error {
|
||||
pathStrs := make([]string, 0, len(path))
|
||||
for _, id := range path {
|
||||
|
|
@ -281,11 +284,22 @@ func persistTopologyResult(
|
|||
"end_component_uuid": endID.String(),
|
||||
"check_in_service": checkInService,
|
||||
"is_reachable": isReachable,
|
||||
"analysis_type": "POINT_TO_POINT_REACHABILITY",
|
||||
"path": pathStrs,
|
||||
"path_node_count": len(pathStrs),
|
||||
"visited_count": visitedCount,
|
||||
"computed_at": time.Now().Unix(),
|
||||
}
|
||||
if isReachable {
|
||||
result["hop_count"] = len(pathStrs) - 1
|
||||
}
|
||||
if blockedBy != nil {
|
||||
result["blocked_by"] = blockedBy.String()
|
||||
result["reason"] = "OUT_OF_SERVICE_COMPONENT"
|
||||
} else if isReachable {
|
||||
result["reason"] = "REACHABLE"
|
||||
} else {
|
||||
result["reason"] = "NO_DIRECTED_PATH"
|
||||
}
|
||||
|
||||
if err := database.CreateAsyncTaskResult(ctx, db, taskID, result); err != nil {
|
||||
|
|
|
|||
|
|
@ -46,12 +46,10 @@ func GetLongestCommonPrefixLength(query string, result string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
queryRunes := []rune(query)
|
||||
resultRunes := []rune(result)
|
||||
minLen := min(len(queryRunes), len(resultRunes))
|
||||
minLen := min(len(query), len(result))
|
||||
|
||||
for i := range minLen {
|
||||
if queryRunes[i] != resultRunes[i] {
|
||||
if query[i] != result[i] {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// GenNanoTSStr define func to generate nanosecond timestamp string by current time
|
||||
func GenNanoTSStr() string {
|
||||
// GenNanoTsStr define func to generate nanosecond timestamp string by current time
|
||||
func GenNanoTsStr() string {
|
||||
now := time.Now()
|
||||
nanoseconds := now.UnixNano()
|
||||
timestampStr := strconv.FormatInt(nanoseconds, 10)
|
||||
|
|
|
|||
Loading…
Reference in New Issue