From 36b2767a6de2b1134a6629d6e6d3199f5d641d02 Mon Sep 17 00:00:00 2001 From: Elliott Stoneham Date: Wed, 7 Sep 2016 18:07:37 +0100 Subject: [PATCH] 1st cut of various overview sub-reports, a WIP --- .../components/section/trello/type-editor.js | 1 + core/section/trello/archive_template.go | 17 ++ core/section/trello/boards_template.go | 17 ++ core/section/trello/graphs_template.go | 28 +++ core/section/trello/labels_template.go | 24 +++ core/section/trello/master_template.go | 87 ++++++++ core/section/trello/members_template.go | 25 +++ core/section/trello/model.go | 61 +++--- core/section/trello/trad_template.go | 38 ++++ core/section/trello/trello.go | 193 ++++++++++++++++-- 10 files changed, 445 insertions(+), 46 deletions(-) create mode 100644 core/section/trello/archive_template.go create mode 100644 core/section/trello/boards_template.go create mode 100644 core/section/trello/graphs_template.go create mode 100644 core/section/trello/labels_template.go create mode 100644 core/section/trello/master_template.go create mode 100644 core/section/trello/members_template.go create mode 100644 core/section/trello/trad_template.go diff --git a/app/app/components/section/trello/type-editor.js b/app/app/components/section/trello/type-editor.js index 9392f020..26123f97 100644 --- a/app/app/components/section/trello/type-editor.js +++ b/app/app/components/section/trello/type-editor.js @@ -188,6 +188,7 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin, .then(function (boards) { self.set('busy', false); self.set('boards', boards); + self.set('config.boards', boards); // save the boards in the config too self.getBoardLists(); }, function (error) { //jshint ignore: line self.set('busy', false); diff --git a/core/section/trello/archive_template.go b/core/section/trello/archive_template.go new file mode 100644 index 00000000..5a4fbe86 --- /dev/null +++ b/core/section/trello/archive_template.go @@ -0,0 +1,17 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const archiveTemplate = ` +Archive?
+
+` diff --git a/core/section/trello/boards_template.go b/core/section/trello/boards_template.go new file mode 100644 index 00000000..54ff508e --- /dev/null +++ b/core/section/trello/boards_template.go @@ -0,0 +1,17 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const boardsTemplate = ` +All Boards
+
+` diff --git a/core/section/trello/graphs_template.go b/core/section/trello/graphs_template.go new file mode 100644 index 00000000..770e9b98 --- /dev/null +++ b/core/section/trello/graphs_template.go @@ -0,0 +1,28 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const graphsTemplate = ` +Single Boards (graphs)
+{{range $b := .Boards}} +
+

There are {{ $b.CardCount }} cards across {{ $b.ListCount }} lists for board {{$b.Board.Name}}.

+
+ {{range $data := $b.Data}} +
+ {{ $data.List.Name }} +
+ {{end}} +
+
+{{end}} +` diff --git a/core/section/trello/labels_template.go b/core/section/trello/labels_template.go new file mode 100644 index 00000000..a9afd9db --- /dev/null +++ b/core/section/trello/labels_template.go @@ -0,0 +1,24 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const labelsTemplate = ` +Labels
+{{range $l := .SharedLabels}} + {{ $l.Name }} + {{range $brd := $l.Boards}} + {{ $brd }}, + {{end}} +
+{{end}} +
+` diff --git a/core/section/trello/master_template.go b/core/section/trello/master_template.go new file mode 100644 index 00000000..ba06042d --- /dev/null +++ b/core/section/trello/master_template.go @@ -0,0 +1,87 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const renderTemplate = ` +

Header for Trello Multi-Board Test (in master_template.go)

+` + labelsTemplate + + boardsTemplate + + graphsTemplate + + membersTemplate + + archiveTemplate + + tradTemplate + + `` + +const oldSave = ` +Labels
+{{range $l := .SharedLabels}} + {{ $l.Name }} + {{range $brd := $l.Boards}} + {{ $brd }}, + {{end}} +
+{{end}} +
+All Boards
+
+Single Boards (graphs)
+{{range $b := .Boards}} +
+

There are {{ $b.CardCount }} cards across {{ $b.ListCount }} lists for board {{$b.Board.Name}}.

+
+ {{range $data := $b.Data}} +
+ {{ $data.List.Name }} +
+ {{end}} +
+
+{{end}} + +
+Member Stats
+{{range $m := .MemberBoardAssign}} + + {{$m.MemberName}} : + {{range $ac := $m.AssignCounts}} + {{$ac.BoardName}} ({{$ac.Count}}), + {{end}} +
+{{end}} +
+Archive?
+
+ +Previous version + +{{range $b := .Boards}} +

Non-printable

+
+

There are {{ $b.CardCount }} cards across {{ $b.ListCount }} lists for board {{$b.Board.Name}}.

+
+
{{$b.Board.Name}}
+ {{range $data := $b.Data}} +
+
{{ $data.List.Name }}
+ {{range $card := $data.Cards}} + +
+ {{ $card.Name }} +
+
+ {{end}} +
+ {{end}} +
+
+{{end}} +` diff --git a/core/section/trello/members_template.go b/core/section/trello/members_template.go new file mode 100644 index 00000000..08c924ef --- /dev/null +++ b/core/section/trello/members_template.go @@ -0,0 +1,25 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const membersTemplate = ` +Member Stats
+{{range $m := .MemberBoardAssign}} + + {{$m.MemberName}} : + {{range $ac := $m.AssignCounts}} + {{$ac.BoardName}} ({{$ac.Count}}), + {{end}} +
+{{end}} +
+` diff --git a/core/section/trello/model.go b/core/section/trello/model.go index 34f70236..a81e5092 100644 --- a/core/section/trello/model.go +++ b/core/section/trello/model.go @@ -13,37 +13,16 @@ package trello import "strings" -const renderTemplate = ` -

Non-printable

-
-

There are {{ .CardCount }} cards across {{ .ListCount }} lists for board {{.Board.Name}}.

-
-
{{.Board.Name}}
- {{range $data := .Data}} -
-
{{ $data.List.Name }}
- {{range $card := $data.Cards}} - -
- {{ $card.Name }} -
-
- {{end}} -
- {{end}} -
-
-` - type secrets struct { - Token string `json:"token"` + Token string `json:"token"` } type trelloConfig struct { - AppKey string `json:"appKey"` - Token string `json:"token"` - Board trelloBoard `json:"board"` - Lists []trelloList `json:"lists"` + AppKey string `json:"appKey"` + Token string `json:"token"` + Board trelloBoard `json:"board"` // TODO review + Lists []trelloList `json:"lists"` // TODO review + Boards []trelloBoard `json:"boards"` } func (c *trelloConfig) Clean() { @@ -197,9 +176,35 @@ type trelloListCards struct { Cards []trelloCard } -type trelloRender struct { +type trelloRenderBoard struct { Board trelloBoard Data []trelloListCards CardCount int ListCount int } + +type trelloSharedLabel struct { + Name string + Color string + Boards []string +} + +type trelloBoardAssignCount struct { + BoardName string + Count int +} + +type trelloBoardAssign struct { + AvatarHash string + MemberName string + AssignCounts []trelloBoardAssignCount +} + +type trelloRender struct { + Boards []trelloRenderBoard + + // items below are generated during the render phase + SharedLabels []trelloSharedLabel + MembersByID map[string]trelloMember + MemberBoardAssign []trelloBoardAssign +} diff --git a/core/section/trello/trad_template.go b/core/section/trello/trad_template.go new file mode 100644 index 00000000..e67865ba --- /dev/null +++ b/core/section/trello/trad_template.go @@ -0,0 +1,38 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package trello + +const tradTemplate = ` +Previous version + +{{range $b := .Boards}} +

Non-printable

+
+

There are {{ $b.CardCount }} cards across {{ $b.ListCount }} lists for board {{$b.Board.Name}}.

+
+
{{$b.Board.Name}}
+ {{range $data := $b.Data}} +
+
{{ $data.List.Name }}
+ {{range $card := $data.Cards}} + +
+ {{ $card.Name }} +
+
+ {{end}} +
+ {{end}} +
+
+{{end}} +` diff --git a/core/section/trello/trello.go b/core/section/trello/trello.go index de6c3fc5..7e7f6693 100644 --- a/core/section/trello/trello.go +++ b/core/section/trello/trello.go @@ -18,10 +18,11 @@ import ( "html/template" "io/ioutil" "net/http" + "sort" "github.com/documize/community/core/api/request" - "github.com/documize/community/core/section/provider" "github.com/documize/community/core/log" + "github.com/documize/community/core/section/provider" ) var meta provider.TypeMeta @@ -147,25 +148,20 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R log.IfErr(ctx.SaveSecrets(string(b))) } -// Render just sends back HMTL as-is. +// Render the payload using the template. func (*Provider) Render(ctx *provider.Context, config, data string) string { - raw := []trelloListCards{} - payload := trelloRender{} + var payload = trelloRender{} var c = trelloConfig{} - json.Unmarshal([]byte(data), &raw) + json.Unmarshal([]byte(data), &payload) json.Unmarshal([]byte(config), &c) - payload.Board = c.Board - payload.Data = raw - payload.ListCount = len(raw) - - for _, list := range raw { - payload.CardCount += len(list.Cards) - } + buildPayloadAnalysis(&c, &payload) t := template.New("trello") - t, _ = t.Parse(renderTemplate) + var err error + t, err = t.Parse(renderTemplate) + log.IfErr(err) buffer := new(bytes.Buffer) t.Execute(buffer, payload) @@ -178,13 +174,41 @@ func (*Provider) Refresh(ctx *provider.Context, config, data string) string { var c = trelloConfig{} json.Unmarshal([]byte(config), &c) - refreshed, err := getCards(c) + save := trelloRender{} + save.Boards = make([]trelloRenderBoard, 0, len(c.Boards)) - if err != nil { - return data + for _, board := range c.Boards { + + var payload = trelloRenderBoard{} + + c.Board = board + c.AppKey = request.ConfigString(meta.ConfigHandle(), "appKey") + + lsts, err := getLists(c) + log.IfErr(err) + if err == nil { + c.Lists = lsts + } + + for l := range c.Lists { + c.Lists[l].Included = true + } + + refreshed, err := getCards(c) + log.IfErr(err) + + payload.Board = c.Board + payload.Data = refreshed + payload.ListCount = len(refreshed) + + for _, list := range refreshed { + payload.CardCount += len(list.Cards) + } + + save.Boards = append(save.Boards, payload) } - j, err := json.Marshal(refreshed) + j, err := json.Marshal(save) if err != nil { log.Error("unable to marshall trello cards", err) @@ -197,6 +221,7 @@ func (*Provider) Refresh(ctx *provider.Context, config, data string) string { // Helpers func getBoards(config trelloConfig) (boards []trelloBoard, err error) { req, err := http.NewRequest("GET", fmt.Sprintf("https://api.trello.com/1/members/me/boards?fields=id,name,url,closed,prefs,idOrganization&key=%s&token=%s", config.AppKey, config.Token), nil) + log.IfErr(err) client := &http.Client{} res, err := client.Do(req) @@ -230,7 +255,9 @@ func getBoards(config trelloConfig) (boards []trelloBoard, err error) { } func getLists(config trelloConfig) (lists []trelloList, err error) { - req, err := http.NewRequest("GET", fmt.Sprintf("https://api.trello.com/1/boards/%s/lists/open?key=%s&token=%s", config.Board.ID, config.AppKey, config.Token), nil) + uri := fmt.Sprintf("https://api.trello.com/1/boards/%s/lists/open?key=%s&token=%s", config.Board.ID, config.AppKey, config.Token) + req, err := http.NewRequest("GET", uri, nil) + log.IfErr(err) client := &http.Client{} res, err := client.Do(req) @@ -264,6 +291,7 @@ func getCards(config trelloConfig) (listCards []trelloListCards, err error) { } req, err := http.NewRequest("GET", fmt.Sprintf("https://api.trello.com/1/lists/%s/cards?key=%s&token=%s", list.ID, config.AppKey, config.Token), nil) + log.IfErr(err) client := &http.Client{} res, err := client.Do(req) @@ -294,3 +322,132 @@ func getCards(config trelloConfig) (listCards []trelloListCards, err error) { return listCards, nil } + +func fetchMember(config *trelloConfig, render *trelloRender, memberID string) (memberInfo trelloMember) { + memberInfo.FullName = "(unknown)" + + if render.MembersByID == nil { + render.MembersByID = make(map[string]trelloMember) + } + found := false + if memberInfo, found = render.MembersByID[memberID]; found { + return + } + render.MembersByID[memberID] = memberInfo // write unknown, so that we do not retry on errors + + if len(config.AppKey) == 0 { + config.AppKey = request.ConfigString(meta.ConfigHandle(), "appKey") + } + uri := fmt.Sprintf("https://api.trello.com/1/members/%s?key=%s&token=%s", memberID, config.AppKey, config.Token) + req, err := http.NewRequest("GET", uri, nil) + if err != nil { + log.IfErr(err) + return + } + client := &http.Client{} + res, err := client.Do(req) + if err != nil { + log.IfErr(err) + return + } + + if res.StatusCode != http.StatusOK { + log.ErrorString("Trello fetch member HTTP status not OK") + return + } + + defer res.Body.Close() + + dec := json.NewDecoder(res.Body) + err = dec.Decode(&memberInfo) + if err != nil { + log.IfErr(err) + return + } + + render.MembersByID[memberID] = memberInfo + + return +} + +func buildPayloadAnalysis(config *trelloConfig, render *trelloRender) { + + // pre-process labels + type labT struct { + color string + boards map[string]bool + } + labels := make(map[string]labT) + + // pre-process member stats + memberBoardCount := make(map[string]map[string]int) + + // main loop + for _, brd := range render.Boards { + for _, lst := range brd.Data { + for _, crd := range lst.Cards { + + // process labels + for _, lab := range crd.Labels { + if _, exists := labels[lab.Name]; !exists { + labels[lab.Name] = labT{color: lab.Color, boards: make(map[string]bool)} + } + labels[lab.Name].boards[brd.Board.Name] = true + } + + // process member stats + for _, mem := range crd.MembersID { + if _, exists := memberBoardCount[mem]; !exists { + memberBoardCount[mem] = make(map[string]int) + } + memberBoardCount[mem][brd.Board.ID]++ + } + } + } + } + + //post-process labels + labs := make([]string, 0, len(labels)) + for lname := range labels { + labs = append(labs, lname) + } + sort.Strings(labs) + for _, lname := range labs { + thisLabel := labels[lname].boards + if l := len(thisLabel); l > 1 { + brds := make([]string, 0, l) + for bname := range thisLabel { + brds = append(brds, bname) + } + sort.Strings(brds) + render.SharedLabels = append(render.SharedLabels, trelloSharedLabel{ + Name: lname, Color: labels[lname].color, Boards: brds, + }) + } + } + + //post-process member stats + mNames := make([]string, 0, len(memberBoardCount)) + for mID := range memberBoardCount { + memInfo := fetchMember(config, render, mID) + mNames = append(mNames, memInfo.FullName) + } + sort.Strings(mNames) + for _, mNam := range mNames { + for mem, brdCounts := range memberBoardCount { + memInfo := fetchMember(config, render, mem) + if mNam == memInfo.FullName { + render.MemberBoardAssign = append(render.MemberBoardAssign, trelloBoardAssign{MemberName: mNam, AvatarHash: memInfo.AvatarHash}) + for _, b := range render.Boards { + if count, ok := brdCounts[b.Board.ID]; ok { + render.MemberBoardAssign[len(render.MemberBoardAssign)-1].AssignCounts = + append(render.MemberBoardAssign[len(render.MemberBoardAssign)-1].AssignCounts, + trelloBoardAssignCount{BoardName: b.Board.Name, Count: count}) + } + } + goto found + } + } + found: + } +}