Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: do not query whether there are documents under the branch #1575

Merged
merged 3 commits into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/dop/endpoints/apidoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"github.com/erda-project/erda/pkg/swagger/oas3"
)

// APIDocWebsocket is the handler for the editor session,
// all edtion in the same websocket session.
func (e *Endpoints) APIDocWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
identity, err := user.GetIdentityInfo(r)
if err != nil {
Expand Down
48 changes: 17 additions & 31 deletions modules/dop/services/apidocsvc/api_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"

"github.com/pkg/errors"
Expand Down Expand Up @@ -286,16 +285,16 @@ func (svc *Service) copyAPIDoc(orgID uint64, userID, srcInode, dstPinode string)
return data, nil
}

// 查询应用下所有的分支, 构成节点列表
// listBranches lists all branches as nodes
func (svc *Service) listBranches(orgID, appID uint64, userID string) ([]*apistructs.FileTreeNodeRspData, *errorresp.APIError) {
// query branches by appID
// there is no project-level file tree, so "pinode != 0" is not discussed
appIDStr := strconv.FormatUint(appID, 10)

// 查询 application (由于不存在项目级目录树, 所以不讨论 pinode != 0 的情况)
branches, err := svc.branchRuleSvc.GetAllValidBranchWorkspaces(int64(appID))
if err != nil {
return nil, apierrors.ListChildrenNodes.InternalError(errors.Wrap(err, "failed to GetAllValidBranchWorkspace"))
}
// 查询 application 数据构造目录树 node
// query application data and make a tree node
app, err := bdl.Bdl.GetApp(appID)
if err != nil {
return nil, apierrors.ListChildrenNodes.InternalError(errors.Wrap(err, "failed to GetApp"))
Expand All @@ -308,27 +307,12 @@ func (svc *Service) listBranches(orgID, appID uint64, userID string) ([]*apistru
var (
results []*apistructs.FileTreeNodeRspData
isManager, _ = bdl.IsManager(userID, apistructs.AppScope, appID)
m = sync.Map{}
w = sync.WaitGroup{}
)

for _, branch := range branches {
branch := branch
w.Add(1)
go func() {
branchInode := ft.Clone().SetBranchName(branch.Name).Inode()
hasAPIDoc := branchHasAPIDoc(orgID, branchInode)
m.Store(branch, hasAPIDoc)
w.Done()
}()
}
w.Wait()

m.Range(func(key, value interface{}) bool {
branch := key.(*apistructs.ValidBranch)
readOnly := !isManager && branch.IsProtect
meta, _ := json.Marshal(map[string]bool{"readOnly": readOnly, "hasDoc": value.(bool)})
results = append(results, &apistructs.FileTreeNodeRspData{
meta, _ := json.Marshal(map[string]bool{"readOnly": readOnly})
data := &apistructs.FileTreeNodeRspData{
Type: "d",
Inode: ft.Clone().SetBranchName(branch.Name).Inode(),
Pinode: ft.Inode(),
Expand All @@ -338,10 +322,9 @@ func (svc *Service) listBranches(orgID, appID uint64, userID string) ([]*apistru
CreatorID: "",
UpdaterID: "",
Meta: meta,
})

return true
})
}
results = append(results, data)
}

sort.Slice(results, func(i, j int) bool {
return results[i].Name < results[j].Name
Expand All @@ -350,21 +333,23 @@ func (svc *Service) listBranches(orgID, appID uint64, userID string) ([]*apistru
return results, nil
}

// 查询分支下所有 API 文档
// listAPIDocs lists all api docs names as nodes
func (svc *Service) listAPIDocs(orgID uint64, userID, pinode string) ([]*apistructs.FileTreeNodeRspData, *errorresp.APIError) {
return svc.listServices(orgID, userID, pinode, apiDocsPathFromRepoRoot, func(node apistructs.TreeEntry) bool {
return node.Type == "blob" && matchSuffix(node.Name, suffixYaml, suffixYml)
})
}

// 查询分支下所有的 migration 的 service 名称, 即 migration 的目录名
// listSchemas lists all module's migration schemas names as nodes
func (svc *Service) listSchemas(orgID uint64, userID, pinode string) ([]*apistructs.FileTreeNodeRspData, *errorresp.APIError) {
return svc.listServices(orgID, userID, pinode, migrationsPathFromRepoRoot, func(node apistructs.TreeEntry) bool {
return node.Type == "tree"
})
}

// 列出目录树的 service 层的节点列表
// listServices lists services names as nodes.
// pathFromRepoRoot the path to read from repo root,
// filter the condition to filter services.
func (svc *Service) listServices(orgID uint64, userID, pinode, pathFromRepoRoot string, filter func(node apistructs.TreeEntry) bool) ([]*apistructs.FileTreeNodeRspData, *errorresp.APIError) {
ft, err := bundle.NewGittarFileTree(pinode)
if err != nil {
Expand All @@ -379,7 +364,8 @@ func (svc *Service) listServices(orgID uint64, userID, pinode, pathFromRepoRoot

orgIDStr := strconv.FormatUint(orgID, 10)

// 查找目录下的文档. 允许错误, 错误则认为目录下没有任何文档
// query the docs under the path,
// if the error is not nil, it is considered that there is no document here.
nodes, err := bdl.Bdl.GetGittarTreeNode(ft.TreePath(), orgIDStr, true)
if err != nil {
logrus.Errorf("failed to GetGittarTreeNode, err: %v", err)
Expand All @@ -388,7 +374,7 @@ func (svc *Service) listServices(orgID uint64, userID, pinode, pathFromRepoRoot
return nil, nil
}

// 文档是否只读: 如果用户是应用管理员, 则
// is the doc readonly
isManager, _ := bdl.IsManager(userID, apistructs.AppScope, appID)
readOnly := !isManager && isBranchProtected(ft.ApplicationID(), ft.BranchName(), svc.branchRuleSvc)
meta, _ := json.Marshal(map[string]bool{"readOnly": readOnly})
Expand Down
11 changes: 6 additions & 5 deletions modules/dop/services/apidocsvc/filetree.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ func (svc *Service) CopyNode(req *apistructs.APIDocMvCpNodeReq) (*apistructs.Fil
}
}

// 获取子节点列表
// ListChildren lists all children nodes
// if the parent node is 0, it lists all branches,
// else if the TreeName is "api-docs", lists all api docs, if the TreeName is "schemas", lists schemas.
func (svc *Service) ListChildren(req *apistructs.APIDocListChildrenReq) ([]*apistructs.FileTreeNodeRspData, *errorresp.APIError) {
// 对于 pinode == 0 的情况, 直接列出应用下的分支, 不用管要查找的是 API 文档还是 schema
// if pinode==0, list all branches
switch {
case req.QueryParams.Pinode == "0" && req.QueryParams.Scope == "application":
appID, err := strconv.ParseUint(req.QueryParams.ScopeID, 10, 64)
Expand All @@ -102,11 +104,10 @@ func (svc *Service) ListChildren(req *apistructs.APIDocListChildrenReq) ([]*apis
return svc.listBranches(req.OrgID, appID, req.Identity.UserID)

case req.QueryParams.Pinode == "0":
return nil, apierrors.ListChildrenNodes.InvalidParameter(errors.Errorf("scope 错误, 本目录树仅支持应用层级, scope: %s", req.QueryParams.Scope))
return nil, apierrors.ListChildrenNodes.InvalidParameter(errors.Errorf("scope error, there is no preject-level file tree, scope: %s", req.QueryParams.Scope))
}

// 对于 pinode != 0 的情况, 列出应用下的 "服务"
// ps: 目前没有项目级的目录树, 所以不必讨论节点的层级, 直接查叶子节点即可; 以后有项目级目录树后, 需要讨论节点层级
// pinode != 0, query docs or schemas
switch req.URIParams.TreeName {
case TreeNameAPIDocs:
return svc.listAPIDocs(req.OrgID, req.Identity.UserID, req.QueryParams.Pinode)
Expand Down
20 changes: 10 additions & 10 deletions modules/dop/services/apidocsvc/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,29 +156,29 @@ func CommitAPIDocModifies(orgID uint64, userID, repo, commitMessage, serviceName
// 如果文档内容为空, 则填充默认内容;
// 前端提交的文档格式为 json, 转换为 yaml 后再提交.
func commitAPIDocContent(orgID uint64, userID, repo, commitMessage, serviceName, content, branch, action string) error {
// 如果 content 为空, 给一个默认的 content
// if the content is empty, set default
if content == "" {
content = defaultOAS3Content(serviceName)
}

// 前端传来的 content 是 json, 转换为 yaml
data, err := oasconv.JSONToYAML([]byte(content))
// load structural Openapi 3
v3, err := oas3.LoadFromData([]byte(content))
if err != nil {
return errors.Wrap(err, "failed to JSONToYAML")
return errors.Wrap(err, "failed to load Openapi 3 structural from content")
}

// 校验文档合法性 仅作反序列化校验
if _, err := oas3.LoadFromData(data); err != nil {
return err
// trans to yaml
data, err := oas3.MarshalYaml(v3)
if err != nil {
return errors.Wrap(err, "failed to MarshalYaml")
}

content = string(data)

// 统一更改文件名和路径后缀
// change filename suffix to .yaml
serviceName = mustSuffix(serviceName, suffixYaml)
filenameFromRepoRoot := filepath.Join(apiDocsPathFromRepoRoot, serviceName)

// 在 gittar 仓库对应分支下创建文档: .dice/apidocs/{docName}
// make commit: .dice/apidocs/{docName}
var commit = apistructs.GittarCreateCommitRequest{
Message: commitMessage,
Actions: []apistructs.EditActionItem{{
Expand Down
2 changes: 1 addition & 1 deletion modules/dop/services/apidocsvc/ws_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func (h *APIDocWSHandler) handleAutoSave(tx *dbclient.TX, w websocket.ResponseWr
return nil
}
if data.Inode != h.ft.Inode() {
h.responseError(w, errors.New("inode 错误"))
h.responseError(w, errors.New("inode error"))
return nil
}

Expand Down
8 changes: 4 additions & 4 deletions modules/dop/services/apidocsvc/ws_upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ import (
"github.com/erda-project/erda/pkg/http/httpserver/errorresp"
)

// http ==> ws
// 直接将错误处理写到了 w http.ResponseWriter, 所以调用方不必对错误进行额外处理了
// Upgrade updates the scheme http to ws.
// It writes the error to w http.ResponseWriter, so the caller need not to handle the error.
func (svc *Service) Upgrade(w http.ResponseWriter, r *http.Request, req *apistructs.WsAPIDocHandShakeReq) *errorresp.APIError {
ft, err := bundle.NewGittarFileTree(req.URIParams.Inode)
if err != nil {
return apierrors.WsUpgrade.InvalidParameter("不合法的 inode")
return apierrors.WsUpgrade.InvalidParameter("invalid inode")
}
appID, err := strconv.ParseUint(ft.ApplicationID(), 10, 64)
if err != nil {
return apierrors.WsUpgrade.InvalidParameter("不合法的 inode")
return apierrors.WsUpgrade.InvalidParameter("invalid inode")
}

h := APIDocWSHandler{
Expand Down