Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

18
app/service/ep/BUILD Normal file
View 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
View File

@ -0,0 +1,8 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- daiwei
labels:
- ep
- new-project
- service

View 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"],
)

View 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报表产品验收时长统计产品验收打回需求统计测试中打回需求统计待测试时长统计纯测试时长统计故事墙流图统计延迟需求列表

View File

@ -0,0 +1,14 @@
# Owner
maojian
yuanmin
fengyifeng
xuneng
# Author
yuanmin
fengyifeng
xuneng
# Reviewer
zhapuyu
wangxu01

View 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

View File

@ -0,0 +1,19 @@
# footman-service
# 项目简介
### 背景/Background
* 目前移动端crash信息会上报到buglybugly系统查询数据比较慢且没有足够的数据分析功能影响开发查询分析crash的效率
* 目前项目需求bug等信息记录在tapd, 同样tapd系统现有的报表功能并不满足我们PMO的需求使PMO拿不到充足的数据做分析
* 于是Footman项目为了解决这些问题孕育而生
### 概览Overview
* Footman会根据开发和PMO的需求从bugly和tapd拉取原始数据生成自定义的报表数据
# 编译环境
# 依赖包
# 编译执行

View File

@ -0,0 +1 @@
# HTTP API文档

View 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"],
)

View 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"],
)

View 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()
}

View 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
}
}
}

View 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"

View 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"],
)

View 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)
}
}
}

View 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"],
)

View 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
}

View 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"],
)

View 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
}

View 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()

View 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

View 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)
}
}

View 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)
})
}

View 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
}

View 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
}

View 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
}

View File

@ -0,0 +1 @@
2106504811

View 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"],
)

View 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"`
}

View 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"
)

View 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
}

View 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"`
}

View 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"],
)

View 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))
}

View 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))
}

View 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)
}

View 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)
}

View 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"],
)

View 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
}

View 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
}
}
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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()
}

View 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()
}

View 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)
}

View 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"],
)

View File

@ -0,0 +1,5 @@
# gitlab-CI相关的工具
# v1.0.0
1.创建工具agent用于Caster里自动拉取配置动态注册gitlab-runner
2.调整changelog和path两个小工具到该目录下

View File

@ -0,0 +1,12 @@
# Owner
muyang
fangrongchang
# Author
muyang
yubaihai
wangweizhen
fangrongchang
# Reviewer
muyang

View 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

View 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"],
)

View 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()
}

View 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"],
)

View 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
}

View 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)
})
}

View 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"],
)

View File

@ -0,0 +1 @@
env GOOS=linux GOARCH=amd64 go build -o pathchecker

View 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
}

View 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"],
)

View 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()
}
}

View 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)
}
}

View 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"