Create & Init Project...
This commit is contained in:
18
app/service/ep/BUILD
Normal file
18
app/service/ep/BUILD
Normal file
@ -0,0 +1,18 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//app/service/ep/footman:all-srcs",
|
||||
"//app/service/ep/saga-agent:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
8
app/service/ep/OWNERS
Normal file
8
app/service/ep/OWNERS
Normal file
@ -0,0 +1,8 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- daiwei
|
||||
labels:
|
||||
- ep
|
||||
- new-project
|
||||
- service
|
22
app/service/ep/footman/BUILD
Normal file
22
app/service/ep/footman/BUILD
Normal file
@ -0,0 +1,22 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//app/service/ep/footman/cmd:all-srcs",
|
||||
"//app/service/ep/footman/conf:all-srcs",
|
||||
"//app/service/ep/footman/dao:all-srcs",
|
||||
"//app/service/ep/footman/model:all-srcs",
|
||||
"//app/service/ep/footman/server/http:all-srcs",
|
||||
"//app/service/ep/footman/service:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
39
app/service/ep/footman/CHANGELOG.md
Normal file
39
app/service/ep/footman/CHANGELOG.md
Normal file
@ -0,0 +1,39 @@
|
||||
##### ep-footman
|
||||
##### Version 1. 0. 9
|
||||
1. 修复exe工具中周末时间计算错误的bug
|
||||
|
||||
##### Version 1. 0. 8
|
||||
1. 修正进搏会调休导致的报表统计时间不对
|
||||
|
||||
##### Version 1. 0. 8
|
||||
1. bugly数据同步到tapd
|
||||
|
||||
##### Version 1. 0. 7
|
||||
1. 增加b+故事墙
|
||||
|
||||
##### Version 1. 0. 6
|
||||
1. 增加releaseid转发布计划字段
|
||||
|
||||
##### Version 1. 0. 5
|
||||
1. 增加Bug缺陷相关数据整理功能de
|
||||
|
||||
|
||||
|
||||
##### Version 1. 0. 4
|
||||
1. 增加定时任务执行重试机制
|
||||
2. 增加失败邮件通知功能
|
||||
|
||||
##### Version 1. 0. 3
|
||||
1. 增加定时任务执行下来tapd数据
|
||||
|
||||
##### Version 1. 0. 2
|
||||
1. 合并TAPD需求和迭代不常用字段写成json
|
||||
|
||||
##### Version 1. 0. 1
|
||||
1. 支持-d参数以判断是写时间戳还是日期到excel
|
||||
2. 优化故事墙excel固定列顺序
|
||||
3. 实现tapd数据写文件和文件下载的接口
|
||||
|
||||
##### Version 1. 0. 0
|
||||
1. 拉取bugly原始数据以自定义的格式落库
|
||||
2. 实现tapd报表:产品验收时长统计,产品验收打回需求统计,测试中打回需求统计,待测试时长统计,纯测试时长统计,故事墙流图统计,延迟需求列表
|
14
app/service/ep/footman/CONTRIBUTORS.md
Normal file
14
app/service/ep/footman/CONTRIBUTORS.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Owner
|
||||
maojian
|
||||
yuanmin
|
||||
fengyifeng
|
||||
xuneng
|
||||
|
||||
# Author
|
||||
yuanmin
|
||||
fengyifeng
|
||||
xuneng
|
||||
|
||||
# Reviewer
|
||||
zhapuyu
|
||||
wangxu01
|
19
app/service/ep/footman/OWNERS
Normal file
19
app/service/ep/footman/OWNERS
Normal file
@ -0,0 +1,19 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- fengyifeng
|
||||
- maojian
|
||||
- xuneng
|
||||
- yuanmin
|
||||
labels:
|
||||
- ep
|
||||
- service
|
||||
- service/ep/footman
|
||||
options:
|
||||
no_parent_owners: true
|
||||
reviewers:
|
||||
- fengyifeng
|
||||
- wangxu01
|
||||
- xuneng
|
||||
- yuanmin
|
||||
- zhapuyu
|
19
app/service/ep/footman/README.md
Normal file
19
app/service/ep/footman/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# footman-service
|
||||
|
||||
# 项目简介
|
||||
### 背景/Background
|
||||
* 目前移动端crash信息会上报到bugly,bugly系统查询数据比较慢且没有足够的数据分析功能,影响开发查询分析crash的效率
|
||||
* 目前项目需求bug等信息记录在tapd, 同样tapd系统现有的报表功能并不满足我们PMO的需求,使PMO拿不到充足的数据做分析
|
||||
* 于是Footman项目为了解决这些问题孕育而生
|
||||
|
||||
### 概览/Overview
|
||||
* Footman会根据开发和PMO的需求,从bugly和tapd拉取原始数据生成自定义的报表数据
|
||||
|
||||
|
||||
# 编译环境
|
||||
|
||||
|
||||
# 依赖包
|
||||
|
||||
|
||||
# 编译执行
|
1
app/service/ep/footman/api/http/api.md
Normal file
1
app/service/ep/footman/api/http/api.md
Normal file
@ -0,0 +1 @@
|
||||
# HTTP API文档
|
48
app/service/ep/footman/cmd/BUILD
Normal file
48
app/service/ep/footman/cmd/BUILD
Normal file
@ -0,0 +1,48 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "cmd",
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["main.go"],
|
||||
data = ["tapd.toml"],
|
||||
importpath = "go-common/app/service/ep/footman/cmd",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/footman/conf:go_default_library",
|
||||
"//app/service/ep/footman/server/http:go_default_library",
|
||||
"//app/service/ep/footman/service:go_default_library",
|
||||
"//library/ecode/tip:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/trace:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//app/service/ep/footman/cmd/bugly:all-srcs",
|
||||
"//app/service/ep/footman/cmd/tapd:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
46
app/service/ep/footman/cmd/bugly/BUILD
Normal file
46
app/service/ep/footman/cmd/bugly/BUILD
Normal file
@ -0,0 +1,46 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "bugly",
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["bugly.go"],
|
||||
importpath = "go-common/app/service/ep/footman/cmd/bugly",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/footman/conf:go_default_library",
|
||||
"//app/service/ep/footman/service:go_default_library",
|
||||
"//library/cache/memcache:go_default_library",
|
||||
"//library/container/pool:go_default_library",
|
||||
"//library/database/orm:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
"//library/net/netutil/breaker:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
100
app/service/ep/footman/cmd/bugly/bugly.go
Normal file
100
app/service/ep/footman/cmd/bugly/bugly.go
Normal file
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
|
||||
"go-common/app/service/ep/footman/conf"
|
||||
"go-common/app/service/ep/footman/service"
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/container/pool"
|
||||
"go-common/library/database/orm"
|
||||
"go-common/library/log"
|
||||
xhttp "go-common/library/net/http/blademaster"
|
||||
"go-common/library/net/netutil/breaker"
|
||||
"go-common/library/time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
versionPath string
|
||||
cookiePath string
|
||||
tokenPath string
|
||||
action string
|
||||
)
|
||||
|
||||
flag.StringVar(&versionPath, "v", "", "版本批次文件路径")
|
||||
flag.StringVar(&cookiePath, "c", "", "cookie文件路径")
|
||||
flag.StringVar(&tokenPath, "t", "", "token文件路径")
|
||||
flag.StringVar(&action, "a", "", "操作类型")
|
||||
flag.Parse()
|
||||
|
||||
c := &conf.Config{
|
||||
HTTPClient: &xhttp.ClientConfig{
|
||||
App: &xhttp.App{
|
||||
Key: "c05dd4e1638a8af0",
|
||||
Secret: "7daa7f8c06cd33c5c3067063c746fdcb",
|
||||
},
|
||||
Dial: time.Duration(20000000000),
|
||||
Timeout: time.Duration(100000000000),
|
||||
KeepAlive: time.Duration(600000000000),
|
||||
Breaker: &breaker.Config{
|
||||
Window: time.Duration(100000000000),
|
||||
Sleep: time.Duration(20000000000),
|
||||
Bucket: 10,
|
||||
Ratio: 0.5,
|
||||
Request: 100,
|
||||
},
|
||||
},
|
||||
Bugly: &conf.BuglyConf{
|
||||
Host: "https://bugly.qq.com",
|
||||
Cookie: cookiePath,
|
||||
Token: tokenPath,
|
||||
Version: versionPath,
|
||||
},
|
||||
ORM: &orm.Config{
|
||||
DSN: "root:123456@tcp(172.18.33.130:3306)/footman?timeout=200ms&readTimeout=2000ms&writeTimeout=2000ms&parseTime=true&loc=Local&charset=utf8,utf8mb4",
|
||||
Active: 5,
|
||||
Idle: 5,
|
||||
IdleTimeout: time.Duration(20000000000),
|
||||
},
|
||||
Mail: &conf.Mail{
|
||||
Host: "smtp.exmail.qq.com",
|
||||
Port: 465,
|
||||
Username: "merlin@bilibili.com",
|
||||
Password: "",
|
||||
NoticeOwner: []string{"fengyifeng@bilibili.com"},
|
||||
},
|
||||
Memcache: &conf.Memcache{
|
||||
Expire: time.Duration(10000000),
|
||||
Config: &memcache.Config{
|
||||
Name: "merlin",
|
||||
Proto: "tcp",
|
||||
Addr: "172.22.33.137:11216",
|
||||
DialTimeout: time.Duration(1000),
|
||||
ReadTimeout: time.Duration(1000),
|
||||
WriteTimeout: time.Duration(1000),
|
||||
Config: &pool.Config{
|
||||
Active: 10,
|
||||
IdleTimeout: time.Duration(1000),
|
||||
},
|
||||
},
|
||||
},
|
||||
Bugly2Tapd: &conf.Bugly2Tapd{
|
||||
ProjectIds: []string{"900028525"},
|
||||
},
|
||||
}
|
||||
s := service.New(c)
|
||||
log.Info("v1.0.40")
|
||||
|
||||
switch action {
|
||||
case "insertTapd":
|
||||
s.BuglyInsertTapd(context.Background())
|
||||
default:
|
||||
s.GetSaveIssuesWithMultiVersion(context.Background())
|
||||
s.UpdateBuglyStatusInTapd(context.Background())
|
||||
s.UpdateBugInTapd(context.Background())
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
}
|
46
app/service/ep/footman/cmd/main.go
Normal file
46
app/service/ep/footman/cmd/main.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"go-common/app/service/ep/footman/conf"
|
||||
"go-common/app/service/ep/footman/server/http"
|
||||
"go-common/app/service/ep/footman/service"
|
||||
ecode "go-common/library/ecode/tip"
|
||||
"go-common/library/log"
|
||||
"go-common/library/net/trace"
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if err := conf.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Init(conf.Conf.Log)
|
||||
defer log.Close()
|
||||
log.Info("start")
|
||||
defer trace.Close()
|
||||
ecode.Init(conf.Conf.Ecode)
|
||||
|
||||
s := service.New(conf.Conf)
|
||||
|
||||
http.Init(conf.Conf, s)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
|
||||
for {
|
||||
si := <-c
|
||||
log.Info("get a signal %s", si.String())
|
||||
switch si {
|
||||
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
|
||||
log.Info("exit")
|
||||
s.Close()
|
||||
return
|
||||
case syscall.SIGHUP:
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
76
app/service/ep/footman/cmd/tapd.toml
Normal file
76
app/service/ep/footman/cmd/tapd.toml
Normal file
@ -0,0 +1,76 @@
|
||||
|
||||
[bm]
|
||||
addr = "0.0.0.0:9001"
|
||||
timeout = "10s"
|
||||
|
||||
[httpClient]
|
||||
key = "c05dd4e1638a8af0"
|
||||
secret = "7daa7f8c06cd33c5c3067063c746fdcb"
|
||||
dial = "2s"
|
||||
timeout = "100s"
|
||||
keepAlive = "60s"
|
||||
timer = 1000
|
||||
[httpClient.breaker]
|
||||
window = "10s"
|
||||
sleep = "2000ms"
|
||||
bucket = 10
|
||||
ratio = 0.5
|
||||
request = 100
|
||||
|
||||
[tapd]
|
||||
WorkspaceIDs = ["20060791","20055921"]
|
||||
|
||||
|
||||
IterationWorkspaceIDs = ["20060791","20055921","20082211"]
|
||||
StoryWorkspaceIDs = ["20060791","20055921"]
|
||||
BugWorkspaceIDs = ["20082211"]
|
||||
|
||||
IPS = 30
|
||||
SPS = 30
|
||||
SCPS = 50
|
||||
CPS = 50
|
||||
StoryFilePath = "story.txt"
|
||||
ChangeFilePath = "change.txt"
|
||||
IterationFilePath = "iteration.txt"
|
||||
BugFilePath = "bug.txt"
|
||||
retryTime = 5
|
||||
waitTime = "10s"
|
||||
|
||||
|
||||
[scheduler]
|
||||
#每天凌晨2点跑拉取数据任务
|
||||
saveTapdTime = "0 0 2 * * ?"
|
||||
|
||||
[mail]
|
||||
host = "smtp.exmail.qq.com"
|
||||
port = 465
|
||||
username = "merlin@bilibili.com"
|
||||
password = ""
|
||||
noticeOwner = ["fengyifeng@bilibili.com"]
|
||||
|
||||
[memcache]
|
||||
name = "merlin"
|
||||
proto = "tcp"
|
||||
addr = "172.22.33.137:11216"
|
||||
idle = 5
|
||||
active = 10
|
||||
dialTimeout = "1s"
|
||||
readTimeout = "1s"
|
||||
writeTimeout = "1s"
|
||||
idleTimeout = "10s"
|
||||
expire = "12h"
|
||||
|
||||
[bugly]
|
||||
host = "https://bugly.qq.com"
|
||||
cookie = "D:\\fyf\\code5\\bugly-dailyrun\\conf\\cookie.conf"
|
||||
token = "D:\\fyf\\code5\\bugly-dailyrun\\conf\\token.conf"
|
||||
version = "D:\\fyf\\code5\\bugly-dailyrun\\conf\\version.conf"
|
||||
|
||||
[bugly2tapd]
|
||||
projectIds = ["900028525"]
|
||||
|
||||
[orm]
|
||||
dsn = "root:123456@tcp(172.18.33.130:3306)/footman?timeout=200ms&readTimeout=2000ms&writeTimeout=2000ms&parseTime=true&loc=Local&charset=utf8,utf8mb4"
|
||||
active = 5
|
||||
idle = 5
|
||||
idleTimeout = "4h"
|
46
app/service/ep/footman/cmd/tapd/BUILD
Normal file
46
app/service/ep/footman/cmd/tapd/BUILD
Normal file
@ -0,0 +1,46 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "tapd",
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["tapd.go"],
|
||||
importpath = "go-common/app/service/ep/footman/cmd/tapd",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/footman/conf:go_default_library",
|
||||
"//app/service/ep/footman/model:go_default_library",
|
||||
"//app/service/ep/footman/service:go_default_library",
|
||||
"//library/cache/memcache:go_default_library",
|
||||
"//library/container/pool:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
"//library/net/netutil/breaker:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
144
app/service/ep/footman/cmd/tapd/tapd.go
Normal file
144
app/service/ep/footman/cmd/tapd/tapd.go
Normal file
@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"go-common/app/service/ep/footman/conf"
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/app/service/ep/footman/service"
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/container/pool"
|
||||
"go-common/library/log"
|
||||
xhttp "go-common/library/net/http/blademaster"
|
||||
"go-common/library/net/netutil/breaker"
|
||||
"go-common/library/time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
workspaceType string
|
||||
reportType string
|
||||
workspaceID string
|
||||
iteration string
|
||||
reportPath string
|
||||
isTimeStr string
|
||||
|
||||
startTime string
|
||||
endTime string
|
||||
importPath string
|
||||
importSheetName string
|
||||
)
|
||||
|
||||
flag.StringVar(&workspaceType, "t", "", "端类型,android或ios")
|
||||
flag.StringVar(&reportType, "r", "", "报表类型")
|
||||
flag.StringVar(&workspaceID, "w", "", "项目ID")
|
||||
flag.StringVar(&iteration, "i", "", "迭代名称")
|
||||
flag.StringVar(&reportPath, "p", "", "报表文件路径")
|
||||
flag.StringVar(&isTimeStr, "d", "n", "是否详细时间")
|
||||
|
||||
flag.StringVar(&startTime, "s", "", "开始时间 2018-01-01")
|
||||
flag.StringVar(&endTime, "e", "", "结束时间 2019-01-01")
|
||||
flag.StringVar(&importPath, "f", "", "导入文件")
|
||||
flag.StringVar(&importSheetName, "sn", "", "导入文件sheet name")
|
||||
flag.Parse()
|
||||
|
||||
c := &conf.Config{
|
||||
Tapd: &conf.Tapd{
|
||||
RetryTime: 5,
|
||||
WaitTime: time.Duration(100000),
|
||||
},
|
||||
|
||||
HTTPClient: &xhttp.ClientConfig{
|
||||
App: &xhttp.App{
|
||||
Key: "c05dd4e1638a8af0",
|
||||
Secret: "7daa7f8c06cd33c5c3067063c746fdcb",
|
||||
},
|
||||
Dial: time.Duration(2000000000),
|
||||
Timeout: time.Duration(10000000000),
|
||||
KeepAlive: time.Duration(60000000000),
|
||||
Breaker: &breaker.Config{
|
||||
Window: time.Duration(10000000000),
|
||||
Sleep: time.Duration(2000000000),
|
||||
Bucket: 10,
|
||||
Ratio: 0.5,
|
||||
Request: 100,
|
||||
},
|
||||
},
|
||||
Mail: &conf.Mail{
|
||||
Host: "smtp.exmail.qq.com",
|
||||
Port: 465,
|
||||
Username: "merlin@bilibili.com",
|
||||
Password: "",
|
||||
NoticeOwner: []string{"fengyifeng@bilibili.com"},
|
||||
},
|
||||
Memcache: &conf.Memcache{
|
||||
Expire: time.Duration(10000000),
|
||||
Config: &memcache.Config{
|
||||
Name: "merlin",
|
||||
Proto: "tcp",
|
||||
Addr: "172.22.33.137:11216",
|
||||
DialTimeout: time.Duration(1000),
|
||||
ReadTimeout: time.Duration(1000),
|
||||
WriteTimeout: time.Duration(1000),
|
||||
Config: &pool.Config{
|
||||
Active: 10,
|
||||
IdleTimeout: time.Duration(1000),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
s := service.New(c)
|
||||
|
||||
switch reportType {
|
||||
|
||||
//纯测试时长统计列表区分_ios_android
|
||||
case "testtime":
|
||||
if err := s.TestTimeReport(workspaceID, workspaceType, iteration, reportPath, model.Test, model.IPS, model.SPS, model.SCPS); err != nil {
|
||||
log.Error("Error happened when generating test time reprot:error(%v)", err)
|
||||
}
|
||||
|
||||
//待测试时长统计 不区分ios android
|
||||
case "waittest":
|
||||
if err := s.WaitTimeReport(workspaceID, iteration, reportPath, model.Test, model.IPS, model.SPS, model.SCPS); err != nil {
|
||||
log.Error("Error happened when generating wait for test time reprot:error(%v)", err)
|
||||
}
|
||||
|
||||
//产品验收时长统计 不区分ios android
|
||||
case "experiencetime":
|
||||
if err := s.TestTimeReport(workspaceID, workspaceType, iteration, reportPath, model.Experience, model.IPS, model.SPS, model.SCPS); err != nil {
|
||||
log.Error("Error happened when generating experience time reprot:error(%v)", err)
|
||||
}
|
||||
case "delayedstory":
|
||||
if err := s.DelayedStoryReport(workspaceID, workspaceType, iteration, reportPath, model.IPS, model.SPS, model.SCPS); err != nil {
|
||||
log.Error("Error happened when generating delayed story reprot:error(%v)", err)
|
||||
}
|
||||
|
||||
//测试中打回需求统计
|
||||
case "testrejected":
|
||||
if err := s.RejectedStoryReport(workspaceID, workspaceType, iteration, reportPath, model.Test, model.IPS, model.SPS, model.SCPS); err != nil {
|
||||
log.Error("Error happened when generating test rejected story reprot:error(%v)", err)
|
||||
}
|
||||
|
||||
//产品验收打回需求统计
|
||||
case "experiencerejected":
|
||||
if err := s.RejectedStoryReport(workspaceID, workspaceType, iteration, reportPath, model.Experience, model.IPS, model.SPS, model.SCPS); err != nil {
|
||||
log.Error("Error happened when generating experience rejected story reprot:error(%v)", err)
|
||||
}
|
||||
|
||||
//故事墙
|
||||
case "storywall":
|
||||
isTime := false
|
||||
if isTimeStr == "y" {
|
||||
isTime = true
|
||||
}
|
||||
if err := s.StoryWallReport(workspaceID, workspaceType, iteration, reportPath, isTime, model.IPS, model.SPS, model.SCPS, model.CPS); err != nil {
|
||||
log.Error("Error happened when generating story wall reprot:error(%v)", err)
|
||||
}
|
||||
|
||||
case "storywallreport":
|
||||
if err := s.GenStoryReport(workspaceType, importPath, importSheetName, reportPath, iteration, startTime, endTime); err != nil {
|
||||
log.Error("Error happened when generating experience rejected story reprot:error(%v)", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
38
app/service/ep/footman/conf/BUILD
Normal file
38
app/service/ep/footman/conf/BUILD
Normal file
@ -0,0 +1,38 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["conf.go"],
|
||||
importpath = "go-common/app/service/ep/footman/conf",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//library/cache/memcache:go_default_library",
|
||||
"//library/conf:go_default_library",
|
||||
"//library/database/orm:go_default_library",
|
||||
"//library/ecode/tip:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
"//library/time:go_default_library",
|
||||
"//vendor/github.com/BurntSushi/toml:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
150
app/service/ep/footman/conf/conf.go
Normal file
150
app/service/ep/footman/conf/conf.go
Normal file
@ -0,0 +1,150 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/conf"
|
||||
"go-common/library/database/orm"
|
||||
ecode "go-common/library/ecode/tip"
|
||||
"go-common/library/log"
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
xtime "go-common/library/time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
var (
|
||||
confPath string
|
||||
client *conf.Client
|
||||
// Conf config
|
||||
Conf = &Config{}
|
||||
)
|
||||
|
||||
// Config .
|
||||
type Config struct {
|
||||
|
||||
Log *log.Config
|
||||
|
||||
Bugly *BuglyConf
|
||||
|
||||
BM *bm.ServerConfig
|
||||
|
||||
Ecode *ecode.Config
|
||||
|
||||
ORM *orm.Config
|
||||
|
||||
HTTPClient *bm.ClientConfig
|
||||
|
||||
Scheduler *Scheduler
|
||||
|
||||
Tapd *Tapd
|
||||
|
||||
Mail *Mail
|
||||
|
||||
Memcache *Memcache
|
||||
|
||||
Bugly2Tapd *Bugly2Tapd
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&confPath, "conf", "", "default config path")
|
||||
}
|
||||
|
||||
// Memcache memcache
|
||||
type Memcache struct {
|
||||
*memcache.Config
|
||||
Expire xtime.Duration
|
||||
}
|
||||
|
||||
// Bugly2Tapd Bugly to Tapd
|
||||
type Bugly2Tapd struct {
|
||||
ProjectIds []string
|
||||
}
|
||||
|
||||
// Mail mail
|
||||
type Mail struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
NoticeOwner []string
|
||||
}
|
||||
|
||||
// BuglyConf Bugly Conf.
|
||||
type BuglyConf struct {
|
||||
Host string
|
||||
Cookie string
|
||||
Token string
|
||||
Version string
|
||||
}
|
||||
|
||||
// Scheduler Scheduler.
|
||||
type Scheduler struct {
|
||||
SaveTapdTime string
|
||||
}
|
||||
|
||||
// Tapd Tapd info
|
||||
type Tapd struct {
|
||||
IterationWorkspaceIDs []string
|
||||
StoryWorkspaceIDs []string
|
||||
BugWorkspaceIDs []string
|
||||
IPS int
|
||||
SPS int
|
||||
SCPS int
|
||||
CPS int
|
||||
StoryFilePath string
|
||||
ChangeFilePath string
|
||||
IterationFilePath string
|
||||
BugFilePath string
|
||||
RetryTime int
|
||||
WaitTime xtime.Duration
|
||||
}
|
||||
|
||||
// Init init conf
|
||||
func Init() error {
|
||||
if confPath != "" {
|
||||
return local()
|
||||
}
|
||||
return remote()
|
||||
}
|
||||
|
||||
func local() (err error) {
|
||||
_, err = toml.DecodeFile(confPath, &Conf)
|
||||
return
|
||||
}
|
||||
|
||||
func remote() (err error) {
|
||||
if client, err = conf.New(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = load(); err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for range client.Event() {
|
||||
log.Info("config reload")
|
||||
if load() != nil {
|
||||
log.Error("config reload error (%v)", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func load() (err error) {
|
||||
var (
|
||||
s string
|
||||
ok bool
|
||||
tmpConf *Config
|
||||
)
|
||||
if s, ok = client.Toml2(); !ok {
|
||||
return errors.New("load config center error")
|
||||
}
|
||||
if _, err = toml.Decode(s, &tmpConf); err != nil {
|
||||
return errors.New("could not decode config")
|
||||
}
|
||||
*Conf = *tmpConf
|
||||
return
|
||||
}
|
48
app/service/ep/footman/dao/BUILD
Normal file
48
app/service/ep/footman/dao/BUILD
Normal file
@ -0,0 +1,48 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bugly.go",
|
||||
"dao.go",
|
||||
"mail.go",
|
||||
"mysql_bug_template.go",
|
||||
"mysql_bugly.go",
|
||||
"tapd.go",
|
||||
],
|
||||
importpath = "go-common/app/service/ep/footman/dao",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/footman/conf:go_default_library",
|
||||
"//app/service/ep/footman/model:go_default_library",
|
||||
"//library/cache/memcache:go_default_library",
|
||||
"//library/database/orm:go_default_library",
|
||||
"//library/ecode:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
"//library/sync/pipeline/fanout:go_default_library",
|
||||
"//vendor/github.com/jinzhu/gorm:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/gopkg.in/gomail.v2:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
243
app/service/ep/footman/dao/bugly.go
Normal file
243
app/service/ep/footman/dao/bugly.go
Normal file
@ -0,0 +1,243 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/ecode"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
const (
|
||||
_buglyOkCode = 200
|
||||
_issueDetailCode = 100000
|
||||
_issueDetailList = "/v2/lastCrashInfo/appId/%s/platformId/%s/issues/%s?offsetTop=56&fsn=6d0260aa-331f-48b9-8557-c2aaf6e0be90"
|
||||
_issueList = "/v2/issueList?sortOrder=desc&sortField=uploadTime&rows=50&fsn=45cdb5aa-eb6f-4bda-9bba-ba0b264bfc93&appId=%s&platformId=%s&version=%s&start=%s&rows=%s&exceptionTypeList=%s"
|
||||
_issueVersionList = "/v2/getSelector/appId/%s/platformId/%s?types=version&fsn=8b8782b5-053d-4f58-bc17-d5c43d7f5ece"
|
||||
_issueExceptionList = "/v2/issueInfo/appId/%s/platformId/%s/issueId/%s/exceptionTypeList/Crash,Native,ExtensionCrash?fsn=114a8d02-586d-4fe4-8c23-79003fbe6882"
|
||||
)
|
||||
|
||||
// BugVersion Bug Version .
|
||||
func (d *Dao) BugVersion(c context.Context, projectID, platformID string) (ret []*model.BugVersion, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res *model.BugVersionResponse
|
||||
cookie string
|
||||
token string
|
||||
hostStr string
|
||||
)
|
||||
|
||||
hostStr = d.c.Bugly.Host + fmt.Sprintf(_issueVersionList, projectID, platformID)
|
||||
|
||||
if req, err = d.newRequest("GET", hostStr, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cookie, token, err = d.cookieAndToken(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Cookie", cookie)
|
||||
req.Header.Set("x-token", token)
|
||||
req.Header.Set("content-type", "application/json;charset=utf-8")
|
||||
req.Header.Set("x-csrf-token", "undefined")
|
||||
|
||||
if err = d.httpClient.Do(c, req, &res); err != nil {
|
||||
log.Error("d.BugVersion url(%s) err(%v)", "BugVersion", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Status != _buglyOkCode {
|
||||
err = ecode.MartheBuglyErr
|
||||
log.Error("Status url(%s) res(%v) err(%v)", "BugVersion", res, err)
|
||||
log.Error("maybe need to update cookie and token")
|
||||
return
|
||||
}
|
||||
|
||||
ret = res.Ret.BugVersionList
|
||||
return
|
||||
}
|
||||
|
||||
// BuglyIssueAndRetry Bugly Issue And Retry.
|
||||
func (d *Dao) BuglyIssueAndRetry(c context.Context, bugIssueRequest *model.BugIssueRequest) (ret *model.BugRet, err error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
if ret, err = d.BuglyIssue(c, bugIssueRequest); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BuglyIssue Get Issue.
|
||||
func (d *Dao) BuglyIssue(c context.Context, bugIssueRequest *model.BugIssueRequest) (ret *model.BugRet, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res *model.BugIssueResponse
|
||||
cookie string
|
||||
token string
|
||||
hostStr string
|
||||
)
|
||||
|
||||
hostStr = d.c.Bugly.Host + fmt.Sprintf(_issueList, bugIssueRequest.ProjectID, bugIssueRequest.PlatformID, bugIssueRequest.Version, strconv.Itoa(bugIssueRequest.StartNum), strconv.Itoa(bugIssueRequest.Rows), bugIssueRequest.ExceptionType)
|
||||
|
||||
if req, err = d.newRequest("GET", hostStr, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cookie, token, err = d.cookieAndToken(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Cookie", cookie)
|
||||
req.Header.Set("x-token", token)
|
||||
req.Header.Set("content-type", "application/json;charset=utf-8")
|
||||
req.Header.Set("x-csrf-token", "undefined")
|
||||
|
||||
if err = d.httpClient.Do(c, req, &res); err != nil {
|
||||
log.Error("d.BuglyIssue url(%s) err(%v)", "BuglyIssue", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Status != _buglyOkCode {
|
||||
err = ecode.MartheBuglyErr
|
||||
log.Error("Status url(%s) res(%v) err(%v)", "BuglyIssue", res, err)
|
||||
log.Error("maybe need to update cookie and token")
|
||||
return
|
||||
}
|
||||
|
||||
ret = res.Ret
|
||||
return
|
||||
}
|
||||
|
||||
// BuglyIssueDetailAndRetry Bugly Issue Detail And Retry.
|
||||
func (d *Dao) BuglyIssueDetailAndRetry(c context.Context, projectID, platformID, issueNo string) (bugIssueDetail *model.BugIssueDetail, err error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
if bugIssueDetail, err = d.BuglyIssueDetail(c, projectID, platformID, issueNo); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BuglyIssueDetail Get Issue Detail.
|
||||
func (d *Dao) BuglyIssueDetail(c context.Context, projectID, platformID, issueNo string) (bugIssueDetail *model.BugIssueDetail, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res *model.BugIssueDetailResponse
|
||||
cookie string
|
||||
token string
|
||||
hostStr string
|
||||
)
|
||||
|
||||
hostStr = d.c.Bugly.Host + fmt.Sprintf(_issueDetailList, projectID, platformID, issueNo)
|
||||
|
||||
if req, err = d.newRequest("GET", hostStr, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cookie, token, err = d.cookieAndToken(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Cookie", cookie)
|
||||
req.Header.Set("x-token", token)
|
||||
req.Header.Set("content-type", "application/json;charset=utf-8")
|
||||
req.Header.Set("x-csrf-token", "undefined")
|
||||
|
||||
if err = d.httpClient.Do(c, req, &res); err != nil {
|
||||
log.Error("d.BuglyIssue url(%s) err(%v)", "BuglyIssue", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Code != _issueDetailCode {
|
||||
err = ecode.MartheBuglyErr
|
||||
log.Error("Status url(%s) res(%v) err(%v)", "BuglyIssue", res, err)
|
||||
return
|
||||
}
|
||||
|
||||
bugIssueDetail = res.Data
|
||||
return
|
||||
}
|
||||
|
||||
// BuglyIssueExceptionList Bugly Issue Exception List.
|
||||
func (d *Dao) BuglyIssueExceptionList(c context.Context, projectID, platformID, issueNo string) (bugIssueException *model.IssueException, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res *model.BugIssueExceptionListResponse
|
||||
cookie string
|
||||
token string
|
||||
hostStr string
|
||||
)
|
||||
|
||||
hostStr = d.c.Bugly.Host + fmt.Sprintf(_issueExceptionList, projectID, platformID, issueNo)
|
||||
|
||||
if req, err = d.newRequest("GET", hostStr, nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cookie, token, err = d.cookieAndToken(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Cookie", cookie)
|
||||
req.Header.Set("x-token", token)
|
||||
req.Header.Set("content-type", "application/json;charset=utf-8")
|
||||
req.Header.Set("x-csrf-token", "undefined")
|
||||
|
||||
if err = d.httpClient.Do(c, req, &res); err != nil {
|
||||
log.Error("d.BuglyIssueExceptionList url(%s) err(%v)", "BuglyIssueExceptionList", err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Status != _buglyOkCode {
|
||||
err = ecode.MartheBuglyErr
|
||||
log.Error("Status url(%s) res(%v) err(%v)", "BuglyIssueExceptionList", res, err)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Ret != nil && len(res.Ret.IssueException) != 0 && res.Ret.IssueException[0].IssueID == issueNo {
|
||||
bugIssueException = res.Ret.IssueException[0]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Dao) cookieAndToken() (cookie, token string, err error) {
|
||||
var (
|
||||
cookieByte []byte
|
||||
tokenByte []byte
|
||||
)
|
||||
if cookieByte, err = ioutil.ReadFile(d.c.Bugly.Cookie); err != nil {
|
||||
return
|
||||
}
|
||||
if tokenByte, err = ioutil.ReadFile(d.c.Bugly.Token); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cookie = string(cookieByte)
|
||||
token = string(tokenByte)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateToken Update Token.
|
||||
func (d *Dao) UpdateToken() (err error) {
|
||||
return d.updateCookieAndToken()
|
||||
}
|
||||
|
||||
func (d *Dao) updateCookieAndToken() (err error) {
|
||||
_, dir, _, _ := runtime.Caller(1)
|
||||
currentPath := strings.Replace(dir, "bugly.go", "", -1)
|
||||
|
||||
cmd := exec.Command("python", "bugly.py")
|
||||
cmd.Dir = currentPath
|
||||
//err = cmd.Run()
|
||||
return
|
||||
}
|
90
app/service/ep/footman/dao/bugly.py
Normal file
90
app/service/ep/footman/dao/bugly.py
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
from browsermobproxy import Server
|
||||
from selenium import webdriver
|
||||
import time
|
||||
import os
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
|
||||
# configuration
|
||||
#browsermobPath = './browsermob-proxy-2.1.4/bin/browsermob-proxy'
|
||||
browsermobPath = 'D:\\fyf\\tool\\browsermob-proxy-2.1.4\\bin\\browsermob-proxy'
|
||||
username = '972360526'
|
||||
password = '61241623FYFzwq'
|
||||
tokenFile = os.getcwd()+'./token.conf'
|
||||
cookiesFile = os.getcwd()+'./cookie.conf'
|
||||
chromedriver = os.getcwd()+"./chromedriver.exe"
|
||||
|
||||
def writeResult(filePath, fileContext):
|
||||
if os.path.exists(filePath):
|
||||
os.remove(filePath)
|
||||
f = open(filePath, 'w')
|
||||
f.write(fileContext)
|
||||
print(fileContext)
|
||||
f.close()
|
||||
return
|
||||
|
||||
def GetCookieAndToken():
|
||||
server = Server(browsermobPath)
|
||||
server.start()
|
||||
proxy = server.create_proxy()
|
||||
profile = webdriver.FirefoxProfile()
|
||||
profile.set_proxy(proxy.selenium_proxy())
|
||||
|
||||
chrome_options = Options()
|
||||
|
||||
chrome_options.add_argument('--ignore-certificate-errors')
|
||||
chrome_options.add_argument('--proxy-server={0}'.format(proxy.proxy))
|
||||
|
||||
os.environ["webdriver.chrome.driver"] = chromedriver
|
||||
|
||||
driver = webdriver.Chrome(chromedriver,chrome_options=chrome_options)
|
||||
|
||||
#driver = webdriver.PhantomJS(firefox_profile=profile,executable_path = geckodriverPah)
|
||||
|
||||
proxy.new_har("bugly", options={"captureHeaders":True})
|
||||
|
||||
driver.get("https://bugly.qq.com/v2/")
|
||||
time.sleep(3)
|
||||
driver.find_element_by_class_name("login_btn").click()
|
||||
time.sleep(3)
|
||||
driver.switch_to.frame("ptlogin_iframe")
|
||||
time.sleep(3)
|
||||
driver.find_element_by_id("switcher_plogin").click()
|
||||
time.sleep(3)
|
||||
driver.find_element_by_id("u").send_keys(username)
|
||||
time.sleep(3)
|
||||
driver.find_element_by_id("p").clear()
|
||||
driver.find_element_by_id("p").send_keys(password)
|
||||
time.sleep(3)
|
||||
driver.find_element_by_id("login_button").click()
|
||||
time.sleep(10)
|
||||
driver.find_element_by_xpath('//*[@id="root"]/div/div/div[2]/div/div/div/div[2]/table/tbody/tr/td[1]/div/div[1]/img').click()
|
||||
time.sleep(3)
|
||||
driver.find_element_by_xpath('//*[@id="root"]/div/div/div[2]/div/div[1]/div[2]/ul[2]/li/a').click()
|
||||
time.sleep(10)
|
||||
strCookies = ""
|
||||
strToken = ""
|
||||
cookies = driver.get_cookies()
|
||||
requestDict = proxy.har['log']['entries']
|
||||
|
||||
for index in range(len(requestDict)):
|
||||
for k in requestDict[index]:
|
||||
if k == "request" and requestDict[index][k]['url'].find('v2/issueList')>=0:
|
||||
for inn in range(len(requestDict[index][k]['headers'])):
|
||||
for ik in requestDict[index][k]['headers'][inn]:
|
||||
if ik == 'name' and requestDict[index][k]['headers'][inn][ik]=='X-token':
|
||||
strToken = requestDict[index][k]['headers'][inn]['value']
|
||||
if ik == 'name' and requestDict[index][k]['headers'][inn][ik]=='Cookie' and requestDict[index][k]['headers'][inn]['value'].find('pt2gguin')>=0 and requestDict[index][k]['headers'][inn]['value'].find('bugly_session')>=0 and requestDict[index][k]['headers'][inn]['value'].find('referrer')>=0:
|
||||
strCookies = requestDict[index][k]['headers'][inn]['value']
|
||||
if strToken!="" and strCookies!="":
|
||||
break;
|
||||
|
||||
|
||||
writeResult(tokenFile,strToken)
|
||||
writeResult(cookiesFile,strCookies)
|
||||
|
||||
server.stop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
GetCookieAndToken()
|
||||
|
1
app/service/ep/footman/dao/cookie.conf
Normal file
1
app/service/ep/footman/dao/cookie.conf
Normal file
@ -0,0 +1 @@
|
||||
pgv_pvi=8114248704; eas_sid=a1j5J203m9A4x1j307E3b0u4A8; pgv_pvid=352985328; ptui_loginuin=972360526; pt2gguin=o0972360526; RK=keyEJkBQW6; ptcz=0f939604b4f1205249c5d28ea26750e9a6077b664d2a54865f6e81dac9e08c90; o_cookie=972360526; pac_uid=1_972360526; __v3_c_review_10643=9; __v3_c_last_10643=1532482927091; __v3_c_visitor=1524887184864119; tvfe_boss_uuid=5a38ed3d664c09a7; __v3_c_uactiveat_10643=1528422214233; LW_sid=X1W5L3o1q7o971s4E7o0F4k6S8; LW_uid=i1Q5A3H1A7D9P1Y4d7t0z4g7g0; _ga=GA1.2.1508917390.1532664512; btcu_id=ed70f24fdc9235e8bc045c4c55a836755b67a9efb455d; _gid=GA1.2.657757944.1534127484; _qpsvr_localtk=0.0342640573507188; pgv_si=s2024529920; ptisp=ctc; pgv_info=pgvReferrer=&ssid=s3519213288; bugly_session=eyJpdiI6IjZQOUVaWVlnQlwvc2pzckUzSnI0cEVnPT0iLCJ2YWx1ZSI6Ikl3d0hueHA1ZzJIZWJHVFFTcU8wa1BadURrSDlad2dXS21UZDdDZkJrXC9ZdWpcLzd0TWFzenYzeG5GbzczbG9pdUcwZ2lrYnI4Uzkyek41clBhbVBKYkE9PSIsIm1hYyI6ImVlMDFhYWQ4MDQxM2U2ZTM5MDMyYmZlNTY1NmM5YmUzMmRlYzE1MDhhOWI3YTViZWYwZGI3NmJlMWQyNGViOGYifQ%3D%3D; token-skey=6eb6b613-efab-a07d-abcb-771b70cad38f; token-lifeTime=1534225952; referrer=eyJpdiI6IjJDUTNGbWROa1M3dWZRekUrK21TWHc9PSIsInZhbHVlIjoibWhQc3M4UWRiOHV5ZU1nZTlUd0JDRnpxdUQxK25kRm8rWXdGNmdBSWhvakhOb21aUWgzZFJKbXVQZm1pK3BTa1NLMzVrczBBZ214NGFSaVZhQitGN000YmlPUFR3T20zSmlPbHVZcVJoUmhBK2RrbytCQUlOcDdKM2J1ZVNvYnI0WkJHUXpUckRCXC9jRFRUODkybzRFalhGXC9uWnU0YUZLaFV6M0FcL3BNN3BvPSIsIm1hYyI6IjEzZmFlMWM3M2Y4MDVmOTJhYzk3YzkzZmIzMWI1NTZkMzQ1Y2I5ZmYwYWY0YjUzMDRmZjQxMzk0MWQ1NjZmOGUifQ%3D%3D; _gat=1; uin=o0972360526; skey=@jikR9kwCF
|
98
app/service/ep/footman/dao/dao.go
Normal file
98
app/service/ep/footman/dao/dao.go
Normal file
@ -0,0 +1,98 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go-common/app/service/ep/footman/conf"
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/database/orm"
|
||||
"go-common/library/log"
|
||||
xhttp "go-common/library/net/http/blademaster"
|
||||
"go-common/library/sync/pipeline/fanout"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
// Dao dao
|
||||
type Dao struct {
|
||||
c *conf.Config
|
||||
httpClient *xhttp.Client
|
||||
email *gomail.Dialer
|
||||
db *gorm.DB
|
||||
cache *fanout.Fanout
|
||||
mc *memcache.Pool
|
||||
expire int32
|
||||
}
|
||||
|
||||
// New init mysql db
|
||||
func New(c *conf.Config) (dao *Dao) {
|
||||
dao = &Dao{
|
||||
c: c,
|
||||
//email: gomail.NewDialer(c.Mail.Host, c.Mail.Port, c.Mail.Username, c.Mail.Password),
|
||||
httpClient: xhttp.NewClient(c.HTTPClient),
|
||||
cache: fanout.New("mcCache", fanout.Worker(1), fanout.Buffer(1024)),
|
||||
mc: memcache.NewPool(c.Memcache.Config),
|
||||
expire: int32(time.Duration(c.Memcache.Expire) / time.Second),
|
||||
}
|
||||
if c.ORM != nil {
|
||||
dao.db = orm.NewMySQL(c.ORM)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close close the resource.
|
||||
func (d *Dao) Close() {
|
||||
if d.db != nil {
|
||||
d.db.Close()
|
||||
}
|
||||
|
||||
if d.mc != nil {
|
||||
d.mc.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Ping verify server is ok.
|
||||
func (d *Dao) Ping(c context.Context) (err error) {
|
||||
if d.db != nil {
|
||||
if err = d.db.DB().Ping(); err != nil {
|
||||
log.Info("dao.cloudDB.Ping() error(%v)", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Dao) newRequest(method, url string, v interface{}) (req *http.Request, err error) {
|
||||
body := &bytes.Buffer{}
|
||||
if method != http.MethodGet {
|
||||
if err = json.NewEncoder(body).Encode(v); err != nil {
|
||||
log.Error("json encode value(%s) err(%v) ", v, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if req, err = http.NewRequest(method, url, body); err != nil {
|
||||
log.Error("http new request url(%s) err(%v)", url, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cacheSave cache Save.
|
||||
func (d *Dao) cacheSave(c context.Context, cacheItem *memcache.Item) {
|
||||
var f = func(ctx context.Context) {
|
||||
var (
|
||||
conn = d.mc.Get(c)
|
||||
err error
|
||||
)
|
||||
defer conn.Close()
|
||||
if err = conn.Set(cacheItem); err != nil {
|
||||
log.Error("Add Cache conn.Set(%s) error(%v)", cacheItem.Key, err)
|
||||
}
|
||||
}
|
||||
if err := d.cache.Do(c, f); err != nil {
|
||||
log.Error("ReleaseName cache save err(%v)", err)
|
||||
}
|
||||
}
|
15
app/service/ep/footman/dao/mail.go
Normal file
15
app/service/ep/footman/dao/mail.go
Normal file
@ -0,0 +1,15 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
// SendMail asynchronous send mail.
|
||||
func (d *Dao) SendMail(message *gomail.Message) {
|
||||
message.SetAddressHeader("From", d.email.Username, "footman")
|
||||
d.cache.Do(context.TODO(), func(ctx context.Context) {
|
||||
d.email.DialAndSend(message)
|
||||
})
|
||||
}
|
12
app/service/ep/footman/dao/mysql_bug_template.go
Normal file
12
app/service/ep/footman/dao/mysql_bug_template.go
Normal file
@ -0,0 +1,12 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"go-common/app/service/ep/footman/model"
|
||||
)
|
||||
|
||||
// FindBugTemplates Find Bug Templates.
|
||||
func (d *Dao) FindBugTemplates(projectID string) (bugTemplate *model.BugTemplate, err error) {
|
||||
bugTemplate = &model.BugTemplate{}
|
||||
err = d.db.Where("project_id = ?", projectID).First(bugTemplate).Error
|
||||
return
|
||||
}
|
103
app/service/ep/footman/dao/mysql_bugly.go
Normal file
103
app/service/ep/footman/dao/mysql_bugly.go
Normal file
@ -0,0 +1,103 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/ecode"
|
||||
"go-common/library/log"
|
||||
|
||||
pkgerr "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
_issueRecords = "issue_records"
|
||||
_issueLastTimes = "issue_last_times"
|
||||
)
|
||||
|
||||
// UpdateIssueRecord Update Issue Record.
|
||||
func (d *Dao) UpdateIssueRecord(issueRecord *model.IssueRecord) (err error) {
|
||||
err = d.db.Table(_issueRecords).Where("issue_no = ? and version = ?", issueRecord.IssueNo, issueRecord.Version).UpdateColumn(map[string]interface{}{
|
||||
"last_time": issueRecord.LastTime,
|
||||
"happen_times": issueRecord.HappenTimes,
|
||||
"user_times": issueRecord.UserTimes,
|
||||
}).Error
|
||||
log.Info("update issue record: %s", issueRecord.IssueNo)
|
||||
fmt.Print("update issue record: " + issueRecord.IssueNo)
|
||||
return
|
||||
}
|
||||
|
||||
// InsertIssueRecord Insert Issue Record.
|
||||
func (d *Dao) InsertIssueRecord(issueRecord *model.IssueRecord) (err error) {
|
||||
err = d.db.Table(_issueRecords).Create(issueRecord).Error
|
||||
log.Info("insert issue record: %s", issueRecord.IssueNo)
|
||||
fmt.Println("insert issue record: " + issueRecord.IssueNo)
|
||||
return
|
||||
}
|
||||
|
||||
// GetIssueLastTime Get Issue LastTime.
|
||||
func (d *Dao) GetIssueLastTime(version string) (issueLastTime *model.IssueLastTime, err error) {
|
||||
issueLastTime = &model.IssueLastTime{}
|
||||
if err = d.db.Where("version = ?", version).First(issueLastTime).Error; err == ecode.NothingFound {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateTaskStatus Update Task Status.
|
||||
func (d *Dao) UpdateTaskStatus(issueLastTime *model.IssueLastTime) (err error) {
|
||||
err = pkgerr.WithStack(d.db.Table(_issueLastTimes).Where("version=?", issueLastTime.Version).Update("task_status", issueLastTime.TaskStatus).Error)
|
||||
return
|
||||
}
|
||||
|
||||
// GetIssueRecord Get Issue Record.
|
||||
func (d *Dao) GetIssueRecord(issueNo, version string) (issueRecord *model.IssueRecord, err error) {
|
||||
issueRecord = &model.IssueRecord{}
|
||||
if err = d.db.Where("issue_no = ? and version = ?", issueNo, version).First(issueRecord).Error; err == ecode.NothingFound {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetIssueRecordNotInTapd Get Issue Record Not in tapd.
|
||||
func (d *Dao) GetIssueRecordNotInTapd(issueFilterSQL string) (issueRecords []*model.IssueRecord, err error) {
|
||||
if err = d.db.Raw(issueFilterSQL).Order("id asc").Find(&issueRecords).Error; err == ecode.NothingFound {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetIssueRecordHasInTapd Get Issue Record in tapd.
|
||||
func (d *Dao) GetIssueRecordHasInTapd(projectID string) (issueRecords []*model.IssueRecord, err error) {
|
||||
if err = d.db.Table(_issueRecords).Where("project_id = ? and tapd_bug_id<>''", projectID).Find(&issueRecords).Error; err == ecode.NothingFound {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateIssueRecordTapdBugID Update Issue Record Tapd Bug ID.
|
||||
func (d *Dao) UpdateIssueRecordTapdBugID(id int64, tapdBugID string) (err error) {
|
||||
return d.db.Table(_issueRecords).Where("id=?", id).Update("tapd_bug_id", tapdBugID).Error
|
||||
}
|
||||
|
||||
// UpdateLastIssueTime Update Last Issue Time.
|
||||
func (d *Dao) UpdateLastIssueTime(issueLastTime *model.IssueLastTime) (err error) {
|
||||
return d.db.Table(_issueLastTimes).Where("version=?", issueLastTime.Version).Update("last_time", issueLastTime.LastTime).Error
|
||||
}
|
||||
|
||||
// InsertIssueLastTime Insert Issue Last Time.
|
||||
func (d *Dao) InsertIssueLastTime(issueLastTime *model.IssueLastTime) (err error) {
|
||||
return d.db.Table(_issueLastTimes).Create(issueLastTime).Error
|
||||
}
|
||||
|
||||
// UpdateLastIssue Update Last Issue.
|
||||
func (d *Dao) UpdateLastIssue(issueLastTime *model.IssueLastTime) (err error) {
|
||||
err = pkgerr.WithStack(d.db.Table(_issueLastTimes).Where("version=?", issueLastTime.Version).Update("last_issue", issueLastTime.LastIssue).Error)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateVersionRecord Update Version Record.
|
||||
func (d *Dao) UpdateVersionRecord(issueLastTime *model.IssueLastTime) (err error) {
|
||||
err = pkgerr.WithStack(d.db.Table(_issueLastTimes).Where("version=?", issueLastTime.Version).Updates(issueLastTime).Error)
|
||||
return
|
||||
}
|
407
app/service/ep/footman/dao/tapd.go
Normal file
407
app/service/ep/footman/dao/tapd.go
Normal file
@ -0,0 +1,407 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/cache/memcache"
|
||||
"go-common/library/ecode"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
const (
|
||||
_userName = "bilibilinik"
|
||||
_password = "0989D4F0-AF9D-949F-C950-E22A3F891NIK"
|
||||
_startPN = 1
|
||||
_successCode = 1
|
||||
)
|
||||
|
||||
//Iteration fetch iterations
|
||||
func (d *Dao) Iteration(iterationURL string) (res *model.IterationResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.IterationResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, iterationURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//Story fetch stories
|
||||
func (d *Dao) Story(storyURL string) (res *model.StoryResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.StoryResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, storyURL, nil); err != nil {
|
||||
return nil, ecode.MartheTapdErr
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//Bug Bug
|
||||
func (d *Dao) Bug(bugURL string) (res *model.BugResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.BugResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, bugURL, nil); err != nil {
|
||||
return nil, ecode.MartheTapdErr
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//BugPre Bug Pre
|
||||
func (d *Dao) BugPre(workSpaceID, bugID string) (bug *model.Bug, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res = &model.BugSingleResponse{}
|
||||
)
|
||||
|
||||
url := fmt.Sprintf(model.BugPreURL, workSpaceID, bugID)
|
||||
if req, err = d.newTapdRequest(http.MethodGet, url, nil); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
if err = d.httpClient.Do(context.TODO(), req, res); err != nil || res.Data == nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
bug = res.Data.Bug
|
||||
return
|
||||
}
|
||||
|
||||
//SpecStory fetch specific story
|
||||
func (d *Dao) SpecStory(storyURL string) (res *model.SpecStoryResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.SpecStoryResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, storyURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//StoryChange fetch story changes
|
||||
func (d *Dao) StoryChange(storyChangeURL string) (res *model.StoryChangeResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.StoryChangeResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, storyChangeURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//NameMap fetch story status name mapping
|
||||
func (d *Dao) NameMap(nameMapURL string) (res *model.NameMapResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.NameMapResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, nameMapURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//Category fetch project category
|
||||
func (d *Dao) Category(categoryURL string) (res *model.CategoryResponse, err error) {
|
||||
var req *http.Request
|
||||
res = &model.CategoryResponse{}
|
||||
if req, err = d.newTapdRequest(http.MethodGet, categoryURL, nil); err != nil {
|
||||
return
|
||||
}
|
||||
err = d.httpClient.Do(context.TODO(), req, res)
|
||||
return
|
||||
}
|
||||
|
||||
//AllIterations get all iterations by query url
|
||||
func (d *Dao) AllIterations(ps int, iterationURL string) (res *model.IterationResponse, err error) {
|
||||
var (
|
||||
tempRes *model.IterationResponse
|
||||
iterationPage = _startPN
|
||||
tempPS = ps
|
||||
currentTime int
|
||||
)
|
||||
url := fmt.Sprintf(iterationURL, iterationPage)
|
||||
for tempPS == ps && currentTime < d.c.Tapd.RetryTime {
|
||||
if tempRes, err = d.Iteration(url); err != nil || len(tempRes.Data) == 0 {
|
||||
currentTime = currentTime + 1
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
if res == nil {
|
||||
res = tempRes
|
||||
} else if len(tempRes.Data) > 0 {
|
||||
res.Data = append(res.Data, tempRes.Data...)
|
||||
}
|
||||
tempPS = len(tempRes.Data)
|
||||
iterationPage++
|
||||
url = fmt.Sprintf(iterationURL, iterationPage)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//AllStories get all stories by query url
|
||||
func (d *Dao) AllStories(ps int, storyURL string) (res *model.StoryResponse, err error) {
|
||||
var (
|
||||
tempRes *model.StoryResponse
|
||||
storyPage = _startPN
|
||||
tempPS = ps
|
||||
currentTime int
|
||||
)
|
||||
url := fmt.Sprintf(storyURL, storyPage)
|
||||
for tempPS == ps && currentTime < d.c.Tapd.RetryTime {
|
||||
if tempRes, err = d.Story(url); err != nil || len(tempRes.Data) == 0 {
|
||||
currentTime = currentTime + 1
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
if res == nil {
|
||||
res = tempRes
|
||||
} else if len(tempRes.Data) > 0 {
|
||||
res.Data = append(res.Data, tempRes.Data...)
|
||||
}
|
||||
tempPS = len(tempRes.Data)
|
||||
storyPage++
|
||||
url = fmt.Sprintf(storyURL, storyPage)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//AllStoryChanges get all story changes by query url
|
||||
func (d *Dao) AllStoryChanges(ps int, storyChangeURL string) (res *model.StoryChangeResponse, err error) {
|
||||
var (
|
||||
tempRes *model.StoryChangeResponse
|
||||
storyChangePage = _startPN
|
||||
tempPS = ps
|
||||
currentTime int
|
||||
)
|
||||
url := fmt.Sprintf(storyChangeURL, storyChangePage)
|
||||
for tempPS == ps && currentTime < d.c.Tapd.RetryTime {
|
||||
if tempRes, err = d.StoryChange(url); err != nil || len(tempRes.Data) == 0 {
|
||||
currentTime = currentTime + 1
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
if res == nil {
|
||||
res = tempRes
|
||||
} else if len(tempRes.Data) > 0 {
|
||||
res.Data = append(res.Data, tempRes.Data...)
|
||||
}
|
||||
tempPS = len(tempRes.Data)
|
||||
storyChangePage++
|
||||
url = fmt.Sprintf(storyChangeURL, storyChangePage)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//AllCategories get all categories of project
|
||||
func (d *Dao) AllCategories(ps int, categoryURL string) (categoryMap map[string]string, err error) {
|
||||
var (
|
||||
tempRes, res *model.CategoryResponse
|
||||
categoryPage = _startPN
|
||||
tempPS = ps
|
||||
currentTime int
|
||||
)
|
||||
url := fmt.Sprintf(categoryURL, categoryPage)
|
||||
for tempPS == ps && currentTime < d.c.Tapd.RetryTime {
|
||||
if tempRes, err = d.Category(url); err != nil {
|
||||
currentTime = currentTime + 1
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
if res == nil {
|
||||
res = tempRes
|
||||
} else if len(tempRes.Data) > 0 {
|
||||
res.Data = append(res.Data, tempRes.Data...)
|
||||
}
|
||||
tempPS = len(tempRes.Data)
|
||||
categoryPage++
|
||||
url = fmt.Sprintf(categoryURL, categoryPage)
|
||||
}
|
||||
|
||||
categoryMap = make(map[string]string)
|
||||
for _, category := range res.Data {
|
||||
categoryMap[category.Category.ID] = category.Category.Name
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//AllBugs All Bugs
|
||||
func (d *Dao) AllBugs(ps int, bugURL string) (res *model.BugResponse, err error) {
|
||||
var (
|
||||
tempRes *model.BugResponse
|
||||
bugPage = _startPN
|
||||
tempPS = ps
|
||||
currentTime int
|
||||
)
|
||||
url := fmt.Sprintf(bugURL, bugPage)
|
||||
for tempPS == ps && currentTime < d.c.Tapd.RetryTime {
|
||||
if tempRes, err = d.Bug(url); err != nil || len(tempRes.Data) == 0 {
|
||||
currentTime = currentTime + 1
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
if res == nil {
|
||||
res = tempRes
|
||||
} else if len(tempRes.Data) > 0 {
|
||||
res.Data = append(res.Data, tempRes.Data...)
|
||||
}
|
||||
tempPS = len(tempRes.Data)
|
||||
bugPage++
|
||||
url = fmt.Sprintf(bugURL, bugPage)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//CategoryPre Category Pre
|
||||
func (d *Dao) CategoryPre(workSpaceID, releaseID string) (category *model.Category, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res = &model.CategoryPreResponse{}
|
||||
)
|
||||
|
||||
releaseURL := fmt.Sprintf(model.CategoryPreURL, workSpaceID, releaseID)
|
||||
if req, err = d.newTapdRequest(http.MethodGet, releaseURL, nil); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
if err = d.httpClient.Do(context.TODO(), req, res); err != nil || res.Data == nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
category = res.Data.Category
|
||||
return
|
||||
}
|
||||
|
||||
// CategoryPreName Category PreName
|
||||
func (d *Dao) CategoryPreName(workspaceID, categoryID string) (categoryPreName string, err error) {
|
||||
var (
|
||||
item *memcache.Item
|
||||
conn = d.mc.Get(context.Background())
|
||||
category *model.Category
|
||||
)
|
||||
defer conn.Close()
|
||||
|
||||
if item, err = conn.Get(workspaceID + categoryID); err == nil {
|
||||
if err = json.Unmarshal(item.Value, &categoryPreName); err != nil {
|
||||
log.Error("Json unmarshal err(%v)", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if category, err = d.CategoryPre(workspaceID, categoryID); err != nil {
|
||||
return
|
||||
}
|
||||
categoryPreName = category.Name
|
||||
item = &memcache.Item{Key: workspaceID + categoryID, Object: category.Name, Flags: memcache.FlagJSON, Expiration: d.expire}
|
||||
d.cacheSave(context.Background(), item)
|
||||
return
|
||||
}
|
||||
|
||||
//Release Release
|
||||
func (d *Dao) Release(workSpaceID, releaseID string) (release *model.Release, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res = &model.ReleaseResponse{}
|
||||
)
|
||||
|
||||
releaseURL := fmt.Sprintf(model.ReleaseURL, workSpaceID, releaseID)
|
||||
if req, err = d.newTapdRequest(http.MethodGet, releaseURL, nil); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
if err = d.httpClient.Do(context.TODO(), req, res); err != nil || res.Data == nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
release = res.Data.Release
|
||||
return
|
||||
}
|
||||
|
||||
// ReleaseName ReleaseName
|
||||
func (d *Dao) ReleaseName(workspaceID, releaseID string) (releaseName string, err error) {
|
||||
var (
|
||||
item *memcache.Item
|
||||
conn = d.mc.Get(context.Background())
|
||||
release *model.Release
|
||||
)
|
||||
defer conn.Close()
|
||||
|
||||
if item, err = conn.Get(workspaceID + releaseID); err == nil {
|
||||
if err = json.Unmarshal(item.Value, &releaseName); err != nil {
|
||||
log.Error("Json unmarshal err(%v)", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if release, err = d.Release(workspaceID, releaseID); err != nil {
|
||||
return
|
||||
}
|
||||
releaseName = release.Name
|
||||
item = &memcache.Item{Key: workspaceID + releaseID, Object: release.Name, Flags: memcache.FlagJSON, Expiration: d.expire}
|
||||
d.cacheSave(context.Background(), item)
|
||||
return
|
||||
}
|
||||
|
||||
//newTapdRequest new tapd request
|
||||
func (d *Dao) newTapdRequest(method, url string, v interface{}) (req *http.Request, err error) {
|
||||
req, err = d.newRequest(method, url, v)
|
||||
req.SetBasicAuth(_userName, _password)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateBug Create Bug.
|
||||
func (d *Dao) CreateBug(bug *model.Bug) (bugID string, err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res = &model.BugSingleResponse{}
|
||||
)
|
||||
|
||||
if req, err = d.newTapdRequest(http.MethodPost, model.CreateBugURL, bug); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.httpClient.Do(context.TODO(), req, res); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
if res.Status != _successCode {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
bugID = res.Data.Bug.ID
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateBug Update Bug.
|
||||
func (d *Dao) UpdateBug(bug *model.UpdateBug) (err error) {
|
||||
var (
|
||||
req *http.Request
|
||||
res = &model.BugSingleResponse{}
|
||||
)
|
||||
|
||||
if req, err = d.newTapdRequest(http.MethodPost, model.CreateBugURL, bug); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
if err = d.httpClient.Do(context.TODO(), req, res); err != nil {
|
||||
err = ecode.MartheTapdErr
|
||||
return
|
||||
}
|
||||
|
||||
if res.Status != _successCode {
|
||||
err = ecode.MartheTapdErr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
1
app/service/ep/footman/dao/token.conf
Normal file
1
app/service/ep/footman/dao/token.conf
Normal file
@ -0,0 +1 @@
|
||||
2106504811
|
33
app/service/ep/footman/model/BUILD
Normal file
33
app/service/ep/footman/model/BUILD
Normal file
@ -0,0 +1,33 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bugly.go",
|
||||
"constants.go",
|
||||
"model.go",
|
||||
"tapd.go",
|
||||
],
|
||||
importpath = "go-common/app/service/ep/footman/model",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
87
app/service/ep/footman/model/bugly.go
Normal file
87
app/service/ep/footman/model/bugly.go
Normal file
@ -0,0 +1,87 @@
|
||||
package model
|
||||
|
||||
// BugIssueRequest Bug Issue Request.
|
||||
type BugIssueRequest struct {
|
||||
StartNum int
|
||||
Version string
|
||||
Rows int
|
||||
PlatformID string
|
||||
ProjectID string
|
||||
ExceptionType string
|
||||
}
|
||||
|
||||
// BugIssueResponse Bug Issue Response.
|
||||
type BugIssueResponse struct {
|
||||
Status int `json:"status"`
|
||||
Ret *BugRet `json:"ret"`
|
||||
}
|
||||
|
||||
// BugRet Bug Ret.
|
||||
type BugRet struct {
|
||||
NumFound int `json:"numFound"`
|
||||
BugIssues []*BugIssues `json:"issueList"`
|
||||
}
|
||||
|
||||
// BugIssues Bug Issues.
|
||||
type BugIssues struct {
|
||||
IssueID string `json:"issueId"`
|
||||
Title string `json:"exceptionName"`
|
||||
ExceptionMsg string `json:"exceptionMessage"`
|
||||
KeyStack string `json:"keyStack"`
|
||||
LastTime string `json:"lastestUploadTime"`
|
||||
Count int64 `json:"count"`
|
||||
Tags []*BugTag `json:"tagInfoList"`
|
||||
UserCount int64 `json:"imeiCount"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// BugTag Bug Tag.
|
||||
type BugTag struct {
|
||||
TagName string `json:"tagName"`
|
||||
}
|
||||
|
||||
// BugIssueDetailResponse Bug Issue Detail Response.
|
||||
type BugIssueDetailResponse struct {
|
||||
Code int `json:"code"`
|
||||
Data *BugIssueDetail `json:"data"`
|
||||
}
|
||||
|
||||
// BugIssueDetail Bug Issue Detail.
|
||||
type BugIssueDetail struct {
|
||||
CallStack string `json:"callStack"`
|
||||
}
|
||||
|
||||
// BugVersionResponse Bug Version Response.
|
||||
type BugVersionResponse struct {
|
||||
Status int `json:"status"`
|
||||
Ret *SelectorPropertyList `json:"ret"`
|
||||
}
|
||||
|
||||
// SelectorPropertyList SelectorPropertyList.
|
||||
type SelectorPropertyList struct {
|
||||
BugVersionList []*BugVersion `json:"versionList"`
|
||||
}
|
||||
|
||||
// BugVersion BugVersion.
|
||||
type BugVersion struct {
|
||||
Name string `json:"name"`
|
||||
Enable int `json:"enable"`
|
||||
SDKVersion string `json:"sdkVersion"`
|
||||
}
|
||||
|
||||
// BugIssueExceptionListResponse Bug Issue Exception List Response.
|
||||
type BugIssueExceptionListResponse struct {
|
||||
Status int `json:"status"`
|
||||
Ret *IssueExceptionList `json:"ret"`
|
||||
}
|
||||
|
||||
// IssueExceptionList IssueExceptionList.
|
||||
type IssueExceptionList struct {
|
||||
IssueException []*IssueException `json:"issueList"`
|
||||
}
|
||||
|
||||
// IssueException IssueException.
|
||||
type IssueException struct {
|
||||
IssueID string `json:"issueId"`
|
||||
Status int `json:"status"`
|
||||
}
|
61
app/service/ep/footman/model/constants.go
Normal file
61
app/service/ep/footman/model/constants.go
Normal file
@ -0,0 +1,61 @@
|
||||
package model
|
||||
|
||||
//tapd urls
|
||||
const (
|
||||
IterationURL = "https://api.tapd.cn/iterations?workspace_id=%s&name=%s&limit=%d&fields=id,name,startdate,enddate"
|
||||
AllIterationURL = "https://api.tapd.cn/iterations?workspace_id=%s&limit=%d"
|
||||
StoryURL = "https://api.tapd.cn/stories?workspace_id=%s&iteration_id=%s&limit=%d"
|
||||
StoryURLWithoutItera = "https://api.tapd.cn/stories?workspace_id=%s&limit=%d"
|
||||
SpecStoryURL = "https://api.tapd.cn/stories?workspace_id=%s&id=%s"
|
||||
StoryChangeURL = "https://api.tapd.cn/story_changes?workspace_id=%s&story_id=%s&limit=%d"
|
||||
NameMapURL = "https://api.tapd.cn/workflows/status_map?system=story&workspace_id=%s"
|
||||
CategoryURL = "https://api.tapd.cn/story_categories?workspace_id=%s&fields=id,name&limit=%d"
|
||||
BugURL = "https://api.tapd.cn/bugs?workspace_id=%s&limit=%d"
|
||||
ReleaseURL = "https://api.tapd.cn/releases?workspace_id=%s&id=%s"
|
||||
CategoryPreURL = "https://api.tapd.cn/story_categories?workspace_id=%s&id=%s"
|
||||
BugPreURL = "https://api.tapd.cn/bugs?workspace_id=%s&id=%s"
|
||||
CreateBugURL = "https://api.tapd.cn/bugs"
|
||||
)
|
||||
|
||||
//page size
|
||||
const (
|
||||
IPS = 30
|
||||
SPS = 30
|
||||
SCPS = 50
|
||||
CPS = 50
|
||||
)
|
||||
|
||||
//ios and and android info
|
||||
var (
|
||||
IOSRelease = map[string]string{"1120055921001001566": "粉 - 5.22", "1120055921001001673": "粉 - 5.23", "1120055921001001766": "粉 - 5.24", "1120055921001001856": "粉 - 5.25", "1120055921001001930": "粉 - 5.26", "1120055921001002000": "粉 - 5.27", "1120055921001002073": "粉 - 5.28", "1120055921001002147": "粉 - 5.29", "1120055921001002213": "粉 - 5.30", "1120055921001002234": "粉 - 5.31"}
|
||||
AndroidRelease = map[string]string{"1120060791001001567": "Android 5.22", "1120060791001001672": "Android 5.23", "1120060791001001731": "Android 5.24", "1120060791001001855": "Android 5.25", "1120060791001001931": "Android 5.26", "1120060791001001999": "Android 5.27", "1120060791001002072": "Android 5.28", "1120060791001002158": "Android 5.29", "1120060791001002212": "Android 5.30", "1120060791001002268": "Android 5.31"}
|
||||
IOSWorkflow = []string{"规划中(研发不受理此状态!)", "需求评审", "需求评审未通过", "确定发布计划", "待开发", "开发中", "产品设计验收", "验收通过待测试", "测试中", "验收通过待测试(接入需求)", "测试通过待合入", "已合入总包", "需求完成", "需求挂起", "需求取消", "免测发布"}
|
||||
AndroidWorkflow = []string{"规划中(研发不受理此状态!)", "需求评审", "需求评审未通过", "确定发布计划", "待开发", "开发中", "产品设计验收", "验收通过待测试", "测试中", "验收通过待测试(接入需求)", "测试通过", "待合入总包", "需求完成", "需求挂起", "需求取消", "免测发布"}
|
||||
BaseFields = map[string]string{"id": "ID", "status": "状态", "priority": "优先级", "size": "规模", "category_id": "需求分类", "parent_id": "父需求", "release_id": "发布计划", "owner": "处理人", "developer": "开发人员", "creator": "创建人", "begin": "预计开始", "due": "预计结束", "created": "创建时间", "completed": "完成时间", "effort": "预估工时"}
|
||||
BaseFieldsList = []string{"ID", "状态", "优先级", "规模", "需求分类", "父需求", "发布计划", "处理人", "开发人员", "创建人", "预计开始", "预计结束", "创建时间", "完成时间", "预估工时"}
|
||||
IOSFields = map[string]string{"custom_field_99": "接口上线日", "custom_field_97": "双端都提得需求", "custom_field_93": "端范围(默认仅粉iPhone)", "custom_field_92": "是否可以单端上线"}
|
||||
IOSFieldsList = []string{"接口上线日", "双端都提得需求", "端范围(默认仅粉iPhone)", "是否可以单端上线"}
|
||||
AndroidFields = map[string]string{"custom_field_99": "接口上线日", "custom_field_97": "双端都提得需求", "custom_field_93": "是否可以单端上线"}
|
||||
AndroidFieldsList = []string{"接口上线日", "双端都提得需求", "是否可以单端上线"}
|
||||
LiveWorkflow = []string{"需求规划中(未受理需求)", "需求文档评审", "待排期", "已排期", "开发中", "开发完成待体验", "验收通过待测试", "测试中", "测试通过", "产品设计验收通过", "需求挂起", "需求取消", "免测发布", "已实现"}
|
||||
BPlusWorkflow = []string{"需求中", "已评审", "开发中", "产品/设计体验", "转测试", "测试中", "测试完成", "已实现", "已拒绝"}
|
||||
|
||||
AndroidStoryWallFields = []string{"auditing", "status_2", "status_11", "developing", "product_experience", "status_5", "testing", "status_6", "status_7", "status_1"}
|
||||
AndroidStoryWallColNames = map[string]string{"auditing": "需求评审", "status_2": "确定发布计划", "status_11": "待开发", "developing": "开发中", "product_experience": "产品设计验收", "status_5": "验收通过待测试", "testing": "测试中", "status_6": "测试通过", "status_7": "待合入总包", "status_1": "需求完成"}
|
||||
IosStoryWallFields = []string{"auditing", "status_2", "status_11", "developing", "product_experience", "status_5", "testing", "status_7", "status_8", "status_1"}
|
||||
IosStoryWallColNames = map[string]string{"auditing": "需求评审", "status_2": "确定发布计划", "status_11": "待开发", "developing": "开发中", "product_experience": "产品设计验收", "status_5": "验收通过待测试", "testing": "测试中", "status_7": "测试通过待合入", "status_8": "已合入总包", "status_1": "需求完成"}
|
||||
)
|
||||
|
||||
//test and reject type
|
||||
var (
|
||||
Test = "test"
|
||||
Experience = "experience"
|
||||
)
|
||||
|
||||
//workspace type
|
||||
var (
|
||||
IOS = "ios"
|
||||
Android = "android"
|
||||
Live = "live"
|
||||
BPlus = "bplus"
|
||||
)
|
62
app/service/ep/footman/model/model.go
Normal file
62
app/service/ep/footman/model/model.go
Normal file
@ -0,0 +1,62 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// IssueRecord Issue Record.
|
||||
type IssueRecord struct {
|
||||
ID int64 `json:"id" gorm:"column:id"`
|
||||
IssueNo string `json:"issue_no" gorm:"column:issue_no"`
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
ExceptionMsg string `json:"exception_msg" gorm:"column:exception_msg"`
|
||||
KeyStack string `json:"key_stack" gorm:"column:key_stack"`
|
||||
Detail string `json:"detail" gorm:"column:detail"`
|
||||
Tags string `json:"tags" gorm:"column:tags"`
|
||||
LastTime time.Time `json:"last_time" gorm:"column:last_time"`
|
||||
HappenTimes int64 `json:"happen_times" gorm:"column:happen_times"`
|
||||
UserTimes int64 `json:"user_times" gorm:"column:user_times"`
|
||||
Version string `json:"version" gorm:"column:version"`
|
||||
ProjectID string `json:"project_id" gorm:"column:project_id"`
|
||||
IssueLink string `json:"issue_link" gorm:"column:issue_link"`
|
||||
TapdBugID string `json:"tapd_bug_id" gorm:"column:tapd_bug_id"`
|
||||
}
|
||||
|
||||
// IssueLastTime Issue Last Time.
|
||||
type IssueLastTime struct {
|
||||
ID int64 `json:"id" gorm:"column:id"`
|
||||
LastTime time.Time `json:"last_time" gorm:"column:last_time"`
|
||||
Version string `json:"version" gorm:"column:version"`
|
||||
//1-正在执行中,0未执行或已执行完
|
||||
TaskStatus int `json:"task_status" gorm:"column:task_status"`
|
||||
LastIssue string `json:"last_issue" gorm:"column:last_issue"`
|
||||
}
|
||||
|
||||
// BugTemplate BugTemplate.
|
||||
type BugTemplate struct {
|
||||
ID int64 `json:"id" gorm:"column:id"`
|
||||
WorkspaceID string `json:"workspace_id" gorm:"column:workspace_id"`
|
||||
ProjectID string `json:"project_id" gorm:"column:project_id"`
|
||||
PlatformID string `json:"platform_id" gorm:"column:platform_id"`
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
Description string `json:"description" gorm:"column:description"`
|
||||
CurrentOwner string `json:"current_owner" gorm:"column:current_owner"`
|
||||
Platform string `json:"platform" gorm:"column:platform"`
|
||||
Module string `json:"module" gorm:"column:module"`
|
||||
ReleaseID string `json:"release_id" gorm:"column:release_id"`
|
||||
Priority string `json:"priority" gorm:"column:priority"`
|
||||
Severity string `json:"severity" gorm:"column:severity"`
|
||||
Source string `json:"source" gorm:"column:source"`
|
||||
CustomFieldFour string `json:"custom_field_four" gorm:"column:custom_field_four"`
|
||||
BugType string `json:"bugtype" gorm:"column:bugtype"`
|
||||
OriginPhase string `json:"originphase" gorm:"column:originphase"`
|
||||
CustomFieldThree string `json:"custom_field_three" gorm:"column:custom_field_three"`
|
||||
Reporter string `json:"reporter" gorm:"column:reporter"`
|
||||
Status string `json:"status" gorm:"column:status"`
|
||||
IssueFilterSQL string `json:"issue_filter_sql" gorm:"column:issue_filter_sql"`
|
||||
SeverityKey string `json:"severity_key" gorm:"column:severity_key"`
|
||||
}
|
||||
|
||||
// StoryWallTimeModel Story Wall Time Model.
|
||||
type StoryWallTimeModel struct {
|
||||
StepStartTime time.Time
|
||||
StepEndTime time.Time
|
||||
}
|
551
app/service/ep/footman/model/tapd.go
Normal file
551
app/service/ep/footman/model/tapd.go
Normal file
@ -0,0 +1,551 @@
|
||||
package model
|
||||
|
||||
//IterationResponse response for tapd iteration query
|
||||
type IterationResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []*IterationWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
//IterationWrapper sub struct in IterationResponse
|
||||
type IterationWrapper struct {
|
||||
Iteration *Iteration `json:"iteration"`
|
||||
}
|
||||
|
||||
//Iteration tapd iteration
|
||||
//type Iteration struct {
|
||||
// ID string `json:"id"`
|
||||
// Name string `json:"name"`
|
||||
// StartDate string `json:"startdate"`
|
||||
// EndDate string `json:"enddate"`
|
||||
//}
|
||||
|
||||
//Iteration tapd iteration
|
||||
type Iteration struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
Startdate string `json:"startdate"`
|
||||
Enddate string `json:"enddate"`
|
||||
Status string `json:"status"`
|
||||
ReleaseID string `json:"release_id"`
|
||||
Description string `json:"description"`
|
||||
Creator string `json:"creator"`
|
||||
Created string `json:"created"`
|
||||
Modified string `json:"modified"`
|
||||
Completed string `json:"completed"`
|
||||
CustomField1 string `json:"custom_field_1"`
|
||||
CustomField2 string `json:"custom_field_2"`
|
||||
CustomField3 string `json:"custom_field_3"`
|
||||
CustomField4 string `json:"custom_field_4"`
|
||||
CustomField5 string `json:"custom_field_5"`
|
||||
CustomField6 string `json:"custom_field_6"`
|
||||
CustomField7 string `json:"custom_field_7"`
|
||||
CustomField8 string `json:"custom_field_8"`
|
||||
CustomField9 string `json:"custom_field_9"`
|
||||
CustomField10 string `json:"custom_field_10"`
|
||||
CustomField11 string `json:"custom_field_11"`
|
||||
CustomField12 string `json:"custom_field_12"`
|
||||
CustomField13 string `json:"custom_field_13"`
|
||||
CustomField14 string `json:"custom_field_14"`
|
||||
CustomField15 string `json:"custom_field_15"`
|
||||
CustomField16 string `json:"custom_field_16"`
|
||||
CustomField17 string `json:"custom_field_17"`
|
||||
CustomField18 string `json:"custom_field_18"`
|
||||
CustomField19 string `json:"custom_field_19"`
|
||||
CustomField20 string `json:"custom_field_20"`
|
||||
CustomField21 string `json:"custom_field_21"`
|
||||
CustomField22 string `json:"custom_field_22"`
|
||||
CustomField23 string `json:"custom_field_23"`
|
||||
CustomField24 string `json:"custom_field_24"`
|
||||
CustomField25 string `json:"custom_field_25"`
|
||||
CustomField26 string `json:"custom_field_26"`
|
||||
CustomField27 string `json:"custom_field_27"`
|
||||
CustomField28 string `json:"custom_field_28"`
|
||||
CustomField29 string `json:"custom_field_29"`
|
||||
CustomField30 string `json:"custom_field_30"`
|
||||
CustomField31 string `json:"custom_field_31"`
|
||||
CustomField32 string `json:"custom_field_32"`
|
||||
CustomField33 string `json:"custom_field_33"`
|
||||
CustomField34 string `json:"custom_field_34"`
|
||||
CustomField35 string `json:"custom_field_35"`
|
||||
CustomField36 string `json:"custom_field_36"`
|
||||
CustomField37 string `json:"custom_field_37"`
|
||||
CustomField38 string `json:"custom_field_38"`
|
||||
CustomField39 string `json:"custom_field_39"`
|
||||
CustomField40 string `json:"custom_field_40"`
|
||||
CustomField41 string `json:"custom_field_41"`
|
||||
CustomField42 string `json:"custom_field_42"`
|
||||
CustomField43 string `json:"custom_field_43"`
|
||||
CustomField44 string `json:"custom_field_44"`
|
||||
CustomField45 string `json:"custom_field_45"`
|
||||
CustomField46 string `json:"custom_field_46"`
|
||||
CustomField47 string `json:"custom_field_47"`
|
||||
CustomField48 string `json:"custom_field_48"`
|
||||
CustomField49 string `json:"custom_field_49"`
|
||||
CustomField50 string `json:"custom_field_50"`
|
||||
}
|
||||
|
||||
//StoryResponse response for tapd multiple stories query
|
||||
type StoryResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []*StoryWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
//SpecStoryResponse response for tapd specific story query
|
||||
type SpecStoryResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data *StoryWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
//StoryWrapper sub struct in story response
|
||||
type StoryWrapper struct {
|
||||
Story *Story `json:"story"`
|
||||
}
|
||||
|
||||
//Story tapd story
|
||||
type Story struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
Creator string `json:"creator"`
|
||||
Created string `json:"created"`
|
||||
Modified string `json:"modified"`
|
||||
Status string `json:"status"`
|
||||
Owner string `json:"owner"`
|
||||
Cc string `json:"cc"`
|
||||
Begin string `json:"begin"`
|
||||
Due string `json:"due"`
|
||||
Size string `json:"size"`
|
||||
Priority string `json:"priority"`
|
||||
Developer string `json:"developer"`
|
||||
IterationID string `json:"iteration_id"`
|
||||
TestFocus string `json:"test_focus"`
|
||||
Type string `json:"type"`
|
||||
Source string `json:"source"`
|
||||
Module string `json:"module"`
|
||||
Version string `json:"version"`
|
||||
Completed string `json:"completed"`
|
||||
CategoryID string `json:"category_id"`
|
||||
ParentID string `json:"parent_id"`
|
||||
ChildrenID string `json:"children_id"`
|
||||
AncestorID string `json:"ancestor_id"`
|
||||
BusinessValue string `json:"business_value"`
|
||||
Effort string `json:"effort"`
|
||||
EffortCompleted string `json:"effort_completed"`
|
||||
Exceed string `json:"exceed"`
|
||||
Remain string `json:"remain"`
|
||||
ReleaseID string `json:"release_id"`
|
||||
CustomFieldOne string `json:"custom_field_one"`
|
||||
CustomFieldTwo string `json:"custom_field_two"`
|
||||
CustomFieldThree string `json:"custom_field_three"`
|
||||
CustomFieldFour string `json:"custom_field_four"`
|
||||
CustomFieldFive string `json:"custom_field_five"`
|
||||
CustomFieldSix string `json:"custom_field_six"`
|
||||
CustomFieldSeven string `json:"custom_field_seven"`
|
||||
CustomFieldEight string `json:"custom_field_eight"`
|
||||
CustomField9 string `json:"custom_field_9"`
|
||||
CustomField10 string `json:"custom_field_10"`
|
||||
CustomField11 string `json:"custom_field_11"`
|
||||
CustomField12 string `json:"custom_field_12"`
|
||||
CustomField13 string `json:"custom_field_13"`
|
||||
CustomField14 string `json:"custom_field_14"`
|
||||
CustomField15 string `json:"custom_field_15"`
|
||||
CustomField16 string `json:"custom_field_16"`
|
||||
CustomField17 string `json:"custom_field_17"`
|
||||
CustomField18 string `json:"custom_field_18"`
|
||||
CustomField19 string `json:"custom_field_19"`
|
||||
CustomField20 string `json:"custom_field_20"`
|
||||
CustomField21 string `json:"custom_field_21"`
|
||||
CustomField22 string `json:"custom_field_22"`
|
||||
CustomField23 string `json:"custom_field_23"`
|
||||
CustomField24 string `json:"custom_field_24"`
|
||||
CustomField25 string `json:"custom_field_25"`
|
||||
CustomField26 string `json:"custom_field_26"`
|
||||
CustomField27 string `json:"custom_field_27"`
|
||||
CustomField28 string `json:"custom_field_28"`
|
||||
CustomField29 string `json:"custom_field_29"`
|
||||
CustomField30 string `json:"custom_field_30"`
|
||||
CustomField31 string `json:"custom_field_31"`
|
||||
CustomField32 string `json:"custom_field_32"`
|
||||
CustomField33 string `json:"custom_field_33"`
|
||||
CustomField34 string `json:"custom_field_34"`
|
||||
CustomField35 string `json:"custom_field_35"`
|
||||
CustomField36 string `json:"custom_field_36"`
|
||||
CustomField37 string `json:"custom_field_37"`
|
||||
CustomField38 string `json:"custom_field_38"`
|
||||
CustomField39 string `json:"custom_field_39"`
|
||||
CustomField40 string `json:"custom_field_40"`
|
||||
CustomField41 string `json:"custom_field_41"`
|
||||
CustomField42 string `json:"custom_field_42"`
|
||||
CustomField43 string `json:"custom_field_43"`
|
||||
CustomField44 string `json:"custom_field_44"`
|
||||
CustomField45 string `json:"custom_field_45"`
|
||||
CustomField46 string `json:"custom_field_46"`
|
||||
CustomField47 string `json:"custom_field_47"`
|
||||
CustomField48 string `json:"custom_field_48"`
|
||||
CustomField49 string `json:"custom_field_49"`
|
||||
CustomField50 string `json:"custom_field_50"`
|
||||
CustomField51 string `json:"custom_field_51"`
|
||||
CustomField52 string `json:"custom_field_52"`
|
||||
CustomField53 string `json:"custom_field_53"`
|
||||
CustomField54 string `json:"custom_field_54"`
|
||||
CustomField55 string `json:"custom_field_55"`
|
||||
CustomField56 string `json:"custom_field_56"`
|
||||
CustomField57 string `json:"custom_field_57"`
|
||||
CustomField58 string `json:"custom_field_58"`
|
||||
CustomField59 string `json:"custom_field_59"`
|
||||
CustomField60 string `json:"custom_field_60"`
|
||||
CustomField61 string `json:"custom_field_61"`
|
||||
CustomField62 string `json:"custom_field_62"`
|
||||
CustomField63 string `json:"custom_field_63"`
|
||||
CustomField64 string `json:"custom_field_64"`
|
||||
CustomField65 string `json:"custom_field_65"`
|
||||
CustomField66 string `json:"custom_field_66"`
|
||||
CustomField67 string `json:"custom_field_67"`
|
||||
CustomField68 string `json:"custom_field_68"`
|
||||
CustomField69 string `json:"custom_field_69"`
|
||||
CustomField70 string `json:"custom_field_70"`
|
||||
CustomField71 string `json:"custom_field_71"`
|
||||
CustomField72 string `json:"custom_field_72"`
|
||||
CustomField73 string `json:"custom_field_73"`
|
||||
CustomField74 string `json:"custom_field_74"`
|
||||
CustomField75 string `json:"custom_field_75"`
|
||||
CustomField76 string `json:"custom_field_76"`
|
||||
CustomField77 string `json:"custom_field_77"`
|
||||
CustomField78 string `json:"custom_field_78"`
|
||||
CustomField79 string `json:"custom_field_79"`
|
||||
CustomField80 string `json:"custom_field_80"`
|
||||
CustomField81 string `json:"custom_field_81"`
|
||||
CustomField82 string `json:"custom_field_82"`
|
||||
CustomField83 string `json:"custom_field_83"`
|
||||
CustomField84 string `json:"custom_field_84"`
|
||||
CustomField85 string `json:"custom_field_85"`
|
||||
CustomField86 string `json:"custom_field_86"`
|
||||
CustomField87 string `json:"custom_field_87"`
|
||||
CustomField88 string `json:"custom_field_88"`
|
||||
CustomField89 string `json:"custom_field_89"`
|
||||
CustomField90 string `json:"custom_field_90"`
|
||||
CustomField91 string `json:"custom_field_91"`
|
||||
CustomField92 string `json:"custom_field_92"`
|
||||
CustomField93 string `json:"custom_field_93"`
|
||||
CustomField94 string `json:"custom_field_94"`
|
||||
CustomField95 string `json:"custom_field_95"`
|
||||
CustomField96 string `json:"custom_field_96"`
|
||||
CustomField97 string `json:"custom_field_97"`
|
||||
CustomField98 string `json:"custom_field_98"`
|
||||
CustomField99 string `json:"custom_field_99"`
|
||||
CustomField100 string `json:"custom_field_100"`
|
||||
}
|
||||
|
||||
//IOSStory additional fields for ios story
|
||||
type IOSStory struct {
|
||||
CustomField99 string `json:"custom_field_99"` //接口上线日
|
||||
CustomField97 string `json:"custom_field_97"` //双端都提得需求
|
||||
CustomField93 string `json:"custom_field_93"` //端范围(默认仅粉iPhone)
|
||||
CustomField92 string `json:"custom_field_92"` //是否可以单端上线
|
||||
}
|
||||
|
||||
//AndroidStory additional fields for android story
|
||||
type AndroidStory struct {
|
||||
CustomField99 string `json:"custom_field_99"` //接口上线日
|
||||
CustomField97 string `json:"custom_field_97"` //双端都提得需求
|
||||
CustomField93 string `json:"custom_field_93"` //是否可以单端上线
|
||||
}
|
||||
|
||||
// ReleaseResponse Release Response
|
||||
type ReleaseResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data *ReleaseWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
// ReleaseWrapper Release Wrapper
|
||||
type ReleaseWrapper struct {
|
||||
Release *Release `json:"Release"`
|
||||
}
|
||||
|
||||
// Release Release
|
||||
type Release struct {
|
||||
ID string `json:"id"`
|
||||
WorkSpaceID string `json:"workspace_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
StartDate string `json:"startdate"`
|
||||
EndDate string `json:"enddate"`
|
||||
Creator string `json:"creator"`
|
||||
Created string `json:"created"`
|
||||
Modified string `json:"modified"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// BugResponse Bug Response
|
||||
type BugResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []*BugWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
// BugSingleResponse Bug Response
|
||||
type BugSingleResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data *BugWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
// BugWrapper Bug Wrapper
|
||||
type BugWrapper struct {
|
||||
Bug *Bug `json:"Bug"`
|
||||
}
|
||||
|
||||
// Bug Bug
|
||||
type Bug struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Priority string `json:"priority"`
|
||||
Severity string `json:"severity"`
|
||||
Module string `json:"module"`
|
||||
Status string `json:"status"`
|
||||
Reporter string `json:"reporter"`
|
||||
Deadline string `json:"deadline"`
|
||||
Created string `json:"created"`
|
||||
BugType string `json:"bugtype"`
|
||||
Resolved string `json:"resolved"`
|
||||
Closed string `json:"closed"`
|
||||
Modified string `json:"modified"`
|
||||
LastModify string `json:"lastmodify"`
|
||||
Auditer string `json:"auditer"`
|
||||
DE string `json:"de"`
|
||||
VersionTest string `json:"version_test"`
|
||||
VersionReport string `json:"version_report"`
|
||||
VersionClose string `json:"version_close"`
|
||||
VersionFix string `json:"version_fix"`
|
||||
BaselineFind string `json:"baseline_find"`
|
||||
BaselineJoin string `json:"baseline_join"`
|
||||
BaselineClose string `json:"baseline_close"`
|
||||
BaselineTest string `json:"baseline_test"`
|
||||
SourcePhase string `json:"sourcephase"`
|
||||
TE string `json:"te"`
|
||||
CurrentOwner string `json:"current_owner"`
|
||||
IterationID string `json:"iteration_id"`
|
||||
Resolution string `json:"resolution"`
|
||||
Source string `json:"source"`
|
||||
OriginPhase string `json:"originphase"`
|
||||
Confirmer string `json:"confirmer"`
|
||||
Milestone string `json:"milestone"`
|
||||
Participator string `json:"participator"`
|
||||
Closer string `json:"closer"`
|
||||
Platform string `json:"platform"`
|
||||
OS string `json:"os"`
|
||||
TestType string `json:"testtype"`
|
||||
TestPhase string `json:"testphase"`
|
||||
Frequency string `json:"frequency"`
|
||||
CC string `json:"cc"`
|
||||
RegressionNumber string `json:"regression_number"`
|
||||
Flows string `json:"flows"`
|
||||
Feature string `json:"feature"`
|
||||
TestMode string `json:"testmode"`
|
||||
Estimate string `json:"estimate"`
|
||||
IssueID string `json:"issue_id"`
|
||||
CreatedFrom string `json:"created_from"`
|
||||
InProgressTime string `json:"in_progress_time"`
|
||||
VerifyTime string `json:"verify_time"`
|
||||
RejectTime string `json:"reject_time"`
|
||||
ReopenTime string `json:"reopen_time"`
|
||||
AuditTime string `json:"audit_time"`
|
||||
SuspendTime string `json:"suspend_time"`
|
||||
Due string `json:"due"`
|
||||
Begin string `json:"begin"`
|
||||
ReleaseID string `json:"release_id"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
CustomFieldOne string `json:"custom_field_one"`
|
||||
CustomFieldTwo string `json:"custom_field_two"`
|
||||
CustomFieldThree string `json:"custom_field_three"`
|
||||
CustomFieldFour string `json:"custom_field_four"`
|
||||
CustomFieldFive string `json:"custom_field_five"`
|
||||
CustomField6 string `json:"custom_field_6"`
|
||||
CustomField7 string `json:"custom_field_7"`
|
||||
CustomField8 string `json:"custom_field_8"`
|
||||
CustomField9 string `json:"custom_field_9"`
|
||||
CustomField10 string `json:"custom_field_10"`
|
||||
CustomField11 string `json:"custom_field_11"`
|
||||
CustomField12 string `json:"custom_field_12"`
|
||||
CustomField13 string `json:"custom_field_13"`
|
||||
CustomField14 string `json:"custom_field_14"`
|
||||
CustomField15 string `json:"custom_field_15"`
|
||||
CustomField16 string `json:"custom_field_16"`
|
||||
CustomField17 string `json:"custom_field_17"`
|
||||
CustomField18 string `json:"custom_field_18"`
|
||||
CustomField19 string `json:"custom_field_19"`
|
||||
CustomField20 string `json:"custom_field_20"`
|
||||
CustomField21 string `json:"custom_field_21"`
|
||||
CustomField22 string `json:"custom_field_22"`
|
||||
CustomField23 string `json:"custom_field_23"`
|
||||
CustomField24 string `json:"custom_field_24"`
|
||||
CustomField25 string `json:"custom_field_25"`
|
||||
CustomField26 string `json:"custom_field_26"`
|
||||
CustomField27 string `json:"custom_field_27"`
|
||||
CustomField28 string `json:"custom_field_28"`
|
||||
CustomField29 string `json:"custom_field_29"`
|
||||
CustomField30 string `json:"custom_field_30"`
|
||||
CustomField31 string `json:"custom_field_31"`
|
||||
CustomField32 string `json:"custom_field_32"`
|
||||
CustomField33 string `json:"custom_field_33"`
|
||||
CustomField34 string `json:"custom_field_34"`
|
||||
CustomField35 string `json:"custom_field_35"`
|
||||
CustomField36 string `json:"custom_field_36"`
|
||||
CustomField37 string `json:"custom_field_37"`
|
||||
CustomField38 string `json:"custom_field_38"`
|
||||
CustomField39 string `json:"custom_field_39"`
|
||||
CustomField40 string `json:"custom_field_40"`
|
||||
CustomField41 string `json:"custom_field_41"`
|
||||
CustomField42 string `json:"custom_field_42"`
|
||||
CustomField43 string `json:"custom_field_43"`
|
||||
CustomField44 string `json:"custom_field_44"`
|
||||
CustomField45 string `json:"custom_field_45"`
|
||||
CustomField46 string `json:"custom_field_46"`
|
||||
CustomField47 string `json:"custom_field_47"`
|
||||
CustomField48 string `json:"custom_field_48"`
|
||||
CustomField49 string `json:"custom_field_49"`
|
||||
CustomField50 string `json:"custom_field_50"`
|
||||
}
|
||||
|
||||
// UpdateBug Update Bug
|
||||
type UpdateBug struct {
|
||||
*Bug
|
||||
CurrentUser string `json:"current_user"`
|
||||
}
|
||||
|
||||
//StoryChangeResponse response for tapd story change query
|
||||
type StoryChangeResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []*WorkitemChangeWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
//WorkitemChangeWrapper sub struct in StoryChangeResponse
|
||||
type WorkitemChangeWrapper struct {
|
||||
WorkitemChange *WorkitemChange `json:"WorkitemChange"`
|
||||
}
|
||||
|
||||
//WorkitemChange sub struct in WorkitemChangeWrapper
|
||||
type WorkitemChange struct {
|
||||
ID string `json:"id"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
Creator string `json:"creator"`
|
||||
Created string `json:"created"`
|
||||
ChangeSummay string `json:"change_summay"`
|
||||
Comment string `json:"comment"`
|
||||
Changes string `json:"changes"`
|
||||
EntityType string `json:"entity_type"`
|
||||
StoryID string `json:"story_id"`
|
||||
}
|
||||
|
||||
//StoryChangeItem story change struct wrote to change file
|
||||
type StoryChangeItem struct {
|
||||
ID string
|
||||
WorkspaceID string
|
||||
StoryID string
|
||||
Number string
|
||||
Field string
|
||||
Creator string
|
||||
Created string
|
||||
ValueBefore string
|
||||
ValueAfter string
|
||||
ChangeSummay string
|
||||
Comment string
|
||||
EntityType string
|
||||
}
|
||||
|
||||
//StoryChangeByIteration story changes organized by iteration
|
||||
type StoryChangeByIteration struct {
|
||||
IterationName string
|
||||
StoryCount int
|
||||
StoryChangeList []*TargetStoryChange
|
||||
}
|
||||
|
||||
//TargetStoryChange story and story changes
|
||||
type TargetStoryChange struct {
|
||||
Story *Story
|
||||
StatusChanges []*StatusChange
|
||||
}
|
||||
|
||||
//StatusChange story change
|
||||
type StatusChange struct {
|
||||
Creator string
|
||||
Created string
|
||||
ValueBefore string
|
||||
ValueAfter string
|
||||
}
|
||||
|
||||
//NameMapResponse story status name mapping
|
||||
type NameMapResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data map[string]string `json:"data"`
|
||||
Info string
|
||||
}
|
||||
|
||||
//RejectedStoryByIteration rejected stories organized by iteration
|
||||
type RejectedStoryByIteration struct {
|
||||
IterationName string
|
||||
RejectedStoryCount int
|
||||
RejectedStoryList []string
|
||||
}
|
||||
|
||||
//TestTimeByIteration stories' test time info organized by iteration
|
||||
type TestTimeByIteration struct {
|
||||
IterationName string
|
||||
StoryCount int
|
||||
TimeByStroy []*TestTimeByStory
|
||||
}
|
||||
|
||||
//TestTimeByStory story base info and test time
|
||||
type TestTimeByStory struct {
|
||||
StoryName string
|
||||
StorySize string
|
||||
StoryEffort string
|
||||
TestTime float64
|
||||
}
|
||||
|
||||
//WaitTimeByIteration stories' wait time organized by iteration
|
||||
type WaitTimeByIteration struct {
|
||||
IterationName string
|
||||
StoryCount int
|
||||
TimeByStroy []*WaitTimeByStory
|
||||
}
|
||||
|
||||
//WaitTimeByStory story base info and wait time
|
||||
type WaitTimeByStory struct {
|
||||
StoryName string
|
||||
StorySize string
|
||||
StoryEffort string
|
||||
WaitTime float64
|
||||
}
|
||||
|
||||
//CategoryResponse response for tapd category query
|
||||
type CategoryResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data []*CategoryWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
//CategoryPreResponse response for tapd category query
|
||||
type CategoryPreResponse struct {
|
||||
Status int `json:"status"`
|
||||
Data *CategoryWrapper `json:"data"`
|
||||
Info string `json:"info"`
|
||||
}
|
||||
|
||||
//CategoryWrapper sub struct in CategoryResponse
|
||||
type CategoryWrapper struct {
|
||||
Category *Category
|
||||
}
|
||||
|
||||
//Category project category
|
||||
type Category struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
39
app/service/ep/footman/server/http/BUILD
Normal file
39
app/service/ep/footman/server/http/BUILD
Normal file
@ -0,0 +1,39 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bugly.go",
|
||||
"bugly2tapd.go",
|
||||
"http.go",
|
||||
"tapd.go",
|
||||
],
|
||||
importpath = "go-common/app/service/ep/footman/server/http",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/footman/conf:go_default_library",
|
||||
"//app/service/ep/footman/service:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//library/net/http/blademaster:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
35
app/service/ep/footman/server/http/bugly.go
Normal file
35
app/service/ep/footman/server/http/bugly.go
Normal file
@ -0,0 +1,35 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
)
|
||||
|
||||
func queryIssue(c *bm.Context) {
|
||||
v := new(struct {
|
||||
Version string `form:"version"`
|
||||
})
|
||||
if err := c.Bind(v); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(srv.Issue(c, v.Version))
|
||||
}
|
||||
|
||||
func updateToken(c *bm.Context) {
|
||||
c.JSON(srv.UpdateToken(c))
|
||||
}
|
||||
|
||||
func saveIssue(c *bm.Context) {
|
||||
v := new(struct {
|
||||
Version string `form:"version"`
|
||||
})
|
||||
if err := c.Bind(v); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(srv.SaveIssue(c, v.Version))
|
||||
}
|
||||
|
||||
func saveIssues(c *bm.Context) {
|
||||
c.JSON(srv.SaveIssues(c))
|
||||
}
|
17
app/service/ep/footman/server/http/bugly2tapd.go
Normal file
17
app/service/ep/footman/server/http/bugly2tapd.go
Normal file
@ -0,0 +1,17 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
)
|
||||
|
||||
func saveBugly2Tapd(c *bm.Context) {
|
||||
c.JSON(nil, srv.AsyncBuglyInsertTapd(c))
|
||||
}
|
||||
|
||||
func updateBuglyStatusInTapd(c *bm.Context) {
|
||||
c.JSON(nil, srv.AsyncUpdateBuglyStatusInTapd(c))
|
||||
}
|
||||
|
||||
func updateTitleInTapd(c *bm.Context) {
|
||||
c.JSON(nil, srv.AsyncUpdateBugInTapd(c))
|
||||
}
|
75
app/service/ep/footman/server/http/http.go
Normal file
75
app/service/ep/footman/server/http/http.go
Normal file
@ -0,0 +1,75 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go-common/app/service/ep/footman/conf"
|
||||
"go-common/app/service/ep/footman/service"
|
||||
"go-common/library/log"
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
)
|
||||
|
||||
var (
|
||||
srv *service.Service
|
||||
)
|
||||
|
||||
// Init Init.
|
||||
func Init(c *conf.Config, s *service.Service) {
|
||||
srv = s
|
||||
engine := bm.DefaultServer(c.BM)
|
||||
engine.Ping(ping)
|
||||
outerRouter(engine)
|
||||
if err := engine.Start(); err != nil {
|
||||
log.Error("engine.Start error(%v)", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func outerRouter(e *bm.Engine) {
|
||||
version := e.Group("/ep/admin/footman/v1")
|
||||
{
|
||||
version.GET("/version", getVersion)
|
||||
|
||||
bugly := version.Group("/bugly")
|
||||
{
|
||||
bugly.GET("/issue/query", queryIssue)
|
||||
bugly.GET("/issue/save", saveIssue)
|
||||
bugly.GET("/saveissue", saveIssue)
|
||||
bugly.GET("/saveissues", saveIssues)
|
||||
bugly.GET("/updatetoken", updateToken)
|
||||
}
|
||||
|
||||
tapd := version.Group("/tapd")
|
||||
{
|
||||
tapd.GET("/file/save", saveFiles)
|
||||
tapd.GET("/file/story/download", downloadStoryFile)
|
||||
tapd.GET("/file/change/download", downloadChangeFile)
|
||||
tapd.GET("/file/iteration/download", downloadIterationFile)
|
||||
tapd.GET("/file/bug/download", downloadBugFile)
|
||||
}
|
||||
|
||||
bugly2tapd := version.Group("/bugly2tapd")
|
||||
{
|
||||
bugly2tapd.GET("/save", saveBugly2Tapd)
|
||||
bugly2tapd.GET("/status/update", updateBuglyStatusInTapd)
|
||||
bugly2tapd.GET("/title/update", updateTitleInTapd)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ping(c *bm.Context) {
|
||||
if err := srv.Ping(c); err != nil {
|
||||
log.Error("ping error(%v)", err)
|
||||
c.AbortWithStatus(http.StatusServiceUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
func getVersion(c *bm.Context) {
|
||||
v := new(struct {
|
||||
Version string `json:"version"`
|
||||
})
|
||||
v.Version = "v.1.5.8.2"
|
||||
c.JSON(v, nil)
|
||||
|
||||
}
|
70
app/service/ep/footman/server/http/tapd.go
Normal file
70
app/service/ep/footman/server/http/tapd.go
Normal file
@ -0,0 +1,70 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"go-common/library/log"
|
||||
bm "go-common/library/net/http/blademaster"
|
||||
)
|
||||
|
||||
func saveFiles(c *bm.Context) {
|
||||
c.JSON(nil, srv.SaveFiles(c))
|
||||
}
|
||||
|
||||
func downloadStoryFile(c *bm.Context) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
code int
|
||||
)
|
||||
if data, err = srv.DownloadStoryFile(c); err != nil {
|
||||
log.Error("Download story file failed, error:%v", err)
|
||||
code = -1
|
||||
}
|
||||
contentType := " text/plain;charset:utf-8;"
|
||||
c.Writer.Header().Set("content-disposition", `attachment; filename=story.txt`)
|
||||
c.Bytes(code, contentType, data)
|
||||
}
|
||||
|
||||
func downloadChangeFile(c *bm.Context) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
code int
|
||||
)
|
||||
if data, err = srv.DownloadChangeFile(c); err != nil {
|
||||
log.Error("Download change file failed, error:%v", err)
|
||||
code = -1
|
||||
}
|
||||
contentType := " text/plain;charset:utf-8;"
|
||||
c.Writer.Header().Set("content-disposition", `attachment; filename=change.txt`)
|
||||
c.Bytes(code, contentType, data)
|
||||
}
|
||||
|
||||
func downloadIterationFile(c *bm.Context) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
code int
|
||||
)
|
||||
if data, err = srv.DownloadIterationFile(c); err != nil {
|
||||
log.Error("Download iteration file failed, error:%v", err)
|
||||
code = -1
|
||||
}
|
||||
contentType := " text/plain;charset:utf-8;"
|
||||
c.Writer.Header().Set("content-disposition", `attachment; filename=iteration.txt`)
|
||||
c.Bytes(code, contentType, data)
|
||||
}
|
||||
|
||||
func downloadBugFile(c *bm.Context) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
code int
|
||||
)
|
||||
if data, err = srv.DownBugFile(c); err != nil {
|
||||
log.Error("Download bug file failed, error:%v", err)
|
||||
code = -1
|
||||
}
|
||||
contentType := " text/plain;charset:utf-8;"
|
||||
c.Writer.Header().Set("content-disposition", `attachment; filename=bug.txt`)
|
||||
c.Bytes(code, contentType, data)
|
||||
}
|
49
app/service/ep/footman/service/BUILD
Normal file
49
app/service/ep/footman/service/BUILD
Normal file
@ -0,0 +1,49 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"bigdata.go",
|
||||
"bugly.go",
|
||||
"bugly2tapd.go",
|
||||
"excel.go",
|
||||
"mail.go",
|
||||
"report.go",
|
||||
"service.go",
|
||||
"tapd.go",
|
||||
"util.go",
|
||||
],
|
||||
importpath = "go-common/app/service/ep/footman/service",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/footman/conf:go_default_library",
|
||||
"//app/service/ep/footman/dao:go_default_library",
|
||||
"//app/service/ep/footman/model:go_default_library",
|
||||
"//library/cache:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//vendor/github.com/360EntSecGroup-Skylar/excelize:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
"//vendor/github.com/robfig/cron:go_default_library",
|
||||
"//vendor/gopkg.in/gomail.v2:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
603
app/service/ep/footman/service/bigdata.go
Normal file
603
app/service/ep/footman/service/bigdata.go
Normal file
@ -0,0 +1,603 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
const (
|
||||
_successTile = "Success Notice"
|
||||
_successContxt = "Success to write %s Data To File,current retry time number: %d"
|
||||
_failedTile = "Failed Notice"
|
||||
_failedContxt = "Failed to write %s Data To File,current retry time number: %d, error:(+%v)"
|
||||
)
|
||||
|
||||
//SaveFiles save story, story change, iteration info to files
|
||||
func (s *Service) SaveFiles(c context.Context) (err error) {
|
||||
return s.cache.Save(func() {
|
||||
s.saveFiles(context.Background())
|
||||
})
|
||||
}
|
||||
|
||||
// SaveFilesForTask Save Files For Task.
|
||||
func (s *Service) SaveFilesForTask() {
|
||||
s.cache.Save(func() {
|
||||
s.saveFiles(context.Background())
|
||||
})
|
||||
}
|
||||
|
||||
//saveFiles save story, story change, iteration info to files
|
||||
func (s *Service) saveFiles(c context.Context) (err error) {
|
||||
|
||||
for index := 0; index < s.c.Tapd.RetryTime; index++ {
|
||||
if err = s.writeBugDataToFile(c); err == nil {
|
||||
s.TapdMailNotice(_successTile, fmt.Sprintf(_successContxt, "bug", index+1))
|
||||
break
|
||||
} else {
|
||||
s.TapdMailNotice(_failedTile, fmt.Sprintf(_failedContxt, "bug", index+1, err))
|
||||
}
|
||||
time.Sleep(time.Duration(s.c.Tapd.WaitTime))
|
||||
}
|
||||
|
||||
for index := 0; index < s.c.Tapd.RetryTime; index++ {
|
||||
if err = s.writeStoryDataToFile(c); err == nil {
|
||||
s.TapdMailNotice(_successTile, fmt.Sprintf(_successContxt, "story", index+1))
|
||||
break
|
||||
} else {
|
||||
s.TapdMailNotice(_failedTile, fmt.Sprintf(_failedContxt, "story", index+1, err))
|
||||
}
|
||||
time.Sleep(time.Duration(s.c.Tapd.WaitTime))
|
||||
}
|
||||
|
||||
for index := 0; index < s.c.Tapd.RetryTime; index++ {
|
||||
if err = s.writeIterationDataToFile(c); err == nil {
|
||||
s.TapdMailNotice(_successTile, fmt.Sprintf(_successContxt, "iteration", index+1))
|
||||
break
|
||||
} else {
|
||||
s.TapdMailNotice(_failedTile, fmt.Sprintf(_failedContxt, "iteration", index+1, err))
|
||||
}
|
||||
time.Sleep(time.Duration(s.c.Tapd.WaitTime))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//DownloadStoryFile return the story file content
|
||||
func (s *Service) DownloadStoryFile(c context.Context) (content []byte, err error) {
|
||||
return ioutil.ReadFile(s.c.Tapd.StoryFilePath)
|
||||
}
|
||||
|
||||
//DownloadChangeFile return the story change file content
|
||||
func (s *Service) DownloadChangeFile(c context.Context) (content []byte, err error) {
|
||||
return ioutil.ReadFile(s.c.Tapd.ChangeFilePath)
|
||||
}
|
||||
|
||||
//DownloadIterationFile return the iteration file content
|
||||
func (s *Service) DownloadIterationFile(c context.Context) (content []byte, err error) {
|
||||
return ioutil.ReadFile(s.c.Tapd.IterationFilePath)
|
||||
}
|
||||
|
||||
//DownBugFile return the bug file content
|
||||
func (s *Service) DownBugFile(c context.Context) (content []byte, err error) {
|
||||
return ioutil.ReadFile(s.c.Tapd.BugFilePath)
|
||||
}
|
||||
|
||||
//writeStoryDataToFile write story and story change info to file
|
||||
func (s *Service) writeStoryDataToFile(c context.Context) (err error) {
|
||||
var (
|
||||
storyRes *model.StoryResponse
|
||||
storyFile, changeFile *os.File
|
||||
storyChangeItems []*model.StoryChangeItem
|
||||
)
|
||||
|
||||
log.Info("Writing story and story change info to file: start")
|
||||
|
||||
writeTime := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
if _, fileErr := os.Stat(s.c.Tapd.StoryFilePath); fileErr == nil {
|
||||
if err = os.Remove(s.c.Tapd.StoryFilePath); err != nil {
|
||||
log.Error("Error happened when removing story file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if storyFile, err = os.Create(s.c.Tapd.StoryFilePath); err != nil {
|
||||
log.Error("Error happened when creating story file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
defer storyFile.Close()
|
||||
|
||||
if _, fileErr := os.Stat(s.c.Tapd.ChangeFilePath); fileErr == nil {
|
||||
if err = os.Remove(s.c.Tapd.ChangeFilePath); err != nil {
|
||||
log.Error("Error happened when removing story change file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if changeFile, err = os.Create(s.c.Tapd.ChangeFilePath); err != nil {
|
||||
log.Error("Error happened when creating story change file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
defer changeFile.Close()
|
||||
|
||||
for _, workspaceID := range s.c.Tapd.StoryWorkspaceIDs {
|
||||
storyURL := fmt.Sprintf(model.StoryURLWithoutItera, workspaceID, s.c.Tapd.SPS) + "&page=%d"
|
||||
if storyRes, err = s.dao.AllStories(s.c.Tapd.SPS, storyURL); err != nil {
|
||||
log.Error("Error happened when fetching all stories for workspace %s. Error: %v", workspaceID, err)
|
||||
continue
|
||||
}
|
||||
for _, story := range storyRes.Data {
|
||||
//releaseId change to release name
|
||||
releaseName := s.handleReleaseName(story.Story.WorkspaceID, story.Story.ReleaseID)
|
||||
categoryName := s.handleCategoryPreName(story.Story.WorkspaceID, story.Story.CategoryID)
|
||||
|
||||
customStoryFields := make(map[string]string)
|
||||
customStoryFields["CustomFieldOne"] = s.removeCRCFInString(story.Story.CustomFieldOne)
|
||||
customStoryFields["CustomFieldTwo"] = s.removeCRCFInString(story.Story.CustomFieldTwo)
|
||||
customStoryFields["CustomFieldThree"] = s.removeCRCFInString(story.Story.CustomFieldThree)
|
||||
customStoryFields["CustomFieldFour"] = s.removeCRCFInString(story.Story.CustomFieldFour)
|
||||
customStoryFields["CustomFieldFive"] = s.removeCRCFInString(story.Story.CustomFieldFive)
|
||||
customStoryFields["CustomFieldSix"] = s.removeCRCFInString(story.Story.CustomFieldSix)
|
||||
customStoryFields["CustomFieldSeven"] = s.removeCRCFInString(story.Story.CustomFieldSeven)
|
||||
customStoryFields["CustomFieldEight"] = s.removeCRCFInString(story.Story.CustomFieldEight)
|
||||
customStoryFields["CustomField9"] = s.removeCRCFInString(story.Story.CustomField9)
|
||||
customStoryFields["CustomField10"] = s.removeCRCFInString(story.Story.CustomField10)
|
||||
customStoryFields["CustomField11"] = s.removeCRCFInString(story.Story.CustomField11)
|
||||
customStoryFields["CustomField12"] = s.removeCRCFInString(story.Story.CustomField12)
|
||||
customStoryFields["CustomField13"] = s.removeCRCFInString(story.Story.CustomField13)
|
||||
customStoryFields["CustomField14"] = s.removeCRCFInString(story.Story.CustomField14)
|
||||
customStoryFields["CustomField15"] = s.removeCRCFInString(story.Story.CustomField15)
|
||||
customStoryFields["CustomField16"] = s.removeCRCFInString(story.Story.CustomField16)
|
||||
customStoryFields["CustomField17"] = s.removeCRCFInString(story.Story.CustomField17)
|
||||
customStoryFields["CustomField18"] = s.removeCRCFInString(story.Story.CustomField18)
|
||||
customStoryFields["CustomField19"] = s.removeCRCFInString(story.Story.CustomField19)
|
||||
customStoryFields["CustomField20"] = s.removeCRCFInString(story.Story.CustomField20)
|
||||
customStoryFields["CustomField21"] = s.removeCRCFInString(story.Story.CustomField21)
|
||||
customStoryFields["CustomField22"] = s.removeCRCFInString(story.Story.CustomField22)
|
||||
customStoryFields["CustomField23"] = s.removeCRCFInString(story.Story.CustomField23)
|
||||
customStoryFields["CustomField24"] = s.removeCRCFInString(story.Story.CustomField24)
|
||||
customStoryFields["CustomField25"] = s.removeCRCFInString(story.Story.CustomField25)
|
||||
customStoryFields["CustomField26"] = s.removeCRCFInString(story.Story.CustomField26)
|
||||
customStoryFields["CustomField27"] = s.removeCRCFInString(story.Story.CustomField27)
|
||||
customStoryFields["CustomField28"] = s.removeCRCFInString(story.Story.CustomField28)
|
||||
customStoryFields["CustomField29"] = s.removeCRCFInString(story.Story.CustomField29)
|
||||
customStoryFields["CustomField30"] = s.removeCRCFInString(story.Story.CustomField30)
|
||||
customStoryFields["CustomField31"] = s.removeCRCFInString(story.Story.CustomField31)
|
||||
customStoryFields["CustomField32"] = s.removeCRCFInString(story.Story.CustomField32)
|
||||
customStoryFields["CustomField33"] = s.removeCRCFInString(story.Story.CustomField33)
|
||||
customStoryFields["CustomField34"] = s.removeCRCFInString(story.Story.CustomField34)
|
||||
customStoryFields["CustomField35"] = s.removeCRCFInString(story.Story.CustomField35)
|
||||
customStoryFields["CustomField36"] = s.removeCRCFInString(story.Story.CustomField36)
|
||||
customStoryFields["CustomField37"] = s.removeCRCFInString(story.Story.CustomField37)
|
||||
customStoryFields["CustomField38"] = s.removeCRCFInString(story.Story.CustomField38)
|
||||
customStoryFields["CustomField39"] = s.removeCRCFInString(story.Story.CustomField39)
|
||||
customStoryFields["CustomField40"] = s.removeCRCFInString(story.Story.CustomField40)
|
||||
customStoryFields["CustomField41"] = s.removeCRCFInString(story.Story.CustomField41)
|
||||
customStoryFields["CustomField42"] = s.removeCRCFInString(story.Story.CustomField42)
|
||||
customStoryFields["CustomField43"] = s.removeCRCFInString(story.Story.CustomField43)
|
||||
customStoryFields["CustomField44"] = s.removeCRCFInString(story.Story.CustomField44)
|
||||
customStoryFields["CustomField45"] = s.removeCRCFInString(story.Story.CustomField45)
|
||||
customStoryFields["CustomField46"] = s.removeCRCFInString(story.Story.CustomField46)
|
||||
customStoryFields["CustomField47"] = s.removeCRCFInString(story.Story.CustomField47)
|
||||
customStoryFields["CustomField48"] = s.removeCRCFInString(story.Story.CustomField48)
|
||||
customStoryFields["CustomField49"] = s.removeCRCFInString(story.Story.CustomField49)
|
||||
customStoryFields["CustomField50"] = s.removeCRCFInString(story.Story.CustomField50)
|
||||
customStoryFields["CustomField51"] = s.removeCRCFInString(story.Story.CustomField51)
|
||||
customStoryFields["CustomField52"] = s.removeCRCFInString(story.Story.CustomField52)
|
||||
customStoryFields["CustomField53"] = s.removeCRCFInString(story.Story.CustomField53)
|
||||
customStoryFields["CustomField54"] = s.removeCRCFInString(story.Story.CustomField54)
|
||||
customStoryFields["CustomField55"] = s.removeCRCFInString(story.Story.CustomField55)
|
||||
customStoryFields["CustomField56"] = s.removeCRCFInString(story.Story.CustomField56)
|
||||
customStoryFields["CustomField57"] = s.removeCRCFInString(story.Story.CustomField57)
|
||||
customStoryFields["CustomField58"] = s.removeCRCFInString(story.Story.CustomField58)
|
||||
customStoryFields["CustomField59"] = s.removeCRCFInString(story.Story.CustomField59)
|
||||
customStoryFields["CustomField60"] = s.removeCRCFInString(story.Story.CustomField60)
|
||||
customStoryFields["CustomField61"] = s.removeCRCFInString(story.Story.CustomField61)
|
||||
customStoryFields["CustomField62"] = s.removeCRCFInString(story.Story.CustomField62)
|
||||
customStoryFields["CustomField63"] = s.removeCRCFInString(story.Story.CustomField63)
|
||||
customStoryFields["CustomField64"] = s.removeCRCFInString(story.Story.CustomField64)
|
||||
customStoryFields["CustomField65"] = s.removeCRCFInString(story.Story.CustomField65)
|
||||
customStoryFields["CustomField66"] = s.removeCRCFInString(story.Story.CustomField66)
|
||||
customStoryFields["CustomField67"] = s.removeCRCFInString(story.Story.CustomField67)
|
||||
customStoryFields["CustomField68"] = s.removeCRCFInString(story.Story.CustomField68)
|
||||
customStoryFields["CustomField69"] = s.removeCRCFInString(story.Story.CustomField69)
|
||||
customStoryFields["CustomField70"] = s.removeCRCFInString(story.Story.CustomField70)
|
||||
customStoryFields["CustomField71"] = s.removeCRCFInString(story.Story.CustomField71)
|
||||
customStoryFields["CustomField72"] = s.removeCRCFInString(story.Story.CustomField72)
|
||||
customStoryFields["CustomField73"] = s.removeCRCFInString(story.Story.CustomField73)
|
||||
customStoryFields["CustomField74"] = s.removeCRCFInString(story.Story.CustomField74)
|
||||
customStoryFields["CustomField75"] = s.removeCRCFInString(story.Story.CustomField75)
|
||||
customStoryFields["CustomField76"] = s.removeCRCFInString(story.Story.CustomField76)
|
||||
customStoryFields["CustomField77"] = s.removeCRCFInString(story.Story.CustomField77)
|
||||
customStoryFields["CustomField78"] = s.removeCRCFInString(story.Story.CustomField78)
|
||||
customStoryFields["CustomField79"] = s.removeCRCFInString(story.Story.CustomField79)
|
||||
customStoryFields["CustomField80"] = s.removeCRCFInString(story.Story.CustomField80)
|
||||
customStoryFields["CustomField81"] = s.removeCRCFInString(story.Story.CustomField81)
|
||||
customStoryFields["CustomField82"] = s.removeCRCFInString(story.Story.CustomField82)
|
||||
customStoryFields["CustomField83"] = s.removeCRCFInString(story.Story.CustomField83)
|
||||
customStoryFields["CustomField84"] = s.removeCRCFInString(story.Story.CustomField84)
|
||||
customStoryFields["CustomField85"] = s.removeCRCFInString(story.Story.CustomField85)
|
||||
customStoryFields["CustomField86"] = s.removeCRCFInString(story.Story.CustomField86)
|
||||
customStoryFields["CustomField87"] = s.removeCRCFInString(story.Story.CustomField87)
|
||||
customStoryFields["CustomField88"] = s.removeCRCFInString(story.Story.CustomField88)
|
||||
customStoryFields["CustomField89"] = s.removeCRCFInString(story.Story.CustomField89)
|
||||
customStoryFields["CustomField90"] = s.removeCRCFInString(story.Story.CustomField90)
|
||||
customStoryFields["CustomField91"] = s.removeCRCFInString(story.Story.CustomField91)
|
||||
customStoryFields["CustomField94"] = s.removeCRCFInString(story.Story.CustomField94)
|
||||
customStoryFields["CustomField95"] = s.removeCRCFInString(story.Story.CustomField95)
|
||||
customStoryFields["CustomField96"] = s.removeCRCFInString(story.Story.CustomField96)
|
||||
customStoryFields["CustomField98"] = s.removeCRCFInString(story.Story.CustomField98)
|
||||
customStoryFields["CustomField100"] = s.removeCRCFInString(story.Story.CustomField100)
|
||||
|
||||
var jsonStr string
|
||||
if mJSON, jsonErr := json.Marshal(customStoryFields); jsonErr == nil {
|
||||
jsonStr = string(mJSON)
|
||||
}
|
||||
|
||||
storyStr := s.removeCRCFInString(story.Story.ID+"\x01"+story.Story.Name+"\x01"+story.Story.WorkspaceID+"\x01"+
|
||||
story.Story.Creator+"\x01"+story.Story.Created+"\x01"+story.Story.Modified+"\x01"+
|
||||
story.Story.Status+"\x01"+story.Story.Owner+"\x01"+story.Story.Cc+"\x01"+
|
||||
story.Story.Begin+"\x01"+story.Story.Due+"\x01"+story.Story.Size+"\x01"+
|
||||
story.Story.Priority+"\x01"+story.Story.Developer+"\x01"+story.Story.IterationID+"\x01"+
|
||||
story.Story.TestFocus+"\x01"+story.Story.Type+"\x01"+story.Story.Source+"\x01"+
|
||||
story.Story.Module+"\x01"+story.Story.Version+"\x01"+story.Story.Completed+"\x01"+
|
||||
categoryName+"\x01"+story.Story.ParentID+"\x01"+story.Story.ChildrenID+"\x01"+
|
||||
story.Story.AncestorID+"\x01"+story.Story.BusinessValue+"\x01"+story.Story.Effort+"\x01"+
|
||||
story.Story.EffortCompleted+"\x01"+story.Story.Exceed+"\x01"+story.Story.Remain+"\x01"+
|
||||
releaseName+"\x01"+story.Story.CustomField92+"\x01"+story.Story.CustomField93+"\x01"+
|
||||
story.Story.CustomField97+"\x01"+story.Story.CustomField99+"\x01"+writeTime+"\x01"+jsonStr) + "\n"
|
||||
|
||||
if _, err = io.WriteString(storyFile, storyStr); err != nil {
|
||||
log.Error("Error happened when writing story info to file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
pattern := `("field":")(.*)(","value_before":")(.*)(","value_after":")(.*)(")`
|
||||
if storyChangeItems, err = s.storyChangeItems(workspaceID, story.Story.ID, pattern, s.c.Tapd.SCPS); err != nil {
|
||||
log.Error("Error happened when fetching story change items for story %s. Error:%v", story.Story.ID, err)
|
||||
return
|
||||
}
|
||||
for _, item := range storyChangeItems {
|
||||
itemBeforeVal := item.ValueBefore
|
||||
if s.isStringAsDigit(item.ValueBefore) {
|
||||
itemBeforeVal = s.handleReleaseNameForStoryChange(workspaceID, item.ValueBefore)
|
||||
}
|
||||
|
||||
itemAfterVal := item.ValueAfter
|
||||
if s.isStringAsDigit(item.ValueAfter) {
|
||||
itemAfterVal = s.handleReleaseNameForStoryChange(workspaceID, item.ValueAfter)
|
||||
}
|
||||
|
||||
itemStr := s.removeCRCFInString(item.ID+"\x01"+item.WorkspaceID+"\x01"+item.StoryID+"\x01"+
|
||||
item.Number+"\x01"+item.Field+"\x01"+item.Creator+"\x01"+
|
||||
item.Created+"\x01"+itemBeforeVal+"\x01"+itemAfterVal+"\x01"+
|
||||
item.ChangeSummay+"\x01"+item.Comment+"\x01"+item.EntityType+"\x01"+writeTime) + "\n"
|
||||
|
||||
if _, err = io.WriteString(changeFile, itemStr); err != nil {
|
||||
log.Error("Error happened when writing story change item to file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info("Writing story and story change info to file: done")
|
||||
return
|
||||
}
|
||||
|
||||
//writeIterationDataToFile write iteration info to file
|
||||
func (s *Service) writeIterationDataToFile(c context.Context) (err error) {
|
||||
var (
|
||||
iterationRes *model.IterationResponse
|
||||
iterationFile *os.File
|
||||
)
|
||||
|
||||
log.Info("Writing iteration info to file: start")
|
||||
|
||||
writeTime := time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
if _, fileErr := os.Stat(s.c.Tapd.IterationFilePath); fileErr == nil {
|
||||
if err = os.Remove(s.c.Tapd.IterationFilePath); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if _, err = os.Create(s.c.Tapd.IterationFilePath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if iterationFile, err = os.OpenFile(s.c.Tapd.IterationFilePath, os.O_WRONLY|os.O_APPEND, 0666); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, workspaceID := range s.c.Tapd.IterationWorkspaceIDs {
|
||||
iterationURL := fmt.Sprintf(model.AllIterationURL, workspaceID, s.c.Tapd.IPS) + "&page=%d"
|
||||
if iterationRes, err = s.dao.AllIterations(s.c.Tapd.IPS, iterationURL); err != nil {
|
||||
log.Error("Error happened when fetching iterations from tapd. Error:(%dv)", err)
|
||||
continue
|
||||
}
|
||||
for _, iteration := range iterationRes.Data {
|
||||
customFields := make(map[string]string)
|
||||
customFields["CustomField1"] = s.removeCRCFInString(iteration.Iteration.CustomField1)
|
||||
customFields["CustomField2"] = s.removeCRCFInString(iteration.Iteration.CustomField2)
|
||||
customFields["CustomField3"] = s.removeCRCFInString(iteration.Iteration.CustomField3)
|
||||
customFields["CustomField4"] = s.removeCRCFInString(iteration.Iteration.CustomField4)
|
||||
customFields["CustomField5"] = s.removeCRCFInString(iteration.Iteration.CustomField5)
|
||||
customFields["CustomField6"] = s.removeCRCFInString(iteration.Iteration.CustomField6)
|
||||
customFields["CustomField7"] = s.removeCRCFInString(iteration.Iteration.CustomField7)
|
||||
customFields["CustomField8"] = s.removeCRCFInString(iteration.Iteration.CustomField8)
|
||||
customFields["CustomField9"] = s.removeCRCFInString(iteration.Iteration.CustomField9)
|
||||
customFields["CustomField10"] = s.removeCRCFInString(iteration.Iteration.CustomField10)
|
||||
customFields["CustomField11"] = s.removeCRCFInString(iteration.Iteration.CustomField11)
|
||||
customFields["CustomField12"] = s.removeCRCFInString(iteration.Iteration.CustomField12)
|
||||
customFields["CustomField13"] = s.removeCRCFInString(iteration.Iteration.CustomField13)
|
||||
customFields["CustomField14"] = s.removeCRCFInString(iteration.Iteration.CustomField14)
|
||||
customFields["CustomField15"] = s.removeCRCFInString(iteration.Iteration.CustomField15)
|
||||
customFields["CustomField16"] = s.removeCRCFInString(iteration.Iteration.CustomField16)
|
||||
customFields["CustomField17"] = s.removeCRCFInString(iteration.Iteration.CustomField17)
|
||||
customFields["CustomField18"] = s.removeCRCFInString(iteration.Iteration.CustomField18)
|
||||
customFields["CustomField19"] = s.removeCRCFInString(iteration.Iteration.CustomField19)
|
||||
customFields["CustomField20"] = s.removeCRCFInString(iteration.Iteration.CustomField20)
|
||||
customFields["CustomField21"] = s.removeCRCFInString(iteration.Iteration.CustomField21)
|
||||
customFields["CustomField22"] = s.removeCRCFInString(iteration.Iteration.CustomField22)
|
||||
customFields["CustomField23"] = s.removeCRCFInString(iteration.Iteration.CustomField23)
|
||||
customFields["CustomField24"] = s.removeCRCFInString(iteration.Iteration.CustomField24)
|
||||
customFields["CustomField25"] = s.removeCRCFInString(iteration.Iteration.CustomField25)
|
||||
customFields["CustomField26"] = s.removeCRCFInString(iteration.Iteration.CustomField26)
|
||||
customFields["CustomField27"] = s.removeCRCFInString(iteration.Iteration.CustomField27)
|
||||
customFields["CustomField28"] = s.removeCRCFInString(iteration.Iteration.CustomField28)
|
||||
customFields["CustomField29"] = s.removeCRCFInString(iteration.Iteration.CustomField29)
|
||||
customFields["CustomField30"] = s.removeCRCFInString(iteration.Iteration.CustomField30)
|
||||
customFields["CustomField31"] = s.removeCRCFInString(iteration.Iteration.CustomField31)
|
||||
customFields["CustomField32"] = s.removeCRCFInString(iteration.Iteration.CustomField32)
|
||||
customFields["CustomField33"] = s.removeCRCFInString(iteration.Iteration.CustomField33)
|
||||
customFields["CustomField34"] = s.removeCRCFInString(iteration.Iteration.CustomField34)
|
||||
customFields["CustomField35"] = s.removeCRCFInString(iteration.Iteration.CustomField35)
|
||||
customFields["CustomField36"] = s.removeCRCFInString(iteration.Iteration.CustomField36)
|
||||
customFields["CustomField37"] = s.removeCRCFInString(iteration.Iteration.CustomField37)
|
||||
customFields["CustomField38"] = s.removeCRCFInString(iteration.Iteration.CustomField38)
|
||||
customFields["CustomField39"] = s.removeCRCFInString(iteration.Iteration.CustomField39)
|
||||
customFields["CustomField40"] = s.removeCRCFInString(iteration.Iteration.CustomField40)
|
||||
customFields["CustomField41"] = s.removeCRCFInString(iteration.Iteration.CustomField41)
|
||||
customFields["CustomField42"] = s.removeCRCFInString(iteration.Iteration.CustomField42)
|
||||
customFields["CustomField43"] = s.removeCRCFInString(iteration.Iteration.CustomField43)
|
||||
customFields["CustomField44"] = s.removeCRCFInString(iteration.Iteration.CustomField44)
|
||||
customFields["CustomField45"] = s.removeCRCFInString(iteration.Iteration.CustomField45)
|
||||
customFields["CustomField46"] = s.removeCRCFInString(iteration.Iteration.CustomField46)
|
||||
customFields["CustomField47"] = s.removeCRCFInString(iteration.Iteration.CustomField47)
|
||||
customFields["CustomField48"] = s.removeCRCFInString(iteration.Iteration.CustomField48)
|
||||
customFields["CustomField49"] = s.removeCRCFInString(iteration.Iteration.CustomField49)
|
||||
customFields["CustomField50"] = s.removeCRCFInString(iteration.Iteration.CustomField50)
|
||||
|
||||
var jsonStr string
|
||||
if mJSON, jsonErr := json.Marshal(customFields); jsonErr == nil {
|
||||
jsonStr = string(mJSON)
|
||||
}
|
||||
|
||||
iterationStr := s.removeCRCFInString(iteration.Iteration.ID+"\x01"+
|
||||
iteration.Iteration.Name+"\x01"+
|
||||
iteration.Iteration.WorkspaceID+"\x01"+
|
||||
iteration.Iteration.Startdate+"\x01"+
|
||||
iteration.Iteration.Enddate+"\x01"+
|
||||
iteration.Iteration.Status+"\x01"+
|
||||
iteration.Iteration.ReleaseID+"\x01"+
|
||||
iteration.Iteration.Description+"\x01"+
|
||||
iteration.Iteration.Creator+"\x01"+
|
||||
iteration.Iteration.Created+"\x01"+
|
||||
iteration.Iteration.Modified+"\x01"+
|
||||
iteration.Iteration.Completed+"\x01"+
|
||||
writeTime+"\x01"+jsonStr) + "\n"
|
||||
if _, err = io.WriteString(iterationFile, iterationStr); err != nil {
|
||||
log.Error("Error happened when writing iteration into to file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Writing iteration info to file: done")
|
||||
return
|
||||
}
|
||||
|
||||
// writeBugDataToFile write Bug Data To File.
|
||||
func (s *Service) writeBugDataToFile(c context.Context) (err error) {
|
||||
var (
|
||||
bugRes *model.BugResponse
|
||||
bugFile *os.File
|
||||
writeTime = time.Now().Format("2006-01-02 15:04:05")
|
||||
)
|
||||
|
||||
log.Info("Writing bug info to file: start")
|
||||
|
||||
if _, fileErr := os.Stat(s.c.Tapd.BugFilePath); fileErr == nil {
|
||||
if err = os.Remove(s.c.Tapd.BugFilePath); err != nil {
|
||||
log.Error("Error happened when removing bug file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if bugFile, err = os.Create(s.c.Tapd.BugFilePath); err != nil {
|
||||
log.Error("Error happened when creating bug file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
defer bugFile.Close()
|
||||
|
||||
for _, workspaceID := range s.c.Tapd.BugWorkspaceIDs {
|
||||
bugURL := fmt.Sprintf(model.BugURL, workspaceID, s.c.Tapd.SPS) + "&page=%d"
|
||||
if bugRes, err = s.dao.AllBugs(s.c.Tapd.SPS, bugURL); err != nil {
|
||||
log.Error("Error happened when fetching all bugs for workspace %s. Error: %v", workspaceID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, bug := range bugRes.Data {
|
||||
//releaseId change to release name
|
||||
releaseName := s.handleReleaseName(bug.Bug.WorkspaceID, bug.Bug.ReleaseID)
|
||||
customBugFields := make(map[string]string)
|
||||
customBugFields["CustomField10"] = s.removeCRCFInString(bug.Bug.CustomField10)
|
||||
customBugFields["CustomField11"] = s.removeCRCFInString(bug.Bug.CustomField11)
|
||||
customBugFields["CustomField12"] = s.removeCRCFInString(bug.Bug.CustomField12)
|
||||
customBugFields["CustomField13"] = s.removeCRCFInString(bug.Bug.CustomField13)
|
||||
customBugFields["CustomField14"] = s.removeCRCFInString(bug.Bug.CustomField14)
|
||||
customBugFields["CustomField15"] = s.removeCRCFInString(bug.Bug.CustomField15)
|
||||
customBugFields["CustomField16"] = s.removeCRCFInString(bug.Bug.CustomField16)
|
||||
customBugFields["CustomField17"] = s.removeCRCFInString(bug.Bug.CustomField17)
|
||||
customBugFields["CustomField18"] = s.removeCRCFInString(bug.Bug.CustomField18)
|
||||
customBugFields["CustomField19"] = s.removeCRCFInString(bug.Bug.CustomField19)
|
||||
customBugFields["CustomField20"] = s.removeCRCFInString(bug.Bug.CustomField20)
|
||||
customBugFields["CustomField21"] = s.removeCRCFInString(bug.Bug.CustomField21)
|
||||
customBugFields["CustomField22"] = s.removeCRCFInString(bug.Bug.CustomField22)
|
||||
customBugFields["CustomField23"] = s.removeCRCFInString(bug.Bug.CustomField23)
|
||||
customBugFields["CustomField24"] = s.removeCRCFInString(bug.Bug.CustomField24)
|
||||
customBugFields["CustomField25"] = s.removeCRCFInString(bug.Bug.CustomField25)
|
||||
customBugFields["CustomField26"] = s.removeCRCFInString(bug.Bug.CustomField26)
|
||||
customBugFields["CustomField27"] = s.removeCRCFInString(bug.Bug.CustomField27)
|
||||
customBugFields["CustomField28"] = s.removeCRCFInString(bug.Bug.CustomField28)
|
||||
customBugFields["CustomField29"] = s.removeCRCFInString(bug.Bug.CustomField29)
|
||||
customBugFields["CustomField30"] = s.removeCRCFInString(bug.Bug.CustomField30)
|
||||
customBugFields["CustomField31"] = s.removeCRCFInString(bug.Bug.CustomField31)
|
||||
customBugFields["CustomField32"] = s.removeCRCFInString(bug.Bug.CustomField32)
|
||||
customBugFields["CustomField33"] = s.removeCRCFInString(bug.Bug.CustomField33)
|
||||
customBugFields["CustomField34"] = s.removeCRCFInString(bug.Bug.CustomField34)
|
||||
customBugFields["CustomField35"] = s.removeCRCFInString(bug.Bug.CustomField35)
|
||||
customBugFields["CustomField36"] = s.removeCRCFInString(bug.Bug.CustomField36)
|
||||
customBugFields["CustomField37"] = s.removeCRCFInString(bug.Bug.CustomField37)
|
||||
customBugFields["CustomField38"] = s.removeCRCFInString(bug.Bug.CustomField38)
|
||||
customBugFields["CustomField39"] = s.removeCRCFInString(bug.Bug.CustomField39)
|
||||
customBugFields["CustomField40"] = s.removeCRCFInString(bug.Bug.CustomField40)
|
||||
customBugFields["CustomField41"] = s.removeCRCFInString(bug.Bug.CustomField41)
|
||||
customBugFields["CustomField42"] = s.removeCRCFInString(bug.Bug.CustomField42)
|
||||
customBugFields["CustomField43"] = s.removeCRCFInString(bug.Bug.CustomField43)
|
||||
customBugFields["CustomField44"] = s.removeCRCFInString(bug.Bug.CustomField44)
|
||||
customBugFields["CustomField45"] = s.removeCRCFInString(bug.Bug.CustomField45)
|
||||
customBugFields["CustomField46"] = s.removeCRCFInString(bug.Bug.CustomField46)
|
||||
customBugFields["CustomField47"] = s.removeCRCFInString(bug.Bug.CustomField47)
|
||||
customBugFields["CustomField48"] = s.removeCRCFInString(bug.Bug.CustomField48)
|
||||
customBugFields["CustomField49"] = s.removeCRCFInString(bug.Bug.CustomField49)
|
||||
customBugFields["CustomField50"] = s.removeCRCFInString(bug.Bug.CustomField50)
|
||||
|
||||
var jsonStr string
|
||||
if mJSON, jsonErr := json.Marshal(customBugFields); jsonErr == nil {
|
||||
jsonStr = string(mJSON)
|
||||
}
|
||||
|
||||
bugStr := s.removeCRCFInString(bug.Bug.ID+"\x01"+bug.Bug.Title+"\x01"+bug.Bug.Description+"\x01"+bug.Bug.Priority+"\x01"+
|
||||
bug.Bug.Severity+"\x01"+bug.Bug.Module+"\x01"+bug.Bug.Status+"\x01"+bug.Bug.Reporter+"\x01"+
|
||||
bug.Bug.Deadline+"\x01"+bug.Bug.Created+"\x01"+bug.Bug.BugType+"\x01"+bug.Bug.Resolved+"\x01"+
|
||||
bug.Bug.Closed+"\x01"+bug.Bug.Modified+"\x01"+bug.Bug.LastModify+"\x01"+bug.Bug.Auditer+"\x01"+
|
||||
bug.Bug.DE+"\x01"+bug.Bug.VersionTest+"\x01"+bug.Bug.VersionReport+"\x01"+bug.Bug.VersionClose+"\x01"+
|
||||
bug.Bug.VersionFix+"\x01"+bug.Bug.BaselineFind+"\x01"+bug.Bug.BaselineJoin+"\x01"+bug.Bug.BaselineClose+"\x01"+
|
||||
bug.Bug.BaselineTest+"\x01"+bug.Bug.SourcePhase+"\x01"+bug.Bug.TE+"\x01"+bug.Bug.CurrentOwner+"\x01"+
|
||||
bug.Bug.IterationID+"\x01"+bug.Bug.Resolution+"\x01"+bug.Bug.Source+"\x01"+bug.Bug.OriginPhase+"\x01"+
|
||||
bug.Bug.Confirmer+"\x01"+bug.Bug.Milestone+"\x01"+bug.Bug.Participator+"\x01"+bug.Bug.Closer+"\x01"+
|
||||
bug.Bug.Platform+"\x01"+bug.Bug.OS+"\x01"+bug.Bug.TestType+"\x01"+bug.Bug.TestPhase+"\x01"+
|
||||
bug.Bug.Frequency+"\x01"+bug.Bug.CC+"\x01"+bug.Bug.RegressionNumber+"\x01"+bug.Bug.Flows+"\x01"+
|
||||
bug.Bug.Feature+"\x01"+bug.Bug.TestMode+"\x01"+bug.Bug.Estimate+"\x01"+bug.Bug.IssueID+"\x01"+
|
||||
bug.Bug.CreatedFrom+"\x01"+bug.Bug.InProgressTime+"\x01"+bug.Bug.VerifyTime+"\x01"+bug.Bug.RejectTime+"\x01"+
|
||||
bug.Bug.ReopenTime+"\x01"+bug.Bug.AuditTime+"\x01"+bug.Bug.SuspendTime+"\x01"+bug.Bug.Due+"\x01"+
|
||||
bug.Bug.Begin+"\x01"+releaseName+"\x01"+bug.Bug.WorkspaceID+"\x01"+bug.Bug.CustomFieldOne+"\x01"+
|
||||
bug.Bug.CustomFieldTwo+"\x01"+bug.Bug.CustomFieldThree+"\x01"+bug.Bug.CustomFieldFour+"\x01"+bug.Bug.CustomFieldFive+"\x01"+
|
||||
bug.Bug.CustomField6+"\x01"+bug.Bug.CustomField7+"\x01"+bug.Bug.CustomField8+"\x01"+bug.Bug.CustomField9+"\x01"+
|
||||
writeTime+"\x01"+jsonStr) + "\n"
|
||||
|
||||
if _, err = io.WriteString(bugFile, bugStr); err != nil {
|
||||
log.Error("Error happened when writing bug info to file. Error:(%v)", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info("Writing bug info to file: done")
|
||||
return
|
||||
}
|
||||
|
||||
// handleReleaseName handle Release Name
|
||||
func (s *Service) handleReleaseName(workSpaceID, releaseID string) (releaseName string) {
|
||||
if len(releaseID) < 10 {
|
||||
return releaseID
|
||||
}
|
||||
|
||||
var err error
|
||||
if releaseID == "0" || strings.TrimSpace(releaseID) == "" {
|
||||
releaseName = ""
|
||||
return
|
||||
}
|
||||
|
||||
if releaseName, err = s.dao.ReleaseName(workSpaceID, releaseID); err != nil {
|
||||
if workSpaceID == "20060791" {
|
||||
if releaseName, err = s.dao.ReleaseName("20055921", releaseID); err != nil {
|
||||
log.Error("Error happened when fetch release for replace workspace:%s, release id: %s, err %v", "20055921", releaseID, err)
|
||||
releaseName = releaseID
|
||||
}
|
||||
} else {
|
||||
log.Error("Error happened when fetch release for workspace:%s, release id: %s, err %v", workSpaceID, releaseID, err)
|
||||
releaseName = releaseID
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// handleReleaseName handle Release Name
|
||||
func (s *Service) handleReleaseNameForStoryChange(workSpaceID, releaseID string) (releaseName string) {
|
||||
if len(releaseID) < 10 {
|
||||
return releaseID
|
||||
}
|
||||
|
||||
var err error
|
||||
if releaseID == "0" || strings.TrimSpace(releaseID) == "" {
|
||||
releaseName = ""
|
||||
return
|
||||
}
|
||||
|
||||
if releaseName, err = s.dao.ReleaseName(workSpaceID, releaseID); err != nil {
|
||||
if workSpaceID == "20060791" {
|
||||
if releaseName, err = s.dao.ReleaseName("20055921", releaseID); err != nil {
|
||||
log.Error("Error happened when fetch release for replace workspace:%s, release id: %s, err %v", "20055921", releaseID, err)
|
||||
releaseName = releaseID
|
||||
} else {
|
||||
releaseName = "发布计划#" + releaseName
|
||||
}
|
||||
} else {
|
||||
log.Error("Error happened when fetch release for workspace:%s, release id: %s, err %v", workSpaceID, releaseID, err)
|
||||
releaseName = releaseID
|
||||
}
|
||||
} else {
|
||||
releaseName = "发布计划#" + releaseName
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// handleCategoryPreName handle CategoryPre Name
|
||||
func (s *Service) handleCategoryPreName(workSpaceID, categoryID string) (categoryName string) {
|
||||
var err error
|
||||
if len(categoryID) < 10 {
|
||||
return categoryID
|
||||
}
|
||||
|
||||
if categoryID == "0" || strings.TrimSpace(categoryID) == "" {
|
||||
categoryName = ""
|
||||
return
|
||||
}
|
||||
|
||||
if categoryName, err = s.dao.CategoryPreName(workSpaceID, categoryID); err != nil {
|
||||
log.Error("Error happened when fetch release for workspace:%s, category id: %s, err %v", workSpaceID, categoryID, err)
|
||||
categoryName = categoryID
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isStringAsDigit is String As Digit.
|
||||
func (s *Service) isStringAsDigit(str string) bool {
|
||||
if len(str) < 10 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range str {
|
||||
if !unicode.IsDigit(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
338
app/service/ep/footman/service/bugly.go
Normal file
338
app/service/ep/footman/service/bugly.go
Normal file
@ -0,0 +1,338 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
const (
|
||||
_issueLink = "/v2/crash-reporting/errors/%s/%s/report?pid=%s&searchType=detail&version=%s&start=0&date=all"
|
||||
)
|
||||
|
||||
// Issue Issue.
|
||||
func (s *Service) Issue(c context.Context, version string) (status int, err error) {
|
||||
status = 0
|
||||
|
||||
bugIssueRequest := &model.BugIssueRequest{
|
||||
Version: version,
|
||||
StartNum: 0,
|
||||
Rows: 100,
|
||||
}
|
||||
|
||||
_, err = s.dao.BuglyIssue(c, bugIssueRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateToken Update Token.
|
||||
func (s *Service) UpdateToken(c context.Context) (status int, err error) {
|
||||
status = 0
|
||||
if err = s.dao.UpdateToken(); err != nil {
|
||||
status = -1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SaveIssue Save Issue.
|
||||
func (s *Service) SaveIssue(c context.Context, version string) (status int, err error) {
|
||||
s.cache.Save(func() {
|
||||
s.GetSaveIssues(context.Background(), version)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// SaveIssues Save Issues.
|
||||
func (s *Service) SaveIssues(c context.Context) (status int, err error) {
|
||||
s.cache.Save(func() {
|
||||
s.GetSaveIssuesWithMultiVersion(context.Background())
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// TaskGetSaveIssues Task Get Save Issues.
|
||||
func (s *Service) TaskGetSaveIssues() {
|
||||
fmt.Println("task start")
|
||||
s.GetSaveIssues(context.Background(), "5.29.1")
|
||||
}
|
||||
|
||||
// GetSaveIssuesWithMultiVersion Get Save Issues With Multi Version.
|
||||
func (s *Service) GetSaveIssuesWithMultiVersion(c context.Context) (status int, err error) {
|
||||
var versions []string
|
||||
if versions, err = ReadLine(s.c.Bugly.Version); err != nil {
|
||||
log.Error("failed to get version with file %s", s.c.Bugly.Version)
|
||||
return
|
||||
}
|
||||
|
||||
for _, version := range versions {
|
||||
|
||||
if _, err = s.GetSaveIssues(context.Background(), version); err != nil {
|
||||
log.Error("failed to execute function version %s %+v", version, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetSaveIssues Get Save Issues.
|
||||
func (s *Service) GetSaveIssues(c context.Context, versions string) (status int, err error) {
|
||||
versionAry := strings.Split(versions, ",")
|
||||
if len(versionAry) != 3 {
|
||||
fmt.Print("version 每行参数为3个 项目id,平台id,版本id")
|
||||
return
|
||||
}
|
||||
|
||||
projectID := strings.TrimSpace(versionAry[0])
|
||||
platformID := strings.TrimSpace(versionAry[1])
|
||||
version := strings.TrimSpace(versionAry[2])
|
||||
versionCnt := 0
|
||||
if strings.Contains(version, "*") {
|
||||
versionPatten := strings.Replace(version, "*", "", -1)
|
||||
var bugVersionList []*model.BugVersion
|
||||
if bugVersionList, err = s.dao.BugVersion(c, projectID, platformID); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Print(len(bugVersionList))
|
||||
for _, bugVersion := range bugVersionList {
|
||||
if strings.Contains(bugVersion.Name, versionPatten) {
|
||||
versionCnt = versionCnt + 1
|
||||
|
||||
s.GetSaveIssuesWithPerVersion(c, projectID, platformID, bugVersion.Name)
|
||||
|
||||
}
|
||||
}
|
||||
fmt.Print("#########################" + strconv.Itoa(versionCnt))
|
||||
|
||||
} else {
|
||||
versionCnt = versionCnt + 1
|
||||
s.GetSaveIssuesWithPerVersion(c, projectID, platformID, version)
|
||||
}
|
||||
fmt.Print("#########################" + strconv.Itoa(versionCnt))
|
||||
return
|
||||
}
|
||||
|
||||
// GetSaveIssuesWithPerVersion Get Save Issues With Per Version.
|
||||
func (s *Service) GetSaveIssuesWithPerVersion(c context.Context, projectID, platformID, version string) (status int, err error) {
|
||||
fmt.Print("start GetSaveIssues version: " + version)
|
||||
var (
|
||||
requestCnt int
|
||||
taskStatus int
|
||||
buglyRet *model.BugRet
|
||||
lastTimeInSQL time.Time
|
||||
currentLastTime time.Time
|
||||
timeLayout = "2006-01-02 15:04:05"
|
||||
lastIssue string
|
||||
exceptionType string
|
||||
)
|
||||
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
|
||||
//get last time in sql
|
||||
if lastTimeInSQL, taskStatus, err = s.GetVersionIssueLastTime(version); err != nil || taskStatus == 1 {
|
||||
return
|
||||
}
|
||||
|
||||
if platformID == "1" {
|
||||
exceptionType = "Crash,Native"
|
||||
} else if platformID == "2" {
|
||||
exceptionType = "Crash,ExtensionCrash"
|
||||
}
|
||||
|
||||
//get issue total count
|
||||
bugIssueRequest := &model.BugIssueRequest{
|
||||
ProjectID: projectID,
|
||||
PlatformID: platformID,
|
||||
Version: version,
|
||||
ExceptionType: exceptionType,
|
||||
StartNum: 0,
|
||||
Rows: 1,
|
||||
}
|
||||
if buglyRet, err = s.dao.BuglyIssueAndRetry(c, bugIssueRequest); err != nil || len(buglyRet.BugIssues) < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
requestCnt = int(buglyRet.NumFound/100) + 1
|
||||
if requestCnt > 50 {
|
||||
requestCnt = 50
|
||||
}
|
||||
|
||||
rs := []rune(buglyRet.BugIssues[0].LastTime)
|
||||
|
||||
if currentLastTime, err = time.ParseInLocation(timeLayout, string(rs[:len(rs)-4]), loc); err != nil || currentLastTime.Before(lastTimeInSQL) {
|
||||
return
|
||||
}
|
||||
|
||||
issueLastTimeTmp := &model.IssueLastTime{
|
||||
Version: version,
|
||||
TaskStatus: 1,
|
||||
}
|
||||
if err = s.dao.UpdateTaskStatus(issueLastTimeTmp); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//get new issue list
|
||||
for i := 0; i < requestCnt; i++ {
|
||||
innerBreak := false
|
||||
/* tmpRequest := &model.BugIssueRequest{
|
||||
ProjectID: projectId,
|
||||
PlatformID: platformId,
|
||||
Version: version,
|
||||
StartNum: 100 * i,
|
||||
Rows: 100,
|
||||
}*/
|
||||
|
||||
bugIssueRequest.StartNum = 100 * i
|
||||
bugIssueRequest.Rows = 100
|
||||
|
||||
var ret *model.BugRet
|
||||
if ret, err = s.dao.BuglyIssueAndRetry(c, bugIssueRequest); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
for _, issueDto := range ret.BugIssues {
|
||||
var (
|
||||
issueTime time.Time
|
||||
bugIssueDetail *model.BugIssueDetail
|
||||
tagStr string
|
||||
bugDetail = "no detail"
|
||||
)
|
||||
tmpTime := []rune(issueDto.LastTime)
|
||||
issueTime, _ = time.ParseInLocation(timeLayout, string(tmpTime[:len(tmpTime)-4]), loc)
|
||||
|
||||
//issue时间早于库里面最新时间的,跳出
|
||||
if issueTime.Before(lastTimeInSQL) {
|
||||
innerBreak = true
|
||||
break
|
||||
}
|
||||
|
||||
for _, bugTag := range issueDto.Tags {
|
||||
tagStr = tagStr + bugTag.TagName + ","
|
||||
}
|
||||
|
||||
var tmpIssueRecord *model.IssueRecord
|
||||
if tmpIssueRecord, err = s.dao.GetIssueRecord(issueDto.IssueID, issueDto.Version); err != nil {
|
||||
log.Error("d.GetSaveIssues url(%s) err(%v)", "GetSaveIssues", err)
|
||||
continue
|
||||
}
|
||||
if tmpIssueRecord.ID != 0 {
|
||||
//update
|
||||
issueRecord := &model.IssueRecord{
|
||||
IssueNo: issueDto.IssueID,
|
||||
Title: issueDto.Title,
|
||||
LastTime: issueTime,
|
||||
HappenTimes: issueDto.Count,
|
||||
UserTimes: issueDto.UserCount,
|
||||
Version: issueDto.Version,
|
||||
Tags: tagStr,
|
||||
}
|
||||
s.dao.UpdateIssueRecord(issueRecord)
|
||||
} else {
|
||||
//create
|
||||
if bugIssueDetail, err = s.dao.BuglyIssueDetailAndRetry(c, projectID, platformID, issueDto.IssueID); err == nil {
|
||||
bugDetail = bugIssueDetail.CallStack
|
||||
}
|
||||
issueURL := s.c.Bugly.Host + fmt.Sprintf(_issueLink, projectID, issueDto.IssueID, platformID, version)
|
||||
issueRecord := &model.IssueRecord{
|
||||
IssueNo: issueDto.IssueID,
|
||||
Title: issueDto.Title,
|
||||
LastTime: issueTime,
|
||||
HappenTimes: issueDto.Count,
|
||||
UserTimes: issueDto.UserCount,
|
||||
Version: issueDto.Version,
|
||||
Tags: tagStr,
|
||||
Detail: bugDetail,
|
||||
ExceptionMsg: issueDto.ExceptionMsg,
|
||||
KeyStack: issueDto.KeyStack,
|
||||
IssueLink: issueURL,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
s.dao.InsertIssueRecord(issueRecord)
|
||||
}
|
||||
lastIssue = issueDto.IssueID
|
||||
}
|
||||
if innerBreak {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
issueLastTime := &model.IssueLastTime{
|
||||
LastTime: currentLastTime,
|
||||
Version: version,
|
||||
TaskStatus: 0,
|
||||
LastIssue: lastIssue,
|
||||
}
|
||||
|
||||
if err = s.dao.UpdateTaskStatus(issueLastTime); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.dao.UpdateLastIssueTime(issueLastTime); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = s.dao.UpdateLastIssue(issueLastTime); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Print("end GetSaveIssues version:" + version)
|
||||
return
|
||||
}
|
||||
|
||||
// GetVersionIssueLastTime Ge tVersion Issue LastTime.
|
||||
func (s *Service) GetVersionIssueLastTime(version string) (lastTime time.Time, taskStatus int, err error) {
|
||||
var issueLastTime *model.IssueLastTime
|
||||
if issueLastTime, err = s.dao.GetIssueLastTime(version); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if issueLastTime.ID != 0 {
|
||||
lastTime = issueLastTime.LastTime
|
||||
taskStatus = issueLastTime.TaskStatus
|
||||
|
||||
} else {
|
||||
toBeCharge := "2018-01-01 00:00:00"
|
||||
timeLayout := "2006-01-02 15:04:05"
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
lastTime, err = time.ParseInLocation(timeLayout, toBeCharge, loc)
|
||||
taskStatus = 0
|
||||
issueLastTime := &model.IssueLastTime{
|
||||
LastTime: lastTime,
|
||||
Version: version,
|
||||
TaskStatus: taskStatus,
|
||||
}
|
||||
s.dao.InsertIssueLastTime(issueLastTime)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadLine Read Line.
|
||||
func ReadLine(fileName string) (lines []string, err error) {
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
buf := bufio.NewReader(f)
|
||||
for {
|
||||
var line string
|
||||
line, err = buf.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
lines = append(lines, line)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
237
app/service/ep/footman/service/bugly2tapd.go
Normal file
237
app/service/ep/footman/service/bugly2tapd.go
Normal file
@ -0,0 +1,237 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
// AsyncBuglyInsertTapd Async Bugly Insert Tapd.
|
||||
func (s *Service) AsyncBuglyInsertTapd(c context.Context) (err error) {
|
||||
return s.cache.Save(func() {
|
||||
s.BuglyInsertTapd(context.Background())
|
||||
})
|
||||
}
|
||||
|
||||
// BuglyInsertTapd Bugly Insert Tapd.
|
||||
func (s *Service) BuglyInsertTapd(c context.Context) (err error) {
|
||||
for _, projectID := range s.c.Bugly2Tapd.ProjectIds {
|
||||
var (
|
||||
issueRecords []*model.IssueRecord
|
||||
bugTemplate *model.BugTemplate
|
||||
)
|
||||
|
||||
if bugTemplate, err = s.dao.FindBugTemplates(projectID); err != nil {
|
||||
log.Error("FindBugTemplates projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if issueRecords, err = s.dao.GetIssueRecordNotInTapd(bugTemplate.IssueFilterSQL); err != nil {
|
||||
log.Error("GetIssueRecordNotInTapd projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, issueRecord := range issueRecords {
|
||||
var (
|
||||
bug *model.Bug
|
||||
bugID string
|
||||
)
|
||||
if bug, err = s.getBugModel(issueRecord, bugTemplate); err != nil {
|
||||
log.Error("getLiveIOSBugModel error (%v)", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if bugID, err = s.dao.CreateBug(bug); err != nil && bugID == "" {
|
||||
log.Error("CreateBug error (%v)", err)
|
||||
continue
|
||||
}
|
||||
fmt.Println(bugID)
|
||||
|
||||
if err = s.dao.UpdateIssueRecordTapdBugID(issueRecord.ID, bugID); err != nil {
|
||||
log.Error("UpdateIssueRecordTapdBugID error (%v)", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
log.Info("finish to insert bugly into tapd [%s] status,", projectID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) getBugModel(issueRecord *model.IssueRecord, bugTemplate *model.BugTemplate) (bug *model.Bug, err error) {
|
||||
title := fmt.Sprintf(bugTemplate.Title, issueRecord.IssueNo, issueRecord.Version, issueRecord.Title, strconv.FormatInt(issueRecord.HappenTimes, 10), strconv.FormatInt(issueRecord.UserTimes, 10))
|
||||
description := fmt.Sprintf(bugTemplate.Description, title, issueRecord.KeyStack,
|
||||
issueRecord.IssueLink, issueRecord.IssueLink, issueRecord.IssueLink,
|
||||
issueRecord.Detail)
|
||||
|
||||
bug = &model.Bug{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Priority: bugTemplate.Priority,
|
||||
Severity: bugTemplate.Severity,
|
||||
Module: bugTemplate.Module,
|
||||
Status: bugTemplate.Status,
|
||||
Reporter: bugTemplate.Reporter,
|
||||
BugType: bugTemplate.BugType,
|
||||
CurrentOwner: bugTemplate.CurrentOwner,
|
||||
Source: bugTemplate.Source,
|
||||
OriginPhase: bugTemplate.OriginPhase,
|
||||
Platform: bugTemplate.Platform,
|
||||
ReleaseID: bugTemplate.ReleaseID,
|
||||
CustomFieldThree: bugTemplate.CustomFieldThree,
|
||||
CustomFieldFour: bugTemplate.CustomFieldFour,
|
||||
WorkspaceID: bugTemplate.WorkspaceID,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AsyncUpdateBuglyStatusInTapd Async Update Bugly Status In Tapd.
|
||||
func (s *Service) AsyncUpdateBuglyStatusInTapd(c context.Context) (err error) {
|
||||
return s.cache.Save(func() {
|
||||
s.UpdateBuglyStatusInTapd(context.Background())
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBuglyStatusInTapd Update Bugly Status In Tapd.
|
||||
func (s *Service) UpdateBuglyStatusInTapd(c context.Context) (err error) {
|
||||
for _, projectID := range s.c.Bugly2Tapd.ProjectIds {
|
||||
log.Info("start projectID [%s] update status to bugly", projectID)
|
||||
var (
|
||||
issueRecords []*model.IssueRecord
|
||||
bugTemplate *model.BugTemplate
|
||||
)
|
||||
|
||||
if bugTemplate, err = s.dao.FindBugTemplates(projectID); err != nil {
|
||||
log.Error("FindBugTemplates projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if issueRecords, err = s.dao.GetIssueRecordHasInTapd(projectID); err != nil {
|
||||
log.Error("GetIssueRecordNotInTapd projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, issueRecord := range issueRecords {
|
||||
log.Info("handle projectID [%s] bug number [%s]", projectID, issueRecord.TapdBugID)
|
||||
var (
|
||||
bugIssueException *model.IssueException
|
||||
bug *model.Bug
|
||||
)
|
||||
if bugIssueException, err = s.dao.BuglyIssueExceptionList(context.Background(), projectID, bugTemplate.PlatformID, issueRecord.IssueNo); err != nil {
|
||||
log.Error("BuglyIssueExceptionList projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if bug, err = s.dao.BugPre(bugTemplate.WorkspaceID, issueRecord.TapdBugID); err != nil {
|
||||
log.Error("BugPre projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if bugIssueException == nil {
|
||||
continue
|
||||
}
|
||||
switch bugIssueException.Status {
|
||||
// bugly 待处理 -> tap 新
|
||||
case 0:
|
||||
s.updateBugToStatus(bug, "new")
|
||||
// bugly 已处理 -> tap 已解决
|
||||
case 1:
|
||||
s.updateBugToStatus(bug, "resolved")
|
||||
// bugly 处理中 -> tapd 接受处理
|
||||
case 2:
|
||||
s.updateBugToStatus(bug, "in_progress")
|
||||
default:
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
log.Info("finish projectID [%s] update status to bugly", projectID)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) updateBugToStatus(bug *model.Bug, status string) {
|
||||
if bug.Status != status {
|
||||
log.Info("bug [%s] update from %s to %s", bug.ID, bug.Status, status)
|
||||
bug.Status = status
|
||||
updateBug := &model.UpdateBug{
|
||||
Bug: bug,
|
||||
CurrentUser: bug.CurrentOwner,
|
||||
}
|
||||
if err := s.dao.UpdateBug(updateBug); err != nil {
|
||||
log.Error("UpdateBug bugid %s, error (%v)", bug.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AsyncUpdateBugInTapd Async Update Title In Tapd.
|
||||
func (s *Service) AsyncUpdateBugInTapd(c context.Context) (err error) {
|
||||
return s.cache.Save(func() {
|
||||
s.UpdateBugInTapd(context.Background())
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateBugInTapd Update Title In Tapd.
|
||||
func (s *Service) UpdateBugInTapd(c context.Context) (err error) {
|
||||
for _, projectID := range s.c.Bugly2Tapd.ProjectIds {
|
||||
log.Info("start projectID [%s] update title in tapd", projectID)
|
||||
var (
|
||||
issueRecords []*model.IssueRecord
|
||||
bugTemplate *model.BugTemplate
|
||||
)
|
||||
|
||||
if bugTemplate, err = s.dao.FindBugTemplates(projectID); err != nil {
|
||||
log.Error("FindBugTemplates projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if issueRecords, err = s.dao.GetIssueRecordHasInTapd(projectID); err != nil {
|
||||
log.Error("GetIssueRecordNotInTapd projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, issueRecord := range issueRecords {
|
||||
log.Info("handle projectID [%s] bug number [%s]", projectID, issueRecord.TapdBugID)
|
||||
var bug *model.Bug
|
||||
|
||||
// update title
|
||||
title := fmt.Sprintf(bugTemplate.Title, issueRecord.IssueNo, issueRecord.Version, issueRecord.Title, strconv.FormatInt(issueRecord.HappenTimes, 10), strconv.FormatInt(issueRecord.UserTimes, 10))
|
||||
|
||||
if bug, err = s.dao.BugPre(bugTemplate.WorkspaceID, issueRecord.TapdBugID); err != nil {
|
||||
log.Error("BugPre projectId %s, error (%v)", projectID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
bug.Title = title
|
||||
|
||||
// update priority
|
||||
if issueRecord.UserTimes >= 20 || issueRecord.HappenTimes >= 20 {
|
||||
bug.Priority = "high"
|
||||
}
|
||||
|
||||
// update serious
|
||||
if strings.TrimSpace(bugTemplate.SeverityKey) != "" {
|
||||
keys := strings.Split(bugTemplate.SeverityKey, ",")
|
||||
for _, key := range keys {
|
||||
if strings.Contains(bug.Description, key) {
|
||||
bug.Severity = "serious"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateBug := &model.UpdateBug{
|
||||
Bug: bug,
|
||||
CurrentUser: bug.CurrentOwner,
|
||||
}
|
||||
|
||||
if err := s.dao.UpdateBug(updateBug); err != nil {
|
||||
log.Error("UpdateBug bugid %s, error (%v)", bug.ID, err)
|
||||
}
|
||||
}
|
||||
log.Info("finish projectID [%s] update title in tapd", projectID)
|
||||
}
|
||||
return
|
||||
}
|
434
app/service/ep/footman/service/excel.go
Normal file
434
app/service/ep/footman/service/excel.go
Normal file
@ -0,0 +1,434 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/log"
|
||||
|
||||
"github.com/360EntSecGroup-Skylar/excelize"
|
||||
)
|
||||
|
||||
//testTimeExcel test time excel
|
||||
func (s *Service) testTimeExcel(excelFilePath string, iteras []*model.TestTimeByIteration, iterationName, testType string) (err error) {
|
||||
log.Info("Start generating test time excel.")
|
||||
var xlsx *excelize.File
|
||||
if xlsx, err = excelize.OpenFile(excelFilePath); err != nil {
|
||||
xlsx = excelize.NewFile()
|
||||
}
|
||||
index := xlsx.NewSheet(iterationName)
|
||||
var tableHeader []string
|
||||
if testType == model.Test {
|
||||
tableHeader = []string{"Item", "需求", "迭代", "规模", "预估工时", "测试耗时(小时)"}
|
||||
}
|
||||
if testType == model.Experience {
|
||||
tableHeader = []string{"Item", "需求", "迭代", "规模", "预估工时", "产品验收耗时(小时)"}
|
||||
}
|
||||
|
||||
for k, v := range tableHeader {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(k, 1), v)
|
||||
}
|
||||
storyCount := 0
|
||||
for _, itera := range iteras {
|
||||
for _, story := range itera.TimeByStroy {
|
||||
storyCount++
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, storyCount+1), storyCount)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(1, storyCount+1), story.StoryName)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(2, storyCount+1), itera.IterationName)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(3, storyCount+1), story.StorySize)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(4, storyCount+1), story.StoryEffort)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(5, storyCount+1), story.TestTime)
|
||||
}
|
||||
}
|
||||
xlsx.SetActiveSheet(index)
|
||||
log.Info("End generating test time excel.")
|
||||
return xlsx.SaveAs(excelFilePath)
|
||||
}
|
||||
|
||||
//delayedStoryExcel delayed story list
|
||||
func (s *Service) delayedStoryExcel(excelFilePath string, iteras []*model.StoryChangeByIteration, workspaceType, iterationName string, nameMap map[string]string) (err error) {
|
||||
log.Info("Start generating delayed story excel.")
|
||||
var xlsx *excelize.File
|
||||
if xlsx, err = excelize.OpenFile(excelFilePath); err != nil {
|
||||
xlsx = excelize.NewFile()
|
||||
}
|
||||
index := xlsx.NewSheet(iterationName)
|
||||
tableHeader := []string{"Item", "需求", "迭代", "规模", "预估工时", "状态", "修改前发布计划", "修改后发布计划", "修改人", "修改时间"}
|
||||
for k, v := range tableHeader {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(k, 1), v)
|
||||
}
|
||||
storyCount := 0
|
||||
rn := 0
|
||||
var releases map[string]string
|
||||
if workspaceType == model.IOS {
|
||||
releases = model.IOSRelease
|
||||
}
|
||||
if workspaceType == model.Android {
|
||||
releases = model.AndroidRelease
|
||||
}
|
||||
|
||||
for _, itera := range iteras {
|
||||
for _, story := range itera.StoryChangeList {
|
||||
for k, v := range story.StatusChanges {
|
||||
if _, ok := releases[v.ValueBefore]; ok {
|
||||
story.StatusChanges[k].ValueBefore = releases[v.ValueBefore]
|
||||
}
|
||||
if _, ok := releases[v.ValueAfter]; ok {
|
||||
story.StatusChanges[k].ValueAfter = releases[v.ValueAfter]
|
||||
}
|
||||
if v.ValueBefore == "0" {
|
||||
story.StatusChanges[k].ValueBefore = "--"
|
||||
}
|
||||
if v.ValueAfter == "" {
|
||||
story.StatusChanges[k].ValueAfter = "--"
|
||||
}
|
||||
}
|
||||
if len(story.StatusChanges) == 0 || (len(story.StatusChanges) == 1 && story.StatusChanges[0].ValueBefore == "--" && strings.Contains(story.StatusChanges[0].ValueAfter, iterationName)) {
|
||||
continue
|
||||
}
|
||||
if len(story.StatusChanges) > 1 {
|
||||
toSkip := 1
|
||||
for _, v := range story.StatusChanges {
|
||||
if v.ValueAfter != "--" && !strings.Contains(v.ValueAfter, iterationName) {
|
||||
toSkip = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
if toSkip == 1 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
storyCount++
|
||||
rn++
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, rn+1), storyCount)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(1, rn+1), story.Story.Name)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(2, rn+1), itera.IterationName)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(3, rn+1), story.Story.Size)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(4, rn+1), story.Story.Effort)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(5, rn+1), nameMap[story.Story.Status])
|
||||
|
||||
for _, c := range story.StatusChanges {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(6, rn+1), c.ValueBefore)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(7, rn+1), c.ValueAfter)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(8, rn+1), c.Creator)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(9, rn+1), c.Created)
|
||||
rn++
|
||||
}
|
||||
len := len(story.StatusChanges)
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(0, rn-len+1), s.cellLocation(0, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(1, rn-len+1), s.cellLocation(1, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(2, rn-len+1), s.cellLocation(2, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(3, rn-len+1), s.cellLocation(3, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(4, rn-len+1), s.cellLocation(4, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(5, rn-len+1), s.cellLocation(5, rn))
|
||||
rn--
|
||||
}
|
||||
}
|
||||
xlsx.SetActiveSheet(index)
|
||||
log.Info("End generating delayed story excel.")
|
||||
return xlsx.SaveAs(excelFilePath)
|
||||
}
|
||||
|
||||
//rejectedStoryExcel rejected story list
|
||||
func (s *Service) rejectedStoryExcel(excelFilePath string, iteras []*model.RejectedStoryByIteration, iterationName, rejectType string) (err error) {
|
||||
log.Info("Start generating rejected story excel.")
|
||||
var xlsx *excelize.File
|
||||
if xlsx, err = excelize.OpenFile(excelFilePath); err != nil {
|
||||
xlsx = excelize.NewFile()
|
||||
}
|
||||
index := xlsx.NewSheet(iterationName)
|
||||
var tableHeader []string
|
||||
if rejectType == model.Test {
|
||||
tableHeader = []string{"Item", "迭代", "测试打回需求数量", "测试打回需求列表"}
|
||||
}
|
||||
if rejectType == model.Experience {
|
||||
tableHeader = []string{"Item", "迭代", "产品打回需求数量", "产品打回需求列表"}
|
||||
}
|
||||
for k, v := range tableHeader {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(k, 1), v)
|
||||
}
|
||||
iteraCount := 0
|
||||
rn := 0
|
||||
|
||||
for _, itera := range iteras {
|
||||
|
||||
if itera.RejectedStoryCount == 0 {
|
||||
continue
|
||||
}
|
||||
iteraCount++
|
||||
rn++
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, rn+1), iteraCount)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(1, rn+1), itera.IterationName)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(2, rn+1), itera.RejectedStoryCount)
|
||||
|
||||
for _, story := range itera.RejectedStoryList {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(3, rn+1), story)
|
||||
rn++
|
||||
}
|
||||
len := itera.RejectedStoryCount
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(0, rn-len+1), s.cellLocation(0, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(1, rn-len+1), s.cellLocation(1, rn))
|
||||
xlsx.MergeCell(iterationName, s.cellLocation(2, rn-len+1), s.cellLocation(2, rn))
|
||||
rn--
|
||||
}
|
||||
xlsx.SetActiveSheet(index)
|
||||
log.Info("End generating rejected story excel.")
|
||||
return xlsx.SaveAs(excelFilePath)
|
||||
}
|
||||
|
||||
//waitTimeExcel wait time excel
|
||||
func (s *Service) waitTimeExcel(excelFilePath string, iteras []*model.WaitTimeByIteration, iterationName, waitType string) (err error) {
|
||||
log.Info("Start generating waiting time excel.")
|
||||
var xlsx *excelize.File
|
||||
if xlsx, err = excelize.OpenFile(excelFilePath); err != nil {
|
||||
xlsx = excelize.NewFile()
|
||||
}
|
||||
index := xlsx.NewSheet(iterationName)
|
||||
var tableHeader []string
|
||||
if waitType == model.Test {
|
||||
tableHeader = []string{"Item", "需求", "迭代", "规模", "预估工时", "等待测试时长(小时)"}
|
||||
}
|
||||
|
||||
for k, v := range tableHeader {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(k, 1), v)
|
||||
}
|
||||
storyCount := 0
|
||||
for _, itera := range iteras {
|
||||
for _, story := range itera.TimeByStroy {
|
||||
storyCount++
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, storyCount+1), storyCount)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(1, storyCount+1), story.StoryName)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(2, storyCount+1), itera.IterationName)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(3, storyCount+1), story.StorySize)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(4, storyCount+1), story.StoryEffort)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(5, storyCount+1), story.WaitTime)
|
||||
}
|
||||
}
|
||||
xlsx.SetActiveSheet(index)
|
||||
log.Info("End generating waiting time excel.")
|
||||
return xlsx.SaveAs(excelFilePath)
|
||||
}
|
||||
|
||||
//storyWallExcel story wall excel
|
||||
func (s *Service) storyWallExcel(excelFilePath string, iteras []*model.StoryChangeByIteration, workspaceID, workspaceType, iterationName string, isTime bool, nameMap map[string]string, categoryMap map[string]string) (err error) {
|
||||
if iterationName == "" {
|
||||
iterationName = "all"
|
||||
}
|
||||
|
||||
log.Info("Start generating story wall excel.")
|
||||
var (
|
||||
xlsx *excelize.File
|
||||
workflow []string
|
||||
releases map[string]string
|
||||
additionalFields map[string]string
|
||||
additionalFieldsList []string
|
||||
)
|
||||
if xlsx, err = excelize.OpenFile(excelFilePath); err != nil {
|
||||
xlsx = excelize.NewFile()
|
||||
}
|
||||
index := xlsx.NewSheet(iterationName)
|
||||
|
||||
//init table header map
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, 1), "Item")
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(1, 1), "需求")
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(2, 1), "迭代")
|
||||
if workspaceType == model.Android {
|
||||
workflow = model.AndroidWorkflow
|
||||
releases = model.AndroidRelease
|
||||
additionalFields = model.AndroidFields
|
||||
additionalFieldsList = model.AndroidFieldsList
|
||||
}
|
||||
if workspaceType == model.IOS {
|
||||
workflow = model.IOSWorkflow
|
||||
releases = model.IOSRelease
|
||||
additionalFields = model.IOSFields
|
||||
additionalFieldsList = model.IOSFieldsList
|
||||
}
|
||||
|
||||
if workspaceType == model.Live {
|
||||
workflow = model.LiveWorkflow
|
||||
//releases
|
||||
//additionalFields
|
||||
//additionalFieldsList
|
||||
|
||||
}
|
||||
|
||||
if workspaceType == model.BPlus {
|
||||
workflow = model.BPlusWorkflow
|
||||
}
|
||||
|
||||
tableHeader := make(map[string]int)
|
||||
order := 3
|
||||
for _, v := range workflow {
|
||||
tableHeader[v] = order
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(order, 1), v)
|
||||
order++
|
||||
}
|
||||
for _, v := range model.BaseFieldsList {
|
||||
tableHeader[v] = order
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(order, 1), v)
|
||||
order++
|
||||
}
|
||||
|
||||
if workspaceType == model.Android || workspaceType == model.IOS {
|
||||
for _, v := range additionalFieldsList {
|
||||
tableHeader[v] = order
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(order, 1), v)
|
||||
order++
|
||||
}
|
||||
}
|
||||
|
||||
storyCount := 0
|
||||
for _, itera := range iteras {
|
||||
for _, story := range itera.StoryChangeList {
|
||||
storyCount++
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, storyCount+1), storyCount)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(1, storyCount+1), story.Story.Name)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(2, storyCount+1), itera.IterationName)
|
||||
|
||||
var createdDate, completedDate string
|
||||
if createdDate, err = s.timeToDate(story.Story.Created); err != nil {
|
||||
return
|
||||
}
|
||||
if completedDate, err = s.timeToDate(story.Story.Completed); err != nil {
|
||||
completedDate = story.Story.Completed
|
||||
}
|
||||
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["id"]], storyCount+1), story.Story.ID)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["status"]], storyCount+1), nameMap[story.Story.Status])
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["created"]], storyCount+1), createdDate)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["priority"]], storyCount+1), story.Story.Priority)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["size"]], storyCount+1), story.Story.Size)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["category_id"]], storyCount+1), categoryMap[story.Story.CategoryID])
|
||||
|
||||
if story.Story.ParentID == "0" {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["parent_id"]], storyCount+1), "")
|
||||
} else {
|
||||
specStoryURL := fmt.Sprintf(model.SpecStoryURL, workspaceID, story.Story.ParentID)
|
||||
var specStoryRes *model.SpecStoryResponse
|
||||
if specStoryRes, err = s.dao.SpecStory(specStoryURL); err != nil {
|
||||
return
|
||||
}
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["parent_id"]], storyCount+1), specStoryRes.Data.Story.Name)
|
||||
}
|
||||
|
||||
var releaseStr string
|
||||
if _, ok := releases[story.Story.ReleaseID]; ok {
|
||||
releaseStr = releases[story.Story.ReleaseID]
|
||||
} else {
|
||||
releaseStr = story.Story.ReleaseID
|
||||
}
|
||||
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["release_id"]], storyCount+1), releaseStr)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["owner"]], storyCount+1), story.Story.Owner)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["developer"]], storyCount+1), story.Story.Developer)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["creator"]], storyCount+1), story.Story.Creator)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["begin"]], storyCount+1), story.Story.Begin)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["due"]], storyCount+1), story.Story.Due)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["completed"]], storyCount+1), completedDate)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[model.BaseFields["effort"]], storyCount+1), story.Story.Effort)
|
||||
|
||||
if workspaceType == model.Android || workspaceType == model.IOS {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[additionalFields["custom_field_99"]], storyCount+1), story.Story.CustomField99)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[additionalFields["custom_field_97"]], storyCount+1), story.Story.CustomField97)
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[additionalFields["custom_field_93"]], storyCount+1), story.Story.CustomField93)
|
||||
|
||||
if workspaceType == model.IOS {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[additionalFields["custom_field_92"]], storyCount+1), story.Story.CustomField92)
|
||||
}
|
||||
}
|
||||
|
||||
for _, change := range story.StatusChanges {
|
||||
var changeDate string
|
||||
if isTime {
|
||||
changeDate = change.Created
|
||||
} else if changeDate, err = s.timeToDate(change.Created); err != nil {
|
||||
return
|
||||
}
|
||||
if _, ok := tableHeader[nameMap[change.ValueAfter]]; !ok {
|
||||
continue
|
||||
}
|
||||
if xlsx.GetCellValue(iterationName, s.cellLocation(tableHeader[nameMap[change.ValueAfter]], storyCount+1)) == "" {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[nameMap[change.ValueAfter]], storyCount+1), changeDate)
|
||||
}
|
||||
}
|
||||
|
||||
if xlsx.GetCellValue(iterationName, s.cellLocation(tableHeader[workflow[0]], storyCount+1)) == "" {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(tableHeader[workflow[0]], storyCount+1), createdDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
xlsx.SetActiveSheet(index)
|
||||
log.Info("End generating story wall excel.")
|
||||
return xlsx.SaveAs(excelFilePath)
|
||||
}
|
||||
|
||||
func (s *Service) storyWallReportExcel(excelFilePath, iterationName string, storyWallColNames, datesSort []string, storyWallColNameMap map[string]string, excelRet map[string]map[string]int) (err error) {
|
||||
log.Info("Start generating story report wall excel.")
|
||||
var (
|
||||
xlsx *excelize.File
|
||||
)
|
||||
if xlsx, err = excelize.OpenFile(excelFilePath); err != nil {
|
||||
xlsx = excelize.NewFile()
|
||||
}
|
||||
|
||||
index := xlsx.NewSheet(iterationName)
|
||||
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, 1), "Date")
|
||||
for k, v := range storyWallColNames {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(k+1, 1), storyWallColNameMap[v])
|
||||
}
|
||||
|
||||
storyCount := 0
|
||||
for _, dateSort := range datesSort {
|
||||
rowDate := excelRet[dateSort]
|
||||
if rowDate == nil {
|
||||
continue
|
||||
}
|
||||
storyCount = storyCount + 1
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(0, storyCount+1), dateSort)
|
||||
for k, v := range storyWallColNames {
|
||||
xlsx.SetCellValue(iterationName, s.cellLocation(k+1, storyCount+1), rowDate[v])
|
||||
}
|
||||
}
|
||||
|
||||
xlsx.SetActiveSheet(index)
|
||||
log.Info("End generating story report wall excel.")
|
||||
return xlsx.SaveAs(excelFilePath)
|
||||
}
|
||||
|
||||
//cellLocation get cell location with given column and row
|
||||
func (s *Service) cellLocation(c int, r int) (cellLocation string) {
|
||||
sc := 'A'
|
||||
if c < 26 {
|
||||
cellLocation = string(int(sc)+c) + strconv.Itoa(r)
|
||||
} else {
|
||||
cellLocation = "A" + string(int(sc)+c-26) + strconv.Itoa(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) getFromExcel(excelName, sheetName string) (ret []map[string]string, err error) {
|
||||
var (
|
||||
xlsx *excelize.File
|
||||
sheetRows [][]string
|
||||
)
|
||||
|
||||
if xlsx, err = excelize.OpenFile(excelName); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sheetRows = xlsx.GetRows(sheetName)
|
||||
for index := 1; index < len(sheetRows); index++ {
|
||||
tmpMap := make(map[string]string)
|
||||
for innerIndex := 0; innerIndex < len(sheetRows[0]); innerIndex++ {
|
||||
key := sheetRows[0][innerIndex]
|
||||
val := sheetRows[index][innerIndex]
|
||||
tmpMap[key] = val
|
||||
}
|
||||
ret = append(ret, tmpMap)
|
||||
}
|
||||
return
|
||||
}
|
16
app/service/ep/footman/service/mail.go
Normal file
16
app/service/ep/footman/service/mail.go
Normal file
@ -0,0 +1,16 @@
|
||||
package service
|
||||
|
||||
import "gopkg.in/gomail.v2"
|
||||
|
||||
func (s *Service) sendMail(receiver []string, header, body string) {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("To", receiver...)
|
||||
m.SetHeader("Subject", header)
|
||||
m.SetBody("text/html", body)
|
||||
s.dao.SendMail(m)
|
||||
}
|
||||
|
||||
// TapdMailNotice Tapd Mail Notice.
|
||||
func (s *Service) TapdMailNotice(header, body string) {
|
||||
s.sendMail(s.c.Mail.NoticeOwner, header, body)
|
||||
}
|
203
app/service/ep/footman/service/report.go
Normal file
203
app/service/ep/footman/service/report.go
Normal file
@ -0,0 +1,203 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const _dayMillisecond = 86400000000000
|
||||
|
||||
//TestTimeReport test time report
|
||||
func (s *Service) TestTimeReport(workspaceID, workspaceType, iterationName, excelFilePath, testType string, ips, sps, scps int) (err error) {
|
||||
|
||||
var (
|
||||
scis []*model.StoryChangeByIteration
|
||||
iteras []*model.TestTimeByIteration
|
||||
)
|
||||
pattern := `("field":"status","value_before":")([\w]+)(","value_after":")([\w]+)(")`
|
||||
if scis, err = s.storyChangeByIterationWithName(workspaceID, iterationName, pattern, ips, sps, scps); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching story changes from tapd")
|
||||
}
|
||||
|
||||
if iteras, err = s.testTimeByIteration(scis, workspaceType, testType); err != nil {
|
||||
return errors.Wrap(err, "Error happened when extracting test time from story changes data")
|
||||
}
|
||||
|
||||
return s.testTimeExcel(excelFilePath, iteras, iterationName, testType)
|
||||
}
|
||||
|
||||
//DelayedStoryReport delayed story report
|
||||
func (s *Service) DelayedStoryReport(workspaceID, workspaceType, iterationName, excelFilePath string, ips, sps, scps int) (err error) {
|
||||
|
||||
var (
|
||||
scis []*model.StoryChangeByIteration
|
||||
nameMapRes *model.NameMapResponse
|
||||
)
|
||||
pattern := `("field":"release_id","value_before":")([\w]*)(","value_after":")([\w]*)(")`
|
||||
if scis, err = s.storyChangeByIterationWithName(workspaceID, iterationName, pattern, ips, sps, scps); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching story changes from tapd")
|
||||
}
|
||||
|
||||
nameMapURL := fmt.Sprintf(model.NameMapURL, workspaceID)
|
||||
if nameMapRes, err = s.dao.NameMap(nameMapURL); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching status name mapping from tapd")
|
||||
}
|
||||
return s.delayedStoryExcel(excelFilePath, scis, workspaceType, iterationName, nameMapRes.Data)
|
||||
}
|
||||
|
||||
//RejectedStoryReport test or experience rejected story report
|
||||
func (s *Service) RejectedStoryReport(workspaceID, workspaceType, iterationName, excelFilePath, rejectType string, ips, sps, scps int) (err error) {
|
||||
|
||||
var (
|
||||
scis []*model.StoryChangeByIteration
|
||||
iteras []*model.RejectedStoryByIteration
|
||||
)
|
||||
pattern := `("field":"status","value_before":")([\w]+)(","value_after":")([\w]+)(")`
|
||||
if scis, err = s.storyChangeByIterationWithName(workspaceID, iterationName, pattern, ips, sps, scps); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching story changes from tapd")
|
||||
}
|
||||
|
||||
if iteras, err = s.rejectedStoryByIteration(scis, workspaceType, rejectType); err != nil {
|
||||
return errors.Wrap(err, "Error happened when extracting rejected stories from story change data")
|
||||
}
|
||||
|
||||
return s.rejectedStoryExcel(excelFilePath, iteras, iterationName, rejectType)
|
||||
}
|
||||
|
||||
//WaitTimeReport test time report
|
||||
func (s *Service) WaitTimeReport(workspaceID, iterationName, excelFilePath, waitType string, ips, sps, scps int) (err error) {
|
||||
|
||||
var (
|
||||
scis []*model.StoryChangeByIteration
|
||||
iteras []*model.WaitTimeByIteration
|
||||
)
|
||||
pattern := `("field":"status","value_before":")([\w]+)(","value_after":")([\w]+)(")`
|
||||
if scis, err = s.storyChangeByIterationWithName(workspaceID, iterationName, pattern, ips, sps, scps); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching story changes from tapd")
|
||||
}
|
||||
|
||||
if iteras, err = s.waitTimeByIteration(scis, waitType); err != nil {
|
||||
return errors.Wrap(err, "Error happened when extracting wait time from story change data")
|
||||
}
|
||||
|
||||
return s.waitTimeExcel(excelFilePath, iteras, iterationName, waitType)
|
||||
}
|
||||
|
||||
//StoryWallReport story wall report
|
||||
func (s *Service) StoryWallReport(workspaceID, workspaceType, iterationName, excelFilePath string, isTime bool, ips, sps, scps, cps int) (err error) {
|
||||
|
||||
var (
|
||||
scis []*model.StoryChangeByIteration
|
||||
nameMapRes *model.NameMapResponse
|
||||
categoryMap map[string]string
|
||||
)
|
||||
pattern := `("field":"status","value_before":")([\w]+)(","value_after":")([\w]+)(")`
|
||||
if scis, err = s.storyChangeByIterationWithName(workspaceID, iterationName, pattern, ips, sps, scps); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching story changes from tapd")
|
||||
}
|
||||
|
||||
nameMapURL := fmt.Sprintf(model.NameMapURL, workspaceID)
|
||||
if nameMapRes, err = s.dao.NameMap(nameMapURL); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching status name mapping from tapd")
|
||||
}
|
||||
|
||||
categoryURL := fmt.Sprintf(model.CategoryURL, workspaceID, model.CPS) + "&page=%d"
|
||||
if categoryMap, err = s.dao.AllCategories(cps, categoryURL); err != nil {
|
||||
return errors.Wrap(err, "Error happened when fetching project categories from tapd")
|
||||
}
|
||||
return s.storyWallExcel(excelFilePath, scis, workspaceID, workspaceType, iterationName, isTime, nameMapRes.Data, categoryMap)
|
||||
}
|
||||
|
||||
// GenStoryReport Gen Story Report.
|
||||
func (s *Service) GenStoryReport(workspaceType, importExcel, sheetName, exportExcel, iterationName, startDate, endDate string) (err error) {
|
||||
var (
|
||||
startTime time.Time
|
||||
endTime time.Time
|
||||
dateSort []string
|
||||
excelRets []map[string]string
|
||||
timeLayout = "2006-01-02"
|
||||
tmpExportMap = make(map[string][]*model.StoryWallTimeModel)
|
||||
exportRet = make(map[string]map[string]int)
|
||||
storyFields []string
|
||||
storyColNames map[string]string
|
||||
)
|
||||
|
||||
switch workspaceType {
|
||||
case "android":
|
||||
storyFields = model.AndroidStoryWallFields
|
||||
storyColNames = model.AndroidStoryWallColNames
|
||||
|
||||
case "ios":
|
||||
storyFields = model.IosStoryWallFields
|
||||
storyColNames = model.IosStoryWallColNames
|
||||
}
|
||||
|
||||
loc, _ := time.LoadLocation("Local")
|
||||
|
||||
if startTime, err = time.ParseInLocation(timeLayout, startDate, loc); err != nil {
|
||||
return
|
||||
}
|
||||
if endTime, err = time.ParseInLocation(timeLayout, endDate, loc); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if excelRets, err = s.getFromExcel(importExcel, sheetName); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, excelRet := range excelRets {
|
||||
if strings.Contains(excelRet["iterationname"], iterationName) {
|
||||
lastTime := endTime
|
||||
|
||||
for index := len(storyFields); index > 0; index-- {
|
||||
//for index, stepField := range model.AndroidStoryWallFields {
|
||||
stepField := storyFields[index-1]
|
||||
if index == 0 {
|
||||
if strings.TrimSpace(excelRet[stepField]) != "" {
|
||||
curDate := strings.Split(excelRet[stepField], " ")[0]
|
||||
curTime, _ := time.ParseInLocation(timeLayout, curDate, loc)
|
||||
if !curTime.After(endTime) {
|
||||
tmpExportMap[stepField] = append(tmpExportMap[stepField], &model.StoryWallTimeModel{StepStartTime: curTime, StepEndTime: endTime})
|
||||
lastTime = curTime.AddDate(0, 0, -1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.TrimSpace(excelRet[stepField]) != "" {
|
||||
curDate := strings.Split(excelRet[stepField], " ")[0]
|
||||
curTime, _ := time.ParseInLocation(timeLayout, curDate, loc)
|
||||
tmpExportMap[stepField] = append(tmpExportMap[stepField], &model.StoryWallTimeModel{StepStartTime: curTime, StepEndTime: lastTime})
|
||||
lastTime = curTime.AddDate(0, 0, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
durDate := int(endTime.Sub(startTime)/_dayMillisecond + 1)
|
||||
|
||||
for index := 0; index < durDate; index++ {
|
||||
curTime := startTime.AddDate(0, 0, index)
|
||||
curDate := curTime.Format("2006-01-02")
|
||||
dayRet := make(map[string]int)
|
||||
for stepKey, stepVals := range tmpExportMap {
|
||||
totalCount := 0
|
||||
for _, stepVal := range stepVals {
|
||||
if !stepVal.StepStartTime.After(curTime) && !curTime.After(stepVal.StepEndTime) {
|
||||
totalCount = totalCount + 1
|
||||
}
|
||||
}
|
||||
dayRet[stepKey] = totalCount
|
||||
}
|
||||
dateSort = append(dateSort, curDate)
|
||||
exportRet[curDate] = dayRet
|
||||
}
|
||||
|
||||
s.storyWallReportExcel(exportExcel, iterationName, storyFields, dateSort, storyColNames, exportRet)
|
||||
return
|
||||
}
|
52
app/service/ep/footman/service/service.go
Normal file
52
app/service/ep/footman/service/service.go
Normal file
@ -0,0 +1,52 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go-common/app/service/ep/footman/conf"
|
||||
"go-common/app/service/ep/footman/dao"
|
||||
"go-common/library/cache"
|
||||
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
// Service struct
|
||||
type Service struct {
|
||||
c *conf.Config
|
||||
dao *dao.Dao
|
||||
cache *cache.Cache
|
||||
buglyCache *cache.Cache
|
||||
cron *cron.Cron
|
||||
}
|
||||
|
||||
// New init
|
||||
func New(c *conf.Config) (s *Service) {
|
||||
s = &Service{
|
||||
c: c,
|
||||
dao: dao.New(c),
|
||||
cache: cache.New(1, 1024000),
|
||||
buglyCache: cache.New(1, 10240),
|
||||
}
|
||||
|
||||
if c.Scheduler == nil {
|
||||
return
|
||||
}
|
||||
scheduler := c.Scheduler
|
||||
s.cron = cron.New()
|
||||
if err := s.cron.AddFunc(scheduler.SaveTapdTime, s.SaveFilesForTask); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.cron.Start()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Ping Service
|
||||
func (s *Service) Ping(c context.Context) (err error) {
|
||||
return s.dao.Ping(c)
|
||||
}
|
||||
|
||||
// Close Service
|
||||
func (s *Service) Close() {
|
||||
s.dao.Close()
|
||||
}
|
302
app/service/ep/footman/service/tapd.go
Normal file
302
app/service/ep/footman/service/tapd.go
Normal file
@ -0,0 +1,302 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-common/app/service/ep/footman/model"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
//storyChangeByIteration get stories and story changes for input iterations
|
||||
func (s *Service) storyChangeByIteration(iterationRes *model.IterationResponse, workspaceID, pattern string, sps, scps int) (iterations []*model.StoryChangeByIteration, err error) {
|
||||
var (
|
||||
storyRes *model.StoryResponse
|
||||
storyChangeRes *model.StoryChangeResponse
|
||||
)
|
||||
iterations = make([]*model.StoryChangeByIteration, 0, 5)
|
||||
|
||||
for _, itera := range iterationRes.Data {
|
||||
sci := &model.StoryChangeByIteration{}
|
||||
sci.IterationName = itera.Iteration.Name
|
||||
|
||||
storyURL := fmt.Sprintf(model.StoryURL, workspaceID, itera.Iteration.ID, sps) + "&page=%d"
|
||||
log.Info("Story:" + fmt.Sprintf(model.StoryURL, workspaceID, itera.Iteration.ID, sps))
|
||||
if storyRes, err = s.dao.AllStories(sps, storyURL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if storyRes == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
storyChanges := make([]*model.TargetStoryChange, 0, 5)
|
||||
|
||||
if len(storyRes.Data) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, story := range storyRes.Data {
|
||||
change := &model.TargetStoryChange{}
|
||||
change.Story = story.Story
|
||||
|
||||
storyChangeURL := fmt.Sprintf(model.StoryChangeURL, workspaceID, change.Story.ID, scps) + "&page=%d"
|
||||
if storyChangeRes, err = s.dao.AllStoryChanges(scps, storyChangeURL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if storyChangeRes == nil {
|
||||
continue
|
||||
}
|
||||
statusChanges := make([]*model.StatusChange, 0, 5)
|
||||
for _, change := range storyChangeRes.Data {
|
||||
reg := regexp.MustCompile(pattern)
|
||||
fields := reg.FindStringSubmatch(change.WorkitemChange.Changes)
|
||||
if len(fields) >= 1 {
|
||||
statusChange := &model.StatusChange{}
|
||||
statusChange.Created = change.WorkitemChange.Created
|
||||
statusChange.Creator = change.WorkitemChange.Creator
|
||||
statusChange.ValueBefore = fields[2]
|
||||
statusChange.ValueAfter = fields[4]
|
||||
statusChanges = append(statusChanges, statusChange)
|
||||
}
|
||||
}
|
||||
change.StatusChanges = statusChanges
|
||||
storyChanges = append(storyChanges, change)
|
||||
}
|
||||
|
||||
sci.StoryChangeList = storyChanges
|
||||
sci.StoryCount = len(storyChanges)
|
||||
iterations = append(iterations, sci)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//storyChangeByIterationWithName get stories and story changes for input iteration name
|
||||
func (s *Service) storyChangeByIterationWithName(workspaceID, iterationName, pattern string, ips, sps, scps int) (iterations []*model.StoryChangeByIteration, err error) {
|
||||
var (
|
||||
iterationRes *model.IterationResponse
|
||||
)
|
||||
iterationURL := fmt.Sprintf(model.IterationURL, workspaceID, iterationName, ips) + "&page=%d"
|
||||
log.Info("Iteration:" + fmt.Sprintf(model.IterationURL, workspaceID, iterationName, ips))
|
||||
if iterationRes, err = s.dao.AllIterations(ips, iterationURL); err != nil || len(iterationRes.Data) == 0 {
|
||||
return
|
||||
}
|
||||
return s.storyChangeByIteration(iterationRes, workspaceID, pattern, sps, scps)
|
||||
}
|
||||
|
||||
//testTimeByIteration get test time data from story changes
|
||||
func (s *Service) testTimeByIteration(scis []*model.StoryChangeByIteration, workspaceType, testType string) (iterations []*model.TestTimeByIteration, err error) {
|
||||
iterations = make([]*model.TestTimeByIteration, 0, 5)
|
||||
for _, sci := range scis {
|
||||
itera := &model.TestTimeByIteration{}
|
||||
itera.IterationName = sci.IterationName
|
||||
stories := make([]*model.TestTimeByStory, 0, 5)
|
||||
for _, story := range sci.StoryChangeList {
|
||||
ts := &model.TestTimeByStory{}
|
||||
ts.StoryName = story.Story.Name
|
||||
ts.StorySize = story.Story.Size
|
||||
ts.StoryEffort = story.Story.Effort
|
||||
|
||||
found := 0
|
||||
var t1, t2 *time.Time
|
||||
var starttime, endtime time.Time
|
||||
total := 0.0
|
||||
for _, change := range story.StatusChanges {
|
||||
var condition1, condition2 bool
|
||||
if workspaceType == model.Android && testType == model.Test {
|
||||
condition1 = change.ValueBefore == "testing" && (change.ValueAfter == "developing" || change.ValueAfter == "status_6" || change.ValueAfter == "product_experience")
|
||||
condition2 = change.ValueBefore == "status_5" && change.ValueAfter == "testing" && found == 1
|
||||
}
|
||||
if workspaceType == model.IOS && testType == model.Test {
|
||||
condition1 = change.ValueBefore == "testing" && (change.ValueAfter == "developing" || change.ValueAfter == "status_7" || change.ValueAfter == "product_experience")
|
||||
condition2 = change.ValueBefore == "status_5" && change.ValueAfter == "testing" && found == 1
|
||||
}
|
||||
if testType == model.Experience {
|
||||
condition1 = change.ValueBefore == "product_experience" && (change.ValueAfter == "developing" || change.ValueAfter == "status_5")
|
||||
condition2 = change.ValueBefore == "developing" && change.ValueAfter == "product_experience" && found == 1
|
||||
}
|
||||
if condition1 {
|
||||
if starttime, err = s.stringToTime(change.Created); err != nil {
|
||||
return
|
||||
}
|
||||
found = 1
|
||||
t1 = &starttime
|
||||
} else if condition2 {
|
||||
if endtime, err = s.stringToTime(change.Created); err != nil {
|
||||
return
|
||||
}
|
||||
found = 2
|
||||
t2 = &endtime
|
||||
} else {
|
||||
found = 0
|
||||
t1 = nil
|
||||
}
|
||||
if t1 != nil && t2 != nil {
|
||||
total = total + t1.Sub(*t2).Hours() - float64(s.weekendDays(t1, t2)*24)
|
||||
t1 = nil
|
||||
t2 = nil
|
||||
}
|
||||
}
|
||||
if ts.TestTime, err = strconv.ParseFloat(strconv.FormatFloat(total, 'f', 2, 64), 64); err != nil {
|
||||
return
|
||||
}
|
||||
stories = append(stories, ts)
|
||||
}
|
||||
itera.TimeByStroy = stories
|
||||
itera.StoryCount = len(stories)
|
||||
iterations = append(iterations, itera)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//rejectedStoryByIteration get rejected stories
|
||||
func (s *Service) rejectedStoryByIteration(scis []*model.StoryChangeByIteration, workspaceType, rejectType string) (iterations []*model.RejectedStoryByIteration, err error) {
|
||||
iterations = make([]*model.RejectedStoryByIteration, 0, 5)
|
||||
for _, sci := range scis {
|
||||
itera := &model.RejectedStoryByIteration{}
|
||||
itera.IterationName = sci.IterationName
|
||||
stories := make([]string, 0, 5)
|
||||
for _, story := range sci.StoryChangeList {
|
||||
for _, change := range story.StatusChanges {
|
||||
var condition bool
|
||||
if (workspaceType == model.Android || workspaceType == model.IOS) && rejectType == model.Test {
|
||||
condition = change.ValueBefore == "testing" && (change.ValueAfter == "developing" || change.ValueAfter == "product_experience")
|
||||
}
|
||||
if (workspaceType == model.Android || workspaceType == model.IOS) && rejectType == model.Experience {
|
||||
condition = change.ValueBefore == "product_experience" && change.ValueAfter == "developing"
|
||||
}
|
||||
if condition {
|
||||
stories = append(stories, story.Story.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
itera.RejectedStoryList = stories
|
||||
itera.RejectedStoryCount = len(stories)
|
||||
iterations = append(iterations, itera)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//waitTimeByIteration get wait time data from story changes
|
||||
func (s *Service) waitTimeByIteration(scis []*model.StoryChangeByIteration, waitType string) (iterations []*model.WaitTimeByIteration, err error) {
|
||||
iterations = make([]*model.WaitTimeByIteration, 0, 5)
|
||||
for _, sci := range scis {
|
||||
itera := &model.WaitTimeByIteration{}
|
||||
itera.IterationName = sci.IterationName
|
||||
stories := make([]*model.WaitTimeByStory, 0, 5)
|
||||
for _, story := range sci.StoryChangeList {
|
||||
ws := &model.WaitTimeByStory{}
|
||||
ws.StoryName = story.Story.Name
|
||||
ws.StorySize = story.Story.Size
|
||||
ws.StoryEffort = story.Story.Effort
|
||||
|
||||
found := 0
|
||||
var t1, t2 *time.Time
|
||||
var starttime, endtime time.Time
|
||||
total := 0.0
|
||||
for _, change := range story.StatusChanges {
|
||||
var condition1, condition2 bool
|
||||
if waitType == model.Test {
|
||||
condition1 = change.ValueBefore == "status_5" && change.ValueAfter == "testing"
|
||||
condition2 = change.ValueBefore == "product_experience" && change.ValueAfter == "status_5" && found == 1
|
||||
}
|
||||
if condition1 {
|
||||
if starttime, err = s.stringToTime(change.Created); err != nil {
|
||||
return
|
||||
}
|
||||
found = 1
|
||||
t1 = &starttime
|
||||
} else if condition2 {
|
||||
if endtime, err = s.stringToTime(change.Created); err != nil {
|
||||
return
|
||||
}
|
||||
found = 2
|
||||
t2 = &endtime
|
||||
} else {
|
||||
found = 0
|
||||
t1 = nil
|
||||
}
|
||||
if t1 != nil && t2 != nil {
|
||||
total = t1.Sub(*t2).Hours() - float64(s.weekendDays(t1, t2)*24)
|
||||
break
|
||||
}
|
||||
}
|
||||
if ws.WaitTime, err = strconv.ParseFloat(strconv.FormatFloat(total, 'f', 2, 64), 64); err != nil {
|
||||
return
|
||||
}
|
||||
stories = append(stories, ws)
|
||||
}
|
||||
itera.TimeByStroy = stories
|
||||
itera.StoryCount = len(stories)
|
||||
iterations = append(iterations, itera)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//storyChangeItems get story change items
|
||||
func (s *Service) storyChangeItems(workspaceID, storyID, pattern string, scps int) (changes []*model.StoryChangeItem, err error) {
|
||||
storyChangeURL := fmt.Sprintf(model.StoryChangeURL, workspaceID, storyID, scps) + "&page=%d"
|
||||
var storyChangeRes *model.StoryChangeResponse
|
||||
if storyChangeRes, err = s.dao.AllStoryChanges(scps, storyChangeURL); err != nil || storyChangeRes == nil {
|
||||
return
|
||||
}
|
||||
for k, v := range storyChangeRes.Data {
|
||||
reg := regexp.MustCompile(pattern)
|
||||
changeStrList := strings.Split(s.unicode2Chinese(v.WorkitemChange.Changes), "},{")
|
||||
for _, changeStr := range changeStrList {
|
||||
fields := reg.FindStringSubmatch(changeStr)
|
||||
if len(fields) >= 1 {
|
||||
change := &model.StoryChangeItem{}
|
||||
change.ID = v.WorkitemChange.ID
|
||||
change.ChangeSummay = v.WorkitemChange.ChangeSummay
|
||||
change.Comment = v.WorkitemChange.Comment
|
||||
change.EntityType = v.WorkitemChange.EntityType
|
||||
change.Number = strconv.Itoa(k)
|
||||
change.StoryID = v.WorkitemChange.StoryID
|
||||
change.WorkspaceID = v.WorkitemChange.WorkspaceID
|
||||
change.Created = v.WorkitemChange.Created
|
||||
change.Creator = v.WorkitemChange.Creator
|
||||
change.Field = fields[2]
|
||||
if fields[2] != "description" {
|
||||
change.ValueBefore = fields[4]
|
||||
change.ValueAfter = fields[6]
|
||||
}
|
||||
|
||||
changes = append(changes, change)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Service) unicode2Chinese(str string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
i, j := 0, len(str)
|
||||
for i < j {
|
||||
x := i + 6
|
||||
if x > j {
|
||||
buf.WriteString(str[i:])
|
||||
break
|
||||
}
|
||||
if str[i] == '\\' && str[i+1] == 'u' {
|
||||
hex := str[i+2 : x]
|
||||
r, err := strconv.ParseUint(hex, 16, 64)
|
||||
if err == nil {
|
||||
buf.WriteRune(rune(r))
|
||||
} else {
|
||||
buf.WriteString(str[i:x])
|
||||
}
|
||||
i = x
|
||||
} else {
|
||||
buf.WriteByte(str[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
93
app/service/ep/footman/service/util.go
Normal file
93
app/service/ep/footman/service/util.go
Normal file
@ -0,0 +1,93 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//timeToDate convert time to date
|
||||
func (s *Service) timeToDate(ts string) (d string, err error) {
|
||||
var t time.Time
|
||||
if t, err = s.stringToTime(ts); err != nil {
|
||||
return
|
||||
}
|
||||
d = t.Format("2006/01/02")
|
||||
return
|
||||
}
|
||||
|
||||
//stringToTime convert string to time
|
||||
func (s *Service) stringToTime(ts string) (t time.Time, err error) {
|
||||
timeLayout := "2006-01-02 15:04:05"
|
||||
var loc *time.Location
|
||||
if loc, err = time.LoadLocation("Local"); err != nil {
|
||||
return
|
||||
}
|
||||
return time.ParseInLocation(timeLayout, ts, loc)
|
||||
}
|
||||
|
||||
//weekendDays calculate weekend days between two time
|
||||
func (s *Service) weekendDays(t1, t2 *time.Time) (days int) {
|
||||
var t *time.Time
|
||||
if t1.After(*t2) {
|
||||
t = t1
|
||||
t1 = t2
|
||||
t2 = t
|
||||
}
|
||||
y1, m1, d1 := t1.Date()
|
||||
y2, m2, d2 := t2.Date()
|
||||
|
||||
if y1 == y2 && m1 == m2 && d1 == d2 {
|
||||
return 0
|
||||
}
|
||||
|
||||
gaps := t2.Sub(*t1).Hours() / 24
|
||||
days = int(gaps) / 7 * 2
|
||||
|
||||
wd1 := int(t1.Weekday())
|
||||
wd2 := int(t2.Weekday())
|
||||
h1 := t1.Hour()
|
||||
mu1 := t1.Minute()
|
||||
s1 := t1.Second()
|
||||
h2 := t2.Hour()
|
||||
mu2 := t2.Minute()
|
||||
s2 := t2.Second()
|
||||
|
||||
if wd2 < wd1 || ((wd2 == wd1) && (h2 < h1 || (h2 == h1 && mu2 < mu1) || (h2 == h1 && mu2 == mu1 && s2 < s1))) {
|
||||
days += 2
|
||||
if wd1 == 6 {
|
||||
days -= 1
|
||||
}
|
||||
if wd2 == 0 {
|
||||
days -= 1
|
||||
}
|
||||
}
|
||||
|
||||
var temp1, temp2, temp3, temp4, temp5, temp6 time.Time
|
||||
temp1, _ = s.stringToTime("2018-11-05 00:00:00")
|
||||
temp2, _ = s.stringToTime("2018-11-06 23:59:59")
|
||||
|
||||
temp3, _ = s.stringToTime("2018-11-03 00:00:00")
|
||||
temp4, _ = s.stringToTime("2018-11-03 23:59:59")
|
||||
temp5, _ = s.stringToTime("2018-11-11 00:00:00")
|
||||
temp6, _ = s.stringToTime("2018-11-11 23:59:59")
|
||||
|
||||
if t1.Before(temp1) && t2.After(temp2) {
|
||||
days += 2
|
||||
}
|
||||
|
||||
if t1.Before(temp3) && t2.After(temp4) {
|
||||
days -= 1
|
||||
}
|
||||
|
||||
if t1.Before(temp5) && t2.After(temp6) {
|
||||
days -= 1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//removeCRCFInString replace CRCF with space in string
|
||||
func (s *Service) removeCRCFInString(str string) (rstr string) {
|
||||
rstr = strings.Replace(str, "\n", "", -1)
|
||||
return strings.Replace(rstr, "\r", "", -1)
|
||||
}
|
20
app/service/ep/saga-agent/BUILD
Normal file
20
app/service/ep/saga-agent/BUILD
Normal file
@ -0,0 +1,20 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//app/service/ep/saga-agent/cmd:all-srcs",
|
||||
"//app/service/ep/saga-agent/conf:all-srcs",
|
||||
"//app/service/ep/saga-agent/path:all-srcs",
|
||||
"//app/service/ep/saga-agent/service/agent:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
5
app/service/ep/saga-agent/CHANGELOG.md
Normal file
5
app/service/ep/saga-agent/CHANGELOG.md
Normal file
@ -0,0 +1,5 @@
|
||||
# gitlab-CI相关的工具
|
||||
|
||||
# v1.0.0
|
||||
1.创建工具agent用于Caster里自动拉取配置动态注册gitlab-runner
|
||||
2.调整changelog和path两个小工具到该目录下
|
12
app/service/ep/saga-agent/CONTRIBUTORS.md
Normal file
12
app/service/ep/saga-agent/CONTRIBUTORS.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Owner
|
||||
muyang
|
||||
fangrongchang
|
||||
|
||||
# Author
|
||||
muyang
|
||||
yubaihai
|
||||
wangweizhen
|
||||
fangrongchang
|
||||
|
||||
# Reviewer
|
||||
muyang
|
18
app/service/ep/saga-agent/OWNERS
Normal file
18
app/service/ep/saga-agent/OWNERS
Normal file
@ -0,0 +1,18 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- fangrongchang
|
||||
- muyang
|
||||
- wangweizhen
|
||||
- yubaihai
|
||||
labels:
|
||||
- ep
|
||||
- service
|
||||
- service/ep/saga-agent
|
||||
options:
|
||||
no_parent_owners: true
|
||||
reviewers:
|
||||
- fangrongchang
|
||||
- muyang
|
||||
- wangweizhen
|
||||
- yubaihai
|
40
app/service/ep/saga-agent/cmd/BUILD
Normal file
40
app/service/ep/saga-agent/cmd/BUILD
Normal file
@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "cmd",
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["main.go"],
|
||||
importpath = "go-common/app/service/ep/saga-agent/cmd",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/saga-agent/conf:go_default_library",
|
||||
"//app/service/ep/saga-agent/service/agent:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
37
app/service/ep/saga-agent/cmd/main.go
Normal file
37
app/service/ep/saga-agent/cmd/main.go
Normal file
@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"go-common/app/service/ep/saga-agent/conf"
|
||||
"go-common/app/service/ep/saga-agent/service/agent"
|
||||
"go-common/library/log"
|
||||
)
|
||||
|
||||
func listenSignal() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1,
|
||||
syscall.SIGUSR2, syscall.SIGTSTP)
|
||||
select {
|
||||
case <-sigs:
|
||||
log.Info("get sig=%v\n, RunnerUnRegisterAll", sigs)
|
||||
agent.RunnerUnRegisterAll()
|
||||
default:
|
||||
log.Info("get sig=%v\n", sigs)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Info("agent start......")
|
||||
err := conf.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
go listenSignal()
|
||||
go agent.UpdateRegister()
|
||||
agent.ExecRegister()
|
||||
agent.RunnerStart()
|
||||
agent.RunnerUnRegisterAll()
|
||||
}
|
44
app/service/ep/saga-agent/conf/BUILD
Normal file
44
app/service/ep/saga-agent/conf/BUILD
Normal file
@ -0,0 +1,44 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["conf_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor/github.com/smartystreets/goconvey/convey:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["conf.go"],
|
||||
importpath = "go-common/app/service/ep/saga-agent/conf",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//library/conf:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//vendor/github.com/BurntSushi/toml:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
117
app/service/ep/saga-agent/conf/conf.go
Normal file
117
app/service/ep/saga-agent/conf/conf.go
Normal file
@ -0,0 +1,117 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"go-common/library/conf"
|
||||
"go-common/library/log"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Runner ...
|
||||
type Runner struct {
|
||||
URL string
|
||||
Token string
|
||||
Name string
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Offline bool
|
||||
Runner []Runner
|
||||
}
|
||||
|
||||
// Conf ...
|
||||
var (
|
||||
confPath string
|
||||
client *conf.Client
|
||||
filename string
|
||||
reload chan bool
|
||||
Conf config
|
||||
RunnerMap map[string]Runner
|
||||
HostName string
|
||||
)
|
||||
|
||||
func load() (err error) {
|
||||
var (
|
||||
s string
|
||||
ok bool
|
||||
)
|
||||
if s, ok = client.Value(filename); !ok {
|
||||
err = errors.Errorf("load config center error [%s]", filename)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("config data: %s", s)
|
||||
Conf = config{}
|
||||
if _, err = toml.Decode(s, &Conf); err != nil {
|
||||
err = errors.Wrapf(err, "could not decode config err(%+v)", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func configCenter() (err error) {
|
||||
if filename == "" {
|
||||
return errors.New("filename is null,please set the environment(RUNNER_REPONAME)")
|
||||
}
|
||||
if client, err = conf.New(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = load(); err != nil {
|
||||
return
|
||||
}
|
||||
client.Watch(filename)
|
||||
go func() {
|
||||
for range client.Event() {
|
||||
log.Info("config reload")
|
||||
|
||||
if load() != nil {
|
||||
log.Error("config reload error (%v)", err)
|
||||
} else {
|
||||
reload <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func osHostName() string {
|
||||
var (
|
||||
host string
|
||||
err error
|
||||
)
|
||||
if host, err = os.Hostname(); err != nil {
|
||||
host = "unKown-Hostname"
|
||||
log.Error("%+v", errors.WithStack(err))
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&confPath, "conf", "", "config path")
|
||||
}
|
||||
|
||||
// Init ...
|
||||
func Init() (err error) {
|
||||
filename = os.Getenv("CONF_FILENAME")
|
||||
RunnerMap = make(map[string]Runner)
|
||||
HostName = osHostName()
|
||||
reload = make(chan bool, 1)
|
||||
if confPath == "" {
|
||||
err = configCenter()
|
||||
return
|
||||
}
|
||||
if _, err = toml.DecodeFile(confPath, &Conf); err != nil {
|
||||
log.Error("toml.DecodeFile(%s) err(%+v)", confPath, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReloadEvent ...
|
||||
func ReloadEvent() <-chan bool {
|
||||
return reload
|
||||
}
|
30
app/service/ep/saga-agent/conf/conf_test.go
Normal file
30
app/service/ep/saga-agent/conf/conf_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func showConf() {
|
||||
for _, v := range Conf.Runner {
|
||||
fmt.Println(v.URL, v.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
flag.Set("conf", "../service/agent/runners.toml")
|
||||
if err = Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
showConf()
|
||||
}
|
||||
|
||||
func TestConf(t *testing.T) {
|
||||
Convey("test conf", t, func() {
|
||||
So(Conf.Runner, ShouldNotBeEmpty)
|
||||
})
|
||||
}
|
36
app/service/ep/saga-agent/path/BUILD
Normal file
36
app/service/ep/saga-agent/path/BUILD
Normal file
@ -0,0 +1,36 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "path",
|
||||
embed = [":go_default_library"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["main.go"],
|
||||
importpath = "go-common/app/service/ep/saga-agent/path",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//vendor/github.com/pkg/errors:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
1
app/service/ep/saga-agent/path/build.sh
Normal file
1
app/service/ep/saga-agent/path/build.sh
Normal file
@ -0,0 +1 @@
|
||||
env GOOS=linux GOARCH=amd64 go build -o pathchecker
|
99
app/service/ep/saga-agent/path/main.go
Normal file
99
app/service/ep/saga-agent/path/main.go
Normal file
@ -0,0 +1,99 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
flagDep = flag.String("dep", "main,live,openplatform,ep", "department list , split by comma")
|
||||
flagPrefix = flag.String("prefix", `business`, "prefix path")
|
||||
flagService = flag.String("serivce", "interface,job,admin,service", "service type")
|
||||
// flagWhite prefix下允许的dir名称
|
||||
flagWhite = flag.String("white", "", "white subpath from prefix , split by comma")
|
||||
)
|
||||
|
||||
const (
|
||||
codeSuccess = 0
|
||||
codeFail = 1
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
var (
|
||||
depList []string
|
||||
serviceList []string
|
||||
filePathList []string
|
||||
whiteDirList []string
|
||||
)
|
||||
filePathList = flag.Args()
|
||||
if len(filePathList) <= 0 {
|
||||
fmt.Println("No file to check")
|
||||
os.Exit(codeSuccess)
|
||||
}
|
||||
|
||||
depList = strings.Split(*flagDep, ",")
|
||||
serviceList = strings.Split(*flagService, ",")
|
||||
for _, wd := range strings.Split(*flagWhite, ",") {
|
||||
if wd != "" {
|
||||
whiteDirList = append(whiteDirList, strings.Join([]string{*flagPrefix, wd}, "/"))
|
||||
}
|
||||
}
|
||||
code := check(filePathList, serviceList, depList, whiteDirList)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func check(filePathList []string, serviceTypeList []string, depList []string, whiteDirList []string) (code int) {
|
||||
var (
|
||||
regDep = strings.Join(depList, "|")
|
||||
serviceType = strings.Join(serviceTypeList, "|")
|
||||
regStr = fmt.Sprintf(`%s/(%s)/(%s)`, *flagPrefix, serviceType, regDep)
|
||||
reg *regexp.Regexp
|
||||
flag = true
|
||||
failedFiles []string
|
||||
err error
|
||||
)
|
||||
regStr = strings.Replace(regStr, "/", `\/`, -1)
|
||||
if reg, err = regexp.Compile(regStr); err != nil {
|
||||
err = errors.Wrapf(err, "regexp : %s", regStr)
|
||||
fmt.Printf("%+v\n", err)
|
||||
code = codeFail
|
||||
return
|
||||
}
|
||||
for _, p := range filePathList {
|
||||
if strings.HasPrefix(p, *flagPrefix) {
|
||||
if whiteCheck(whiteDirList, p) {
|
||||
continue
|
||||
}
|
||||
if !reg.MatchString(p) {
|
||||
failedFiles = append(failedFiles, p)
|
||||
flag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !flag {
|
||||
fmt.Println("invalid files : ")
|
||||
for _, f := range failedFiles {
|
||||
fmt.Printf("\t%s\n", f)
|
||||
}
|
||||
code = codeFail
|
||||
} else {
|
||||
code = codeSuccess
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func whiteCheck(whiteDirList []string, path string) bool {
|
||||
for _, wd := range whiteDirList {
|
||||
if strings.HasPrefix(path, wd) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
43
app/service/ep/saga-agent/service/agent/BUILD
Normal file
43
app/service/ep/saga-agent/service/agent/BUILD
Normal file
@ -0,0 +1,43 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_test",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["agent_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
rundir = ".",
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["agent.go"],
|
||||
data = ["runners.toml"],
|
||||
importpath = "go-common/app/service/ep/saga-agent/service/agent",
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//app/service/ep/saga-agent/conf:go_default_library",
|
||||
"//library/log:go_default_library",
|
||||
"//vendor/github.com/pkg/errors:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
146
app/service/ep/saga-agent/service/agent/agent.go
Normal file
146
app/service/ep/saga-agent/service/agent/agent.go
Normal file
@ -0,0 +1,146 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"go-common/app/service/ep/saga-agent/conf"
|
||||
"go-common/library/log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
hostName string
|
||||
)
|
||||
|
||||
const _runnerWorkDir = "/data/gitlab-runner"
|
||||
|
||||
func shellExec(dir string, name string, args ...string) (stdout string, stderr string, err error) {
|
||||
defer func() {
|
||||
log.Info("Shell Exec dir[%s] name[%s] args[%+v] stdout[%s] stderr[%s] err[%+v]", dir, name, args, stdout, stderr, err)
|
||||
}()
|
||||
if _, err = exec.LookPath(name); err != nil {
|
||||
err = errors.Wrapf(err, "LookPath(%s) failed", name)
|
||||
return
|
||||
}
|
||||
var (
|
||||
stdo = new(bytes.Buffer)
|
||||
stde = new(bytes.Buffer)
|
||||
cmd *exec.Cmd
|
||||
)
|
||||
cmd = exec.Command(name, args...)
|
||||
cmd.Stdout = stdo
|
||||
cmd.Stderr = stde
|
||||
cmd.Dir = dir
|
||||
if err = cmd.Run(); err != nil {
|
||||
err = errors.Wrapf(err, "cmd.Run(%s,%s,%v) failed", dir, name, args)
|
||||
}
|
||||
stdout = stdo.String()
|
||||
stderr = stde.String()
|
||||
return
|
||||
}
|
||||
|
||||
// RunnerRegister ...
|
||||
func RunnerRegister(url, token, name string) (stdout string, stderr string, err error) {
|
||||
var args string
|
||||
|
||||
if (url == "") || (token == "") {
|
||||
err = fmt.Errorf("RunnerRegister invalid parameters(url:%s token:%s)", url, token)
|
||||
log.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("gitlab-runner register url:%s token:%s", url, token)
|
||||
args = fmt.Sprintf("gitlab-runner register -u %s -r %s --name %s --executor shell -n true -c /data/gitlab-runner/config.toml",
|
||||
url, token, name)
|
||||
if stdout, stderr, err = shellExec(_runnerWorkDir, "/bin/sh", "-c", args); err != nil {
|
||||
log.Error(strings.TrimSpace(stdout + "\n" + stderr))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunnerUnRegister ...
|
||||
func RunnerUnRegister(name string) (stdout string, stderr string, err error) {
|
||||
var args string
|
||||
log.Info("gitlab-runner unregister name:%s", name)
|
||||
args = fmt.Sprintf("gitlab-runner unregister -n %s", name)
|
||||
if stdout, stderr, err = shellExec(_runnerWorkDir, "/bin/sh", "-c", args); err != nil {
|
||||
log.Error(strings.TrimSpace(stdout + "\n" + stderr))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunnerUnRegisterAll ...
|
||||
func RunnerUnRegisterAll() (stdout string, stderr string, err error) {
|
||||
var args string
|
||||
log.Info("gitlab-runner RunnerUnregisterAll")
|
||||
args = "gitlab-runner unregister --all-runners"
|
||||
if stdout, stderr, err = shellExec(_runnerWorkDir, "/bin/sh", "-c", args); err != nil {
|
||||
log.Error(strings.TrimSpace(stdout + "\n" + stderr))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RunnerStart ...
|
||||
func RunnerStart() (stdout string, stderr string, err error) {
|
||||
var args string
|
||||
log.Info("gitlab-runner start...")
|
||||
args = fmt.Sprintf("gitlab-runner run -d %s", _runnerWorkDir)
|
||||
if stdout, stderr, err = shellExec(_runnerWorkDir, "/bin/sh", "-c", args); err != nil {
|
||||
log.Error(strings.TrimSpace(stdout + "\n" + stderr))
|
||||
}
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// ExecRegister ...
|
||||
func ExecRegister() {
|
||||
var tmpMap map[string]conf.Runner
|
||||
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
log.Error("execRegister: %+v %s", x, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("execRegister... runner.offline %t,runner.len:%d", conf.Conf.Offline, len(conf.Conf.Runner))
|
||||
if conf.Conf.Offline {
|
||||
conf.RunnerMap = make(map[string]conf.Runner)
|
||||
RunnerUnRegisterAll()
|
||||
return
|
||||
}
|
||||
tmpMap = make(map[string]conf.Runner)
|
||||
for _, v := range conf.Conf.Runner {
|
||||
tmpMap[v.Token] = v
|
||||
_, ok := conf.RunnerMap[v.Token]
|
||||
if !ok {
|
||||
RunnerRegister(v.URL, v.Token, hostName+"-"+v.Name)
|
||||
conf.RunnerMap[v.Token] = v
|
||||
}
|
||||
}
|
||||
for _, v := range conf.RunnerMap {
|
||||
_, ok := tmpMap[v.Token]
|
||||
if !ok {
|
||||
RunnerUnRegister(hostName + "-" + v.Name)
|
||||
delete(conf.RunnerMap, v.Token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateRegister ...
|
||||
func UpdateRegister() {
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
log.Error("updateRegister: %+v %s", x, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
for range conf.ReloadEvent() {
|
||||
log.Info("updateRegister")
|
||||
ExecRegister()
|
||||
}
|
||||
}
|
33
app/service/ep/saga-agent/service/agent/agent_test.go
Normal file
33
app/service/ep/saga-agent/service/agent/agent_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunnerStart(t *testing.T) {
|
||||
stdout, stderr, err := RunnerStart()
|
||||
if err != nil {
|
||||
t.Error(stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerRegister(t *testing.T) {
|
||||
stdout, stderr, err := RunnerRegister("http://gitlab.bilibili.co/", "pxZPKWk1JQHLHNzYyj5p", "mac-test-1")
|
||||
if err != nil {
|
||||
t.Error(stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerUnRegister(t *testing.T) {
|
||||
stdout, stderr, err := RunnerUnRegister("mac-test-1")
|
||||
if err != nil {
|
||||
t.Error(stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunnerUnRegisterAll(t *testing.T) {
|
||||
stdout, stderr, err := RunnerUnRegisterAll()
|
||||
if err != nil {
|
||||
t.Error(stdout, stderr)
|
||||
}
|
||||
}
|
10
app/service/ep/saga-agent/service/agent/runners.toml
Normal file
10
app/service/ep/saga-agent/service/agent/runners.toml
Normal file
@ -0,0 +1,10 @@
|
||||
ID=123
|
||||
[[runner]]
|
||||
url="http://gitlab.bilibili.co/"
|
||||
token="pxZPKWk1JQHLHNzYyj5p"
|
||||
name="mac-test-1"
|
||||
|
||||
[[runner]]
|
||||
url="http://gitlab.bilibili.co/"
|
||||
token="crkm4z2ZQjfgQRi_puup"
|
||||
name="mac-test-2"
|
Reference in New Issue
Block a user