Skip to content

Commit

Permalink
support redirect in dingtalk and lark approval
Browse files Browse the repository at this point in the history
Signed-off-by: Patrick Zhao <[email protected]>
  • Loading branch information
PetrusZ committed Dec 26, 2024
1 parent 431091a commit f124cbd
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 58 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/samber/lo v1.37.0
github.com/sashabaranov/go-openai v1.24.0
github.com/segmentio/encoding v0.4.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shirou/gopsutil/v3 v3.22.8
github.com/spf13/cobra v1.8.0
Expand All @@ -100,6 +101,7 @@ require (
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.30.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
Expand Down Expand Up @@ -283,6 +285,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rubenv/sql-migrate v1.3.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/afero v1.9.2 // indirect
Expand Down Expand Up @@ -329,7 +332,6 @@ require (
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
k8s.io/apiserver v0.27.7 // indirect
k8s.io/component-base v0.27.7 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,10 @@ github.com/sashabaranov/go-openai v1.24.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adO
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.4.1 h1:KLGaLSW0jrmhB58Nn4+98spfvPvmo4Ci1P/WIQ9wn7w=
github.com/segmentio/encoding v0.4.1/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
Expand Down
7 changes: 4 additions & 3 deletions pkg/microservice/aslan/config/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,12 @@ const (
SAEBatchReleaseTypeManual = "manual"
)

type ApproveOrReject string
type ApprovalStatus string

const (
Approve ApproveOrReject = "approve"
Reject ApproveOrReject = "reject"
Approve ApprovalStatus = "approve"
Reject ApprovalStatus = "reject"
Redirect ApprovalStatus = "redirect"
)

type DeploySourceType string
Expand Down
52 changes: 26 additions & 26 deletions pkg/microservice/aslan/core/common/repository/models/workflow_v4.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ type Approval struct {
}

type NativeApproval struct {
Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"`
ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"`
FloatApproveUsers []*User `bson:"-" yaml:"flat_approve_users" json:"flat_approve_users"`
NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"`
RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"`
Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"`
ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"`
FloatApproveUsers []*User `bson:"-" yaml:"flat_approve_users" json:"flat_approve_users"`
NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"`
RejectOrApprove config.ApprovalStatus `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"`
// InstanceCode: native approval instance code, save for working after restart aslan
InstanceCode string `bson:"instance_code" yaml:"instance_code" json:"instance_code"`
}
Expand All @@ -160,16 +160,16 @@ type DingTalkApproval struct {
type DingTalkApprovalNode struct {
ApproveUsers []*DingTalkApprovalUser `bson:"approve_users" yaml:"approve_users" json:"approve_users"`
Type dingtalk.ApprovalAction `bson:"type" yaml:"type" json:"type"`
RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"`
RejectOrApprove config.ApprovalStatus `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"`
}

type DingTalkApprovalUser struct {
ID string `bson:"id" yaml:"id" json:"id"`
Name string `bson:"name" yaml:"name" json:"name"`
Avatar string `bson:"avatar" yaml:"avatar" json:"avatar"`
RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve,omitempty" yaml:"-" json:"reject_or_approve,omitempty"`
Comment string `bson:"comment,omitempty" yaml:"-" json:"comment,omitempty"`
OperationTime int64 `bson:"operation_time,omitempty" yaml:"-" json:"operation_time,omitempty"`
ID string `bson:"id" yaml:"id" json:"id"`
Name string `bson:"name" yaml:"name" json:"name"`
Avatar string `bson:"avatar" yaml:"avatar" json:"avatar"`
RejectOrApprove config.ApprovalStatus `bson:"reject_or_approve,omitempty" yaml:"-" json:"reject_or_approve,omitempty"`
Comment string `bson:"comment,omitempty" yaml:"-" json:"comment,omitempty"`
OperationTime int64 `bson:"operation_time,omitempty" yaml:"-" json:"operation_time,omitempty"`
}

type LarkApproval struct {
Expand Down Expand Up @@ -211,16 +211,16 @@ func (l LarkApproval) GetLarkApprovalNode() (resp []*lark.ApprovalNode) {
}

type LarkApprovalNode struct {
ApproveUsers []*LarkApprovalUser `bson:"approve_users" yaml:"approve_users" json:"approve_users"`
Type lark.ApproveType `bson:"type" yaml:"type" json:"type"`
RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"`
ApproveUsers []*LarkApprovalUser `bson:"approve_users" yaml:"approve_users" json:"approve_users"`
Type lark.ApproveType `bson:"type" yaml:"type" json:"type"`
RejectOrApprove config.ApprovalStatus `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"`
}

type LarkApprovalUser struct {
lark.UserInfo `bson:",inline" yaml:",inline" json:",inline"`
RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve,omitempty" yaml:"-" json:"reject_or_approve,omitempty"`
Comment string `bson:"comment,omitempty" yaml:"-" json:"comment,omitempty"`
OperationTime int64 `bson:"operation_time,omitempty" yaml:"-" json:"operation_time,omitempty"`
RejectOrApprove config.ApprovalStatus `bson:"reject_or_approve,omitempty" yaml:"-" json:"reject_or_approve,omitempty"`
Comment string `bson:"comment,omitempty" yaml:"-" json:"comment,omitempty"`
OperationTime int64 `bson:"operation_time,omitempty" yaml:"-" json:"operation_time,omitempty"`
}

type WorkWXApproval struct {
Expand All @@ -235,14 +235,14 @@ type WorkWXApproval struct {
}

type User struct {
Type string `bson:"type" yaml:"type" json:"type"`
UserID string `bson:"user_id,omitempty" yaml:"user_id,omitempty" json:"user_id,omitempty"`
UserName string `bson:"user_name,omitempty" yaml:"user_name,omitempty" json:"user_name,omitempty"`
GroupID string `bson:"group_id,omitempty" yaml:"group_id,omitempty" json:"group_id,omitempty"`
GroupName string `bson:"group_name,omitempty" yaml:"group_name,omitempty" json:"group_name,omitempty"`
RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve,omitempty" yaml:"-" json:"reject_or_approve,omitempty"`
Comment string `bson:"comment,omitempty" yaml:"-" json:"comment,omitempty"`
OperationTime int64 `bson:"operation_time,omitempty" yaml:"-" json:"operation_time,omitempty"`
Type string `bson:"type" yaml:"type" json:"type"`
UserID string `bson:"user_id,omitempty" yaml:"user_id,omitempty" json:"user_id,omitempty"`
UserName string `bson:"user_name,omitempty" yaml:"user_name,omitempty" json:"user_name,omitempty"`
GroupID string `bson:"group_id,omitempty" yaml:"group_id,omitempty" json:"group_id,omitempty"`
GroupName string `bson:"group_name,omitempty" yaml:"group_name,omitempty" json:"group_name,omitempty"`
RejectOrApprove config.ApprovalStatus `bson:"reject_or_approve,omitempty" yaml:"-" json:"reject_or_approve,omitempty"`
Comment string `bson:"comment,omitempty" yaml:"-" json:"comment,omitempty"`
OperationTime int64 `bson:"operation_time,omitempty" yaml:"-" json:"operation_time,omitempty"`
}

type Job struct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ func EventHandler(appKey string, body []byte, signature, ts, nonce string) (*Eve
eventType := gjson.Get(data, "EventType").String()
log.Infof("receive dingtalk event type: %s instanceID: %s", eventType,
gjson.Get(data, "processInstanceId").String())
log.Debugf("receive dingtalk event data: %s", data)

switch eventType {
case EventTaskChange:
Expand Down
2 changes: 1 addition & 1 deletion pkg/microservice/aslan/core/common/service/lark/lark.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ type ApprovalManager struct {

type UserApprovalResult struct {
Result string
ApproveOrReject config.ApproveOrReject
ApproveOrReject config.ApprovalStatus
OperationTime int64
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func waitForLarkApprove(ctx context.Context, spec *commonmodels.JobTaskApprovalS
}
}

checkNodeStatus := func(node *commonmodels.LarkApprovalNode) (config.ApproveOrReject, error) {
checkNodeStatus := func(node *commonmodels.LarkApprovalNode) (config.ApprovalStatus, error) {
switch node.Type {
case "AND":
result := config.Approve
Expand Down Expand Up @@ -431,12 +431,13 @@ func waitForDingTalkApprove(ctx context.Context, spec *commonmodels.JobTaskAppro
dingservice.RemoveDingTalkApprovalManager(instanceID)
}()

resultMap := map[string]config.ApproveOrReject{
"agree": config.Approve,
"refuse": config.Reject,
resultMap := map[string]config.ApprovalStatus{
"agree": config.Approve,
"refuse": config.Reject,
"redirect": config.Redirect,
}

checkNodeStatus := func(node *commonmodels.DingTalkApprovalNode) (config.ApproveOrReject, error) {
checkNodeStatus := func(node *commonmodels.DingTalkApprovalNode) (config.ApprovalStatus, error) {
users := node.ApproveUsers
switch node.Type {
case "AND":
Expand All @@ -462,6 +463,22 @@ func waitForDingTalkApprove(ctx context.Context, spec *commonmodels.JobTaskAppro
}
}

checkApprovalFinished := func(nodes []*commonmodels.DingTalkApprovalNode) bool {
count := len(nodes)
finishedNode := 0
for _, node := range nodes {
if node.RejectOrApprove == config.Approve || node.RejectOrApprove == config.Redirect {
finishedNode++
}
}

if finishedNode == count {
return true
}

return false
}

timeoutChan := time.After(time.Duration(timeout) * time.Minute)
for {
time.Sleep(1 * time.Second)
Expand All @@ -471,28 +488,119 @@ func waitForDingTalkApprove(ctx context.Context, spec *commonmodels.JobTaskAppro
case <-timeoutChan:
return config.StatusTimeout, fmt.Errorf("workflow timeout")
default:
userApprovalResult := dingservice.GetAllUserApprovalResults(instanceID)
userUpdated := false
userApprovalResult := dingservice.GetAllUserApprovalResults(instanceID)

approvalUserMap := map[string]*commonmodels.DingTalkApprovalUser{}
for _, node := range approval.ApprovalNodes {
for _, approveUser := range node.ApproveUsers {
approvalUserMap[approveUser.ID] = approveUser
}
}

for _, node := range approval.ApprovalNodes {
if node.RejectOrApprove != "" {
continue
}

isRedirected := false
for _, user := range node.ApproveUsers {
if result := userApprovalResult[user.ID]; result != nil && user.RejectOrApprove == "" {
if result := userApprovalResult[user.ID]; result != nil {
if user.RejectOrApprove == resultMap[result.Result] &&
user.Comment == result.Remark &&
user.OperationTime == result.OperationTime {
continue
}

user.RejectOrApprove = resultMap[result.Result]
user.Comment = result.Remark
user.OperationTime = result.OperationTime
userUpdated = true

if user.RejectOrApprove == config.Redirect {
isRedirected = true
}
}
}

if isRedirected {
instanceInfo, err := client.GetApprovalInstance(instanceID)
if err != nil {
log.Errorf("get instance final info failed: %v", err)
return config.StatusFailed, fmt.Errorf("get instance final info error: %s", err)
}

timeLayout := "2006-01-02T15:04Z"
operationRecordMap := map[string]*dingtalk.OperationRecord{}
for _, record := range instanceInfo.OperationRecords {
oldRecord, ok := operationRecordMap[record.UserID]
if !ok {
operationRecordMap[record.UserID] = record
} else {
oldTime, err := time.Parse(timeLayout, oldRecord.Date)
if err != nil {
return config.StatusFailed, fmt.Errorf("parse operation time failed: %s", err)
}
newTime, err := time.Parse(timeLayout, record.Date)
if err != nil {
return config.StatusFailed, fmt.Errorf("parse operation time failed: %s", err)
}

if newTime.After(oldTime) {
operationRecordMap[record.UserID] = record
}
}
}

for _, task := range instanceInfo.Tasks {
if _, ok := approvalUserMap[task.UserID]; !ok {
operationTime := int64(0)
record, ok := operationRecordMap[task.UserID]
if !ok {
record = &dingtalk.OperationRecord{
UserID: task.UserID,
Result: task.Result,
}
} else {
t, err := time.Parse(timeLayout, record.Date)
if err != nil {
return config.StatusFailed, fmt.Errorf("parse operation time failed: %s", err)
}
operationTime = t.Unix()
}

userInfo, err := client.GetUserInfo(task.UserID)
if err != nil {
err = fmt.Errorf("get user info %s failed: %s", task.UserID, err)
log.Error(err)
return config.StatusFailed, err
}

redirectedUser := &commonmodels.DingTalkApprovalUser{
ID: task.UserID,
Name: userInfo.Name,
RejectOrApprove: resultMap[task.Result],
Comment: record.Remark,
OperationTime: operationTime,
}
node.ApproveUsers = append(node.ApproveUsers, redirectedUser)

userUpdated = true
}
}
}

node.RejectOrApprove, err = checkNodeStatus(node)
if err != nil {
log.Errorf("check node failed: %v", err)
return config.StatusFailed, fmt.Errorf("check node failed, error: %s", err)
}

switch node.RejectOrApprove {
case config.Approve:
ack()
case config.Redirect:
ack()
case config.Reject:
return config.StatusReject, fmt.Errorf("Approval has been rejected")
default:
Expand All @@ -502,7 +610,8 @@ func waitForDingTalkApprove(ctx context.Context, spec *commonmodels.JobTaskAppro
}
break
}
if approval.ApprovalNodes[len(approval.ApprovalNodes)-1].RejectOrApprove == config.Approve {

if checkApprovalFinished(approval.ApprovalNodes) {
instanceInfo, err := client.GetApprovalInstance(instanceID)
if err != nil {
log.Errorf("get instance final info failed: %v", err)
Expand All @@ -511,8 +620,9 @@ func waitForDingTalkApprove(ctx context.Context, spec *commonmodels.JobTaskAppro
if instanceInfo.Status == "COMPLETED" && instanceInfo.Result == "agree" {
return config.StatusPassed, nil
} else {
log.Errorf("Unexpect instance final status is %s, result is %s", instanceInfo.Status, instanceInfo.Result)
return config.StatusFailed, fmt.Errorf("get unexpected instance final info, error: %s", err)
err = fmt.Errorf("Unexpect instance final status is %s, result is %s", instanceInfo.Status, instanceInfo.Result)
log.Error(err)
return config.StatusFailed, err
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/microservice/aslan/core/common/util/workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (

commonmodels "github.com/koderover/zadig/v2/pkg/microservice/aslan/core/common/repository/models"
"github.com/koderover/zadig/v2/pkg/setting"
"github.com/koderover/zadig/v2/pkg/tool/log"
)

func CalcWorkflowTaskRunningTime(task *commonmodels.WorkflowTask) int64 {
Expand All @@ -31,7 +30,7 @@ func CalcWorkflowTaskRunningTime(task *commonmodels.WorkflowTask) int64 {
runningTime += 0
} else if task.EndTime == 0 {
runningTime += 0
log.Errorf("workflow task %s/%d stage %s end time is 0", task.WorkflowName, task.TaskID, stage.Name)
// log.Errorf("workflow task %s/%d stage %s end time is 0", task.WorkflowName, task.TaskID, stage.Name)
} else {
runningTime += stage.EndTime - stage.StartTime
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/microservice/aslan/core/release_plan/service/approval.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ func updateDingTalkApproval(ctx context.Context, approvalInfo *models.Approval)
}
client := dingtalk.NewClient(data.DingTalkAppKey, data.DingTalkAppSecret)

resultMap := map[string]config.ApproveOrReject{
resultMap := map[string]config.ApprovalStatus{
"agree": config.Approve,
"refuse": config.Reject,
}

checkNodeStatus := func(node *models.DingTalkApprovalNode) (config.ApproveOrReject, error) {
checkNodeStatus := func(node *models.DingTalkApprovalNode) (config.ApprovalStatus, error) {
users := node.ApproveUsers
switch node.Type {
case "AND":
Expand Down Expand Up @@ -516,7 +516,7 @@ func updateLarkApproval(ctx context.Context, approval *models.Approval) error {
}
client := lark.NewClient(data.AppID, data.AppSecret)

checkNodeStatus := func(node *models.LarkApprovalNode) (config.ApproveOrReject, error) {
checkNodeStatus := func(node *models.LarkApprovalNode) (config.ApprovalStatus, error) {
switch node.Type {
case "AND":
result := config.Approve
Expand Down
Loading

0 comments on commit f124cbd

Please sign in to comment.