1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 13:19:43 +02:00

simplified Trello integration to display visual board

This commit is contained in:
Harvey Kandola 2017-01-24 10:53:58 -08:00
parent 17b01c2de4
commit ea209e387d
15 changed files with 205 additions and 1104 deletions

View file

@ -50,8 +50,7 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin,
token: "",
user: null,
board: null,
lists: [],
boards: []
lists: []
};
}
@ -102,12 +101,6 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin,
this.set('noBoards', false);
if (is.undefined(self.get('initDateTimePicker'))) {
$.datetimepicker.setLocale('en');
$('#trello-since').datetimepicker();
self.set('initDateTimePicker', "Done");
}
if (is.null(board) || is.undefined(board)) {
if (boards.length) {
board = boards[0];
@ -117,34 +110,30 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin,
this.set('config.board', boards.findBy('id', board.id));
}
if (is.null(board.id) || is.undefined(board.id)) {
self.set('busy', false);
} else {
this.get('sectionService').fetch(page, "lists", self.get('config'))
.then(function (lists) {
let savedLists = self.get('config.lists');
if (savedLists === null) {
savedLists = [];
this.get('sectionService').fetch(page, "lists", self.get('config'))
.then(function (lists) {
let savedLists = self.get('config.lists');
if (savedLists === null) {
savedLists = [];
}
lists.forEach(function (list) {
let saved = savedLists.findBy("id", list.id);
let included = true;
if (is.not.undefined(saved)) {
included = saved.included;
}
lists.forEach(function (list) {
let saved = savedLists.findBy("id", list.id);
let included = true;
if (is.not.undefined(saved)) {
included = saved.included;
}
list.included = included;
});
self.set('config.lists', lists);
self.set('busy', false);
}, function (error) { //jshint ignore: line
self.set('busy', false);
self.set('authenticated', false);
self.showNotification("Unable to fetch board lists");
console.log(error);
list.included = included;
});
}
self.set('config.lists', lists);
self.set('busy', false);
}, function (error) { //jshint ignore: line
self.set('busy', false);
self.set('authenticated', false);
self.showNotification("Unable to fetch board lists");
console.log(error);
});
},
actions: {
@ -161,15 +150,6 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin,
}
},
onBoardCheckbox(id) {
let boards = this.get('config.boards');
let board = boards.findBy('id', id);
if (board !== null) {
Ember.set(board, 'included', !board.included);
}
},
auth() {
if (this.get('appKey') === "") {
$("#trello-appkey").addClass('error').focus();
@ -207,8 +187,6 @@ export default Ember.Component.extend(SectionMixin, NotifierMixin, TooltipMixin,
self.get('sectionService').fetch(page, "boards", self.get('config'))
.then(function (boards) {
self.set('busy', false);
boards.unshift({ id: null, namePath: "< do not show >", backgroundColor: "white" }); // add the non-selection to the front
self.set('config.boards', boards); // save the boards in the config too
self.set('boards', boards);
self.getBoardLists();
}, function (error) { //jshint ignore: line

View file

@ -1,103 +1,89 @@
.section-trello-editor {
.section-trello-board {
width: 100%;
padding: 10px;
white-space: nowrap;
overflow: auto
}
.section-trello-board-title {
font-weight: bold;
color: #fff;
font-size: 16px;
}
.section-trello-list {
background-color: #e2e4e6;
padding: 10px;
border-radius: 3px;
margin: 10px 10px 0 0;
max-width: 300px;
}
.section-trello-list-title {
font-weight: bold;
color: #4c4c4c;
font-size: 14px;
margin: 5px;
}
.section-trello-list-checkbox {
vertical-align: text-bottom;
}
.trello-list {
margin-bottom: 10px;
i {
vertical-align: middle;
}
.trello-label {
font-size: 11px;
color: #fff;
padding: 0 8px;
margin-right: 5px;
border-radius: 2px;
box-shadow: inset 0 -1px 0 rgba(0,0,0,.12);
display: inline-block;
line-height: 22px;
}
}
.section-trello-board {
width: 100%;
padding: 10px;
white-space: nowrap;
overflow: auto
}
.section-trello-board-title {
font-weight: bold;
color: #fff;
font-size: 16px;
}
.section-trello-list {
background-color: #e2e4e6;
padding: 10px;
border-radius: 3px;
margin: 10px 10px 0 0;
max-width: 300px;
}
.section-trello-list-title {
font-weight: bold;
color: #4c4c4c;
font-size: 14px;
margin: 5px;
}
.section-trello-list-checkbox {
vertical-align: text-bottom;
}
.section-trello-render {
> .trello-board {
width: 100%;
max-height: 600px;
padding: 10px;
white-space: nowrap;
overflow: auto;
a:hover {
text-decoration: underline;
}
.heading {
h3 {
font-size: 22px;
margin: 0;
font-family: "open_sanslight";
> a {
> .trello-board-title {
font-weight: bold;
color: #fff;
font-size: 16px;
}
}
}
> .trello-list {
background-color: #e2e4e6;
padding: 10px;
border-radius: 3px;
margin: 10px 10px 0 0;
width: 300px;
max-height: 500px;
display: inline-block;
white-space: nowrap;
overflow: auto;
vertical-align: top;
table.trello-single-board {
border: none!important;
text-align: left;
margin:0!important;
}
.trello-single-board thead tr th {
padding: 15px 0;
border-bottom: 1px solid #e1e1e1;
text-transform: uppercase;
font-size: 14px;
span {
color:#838d94;
> .trello-list-title {
font-weight: bold;
color: #4c4c4c;
font-size: 14px;
margin: 0 10px 10px 0;
}
> a {
> .trello-card {
color: #4c4c4c;
border-bottom: 1px solid #CDD2D4;
background-color: #fff;
border-radius: 3px;
padding: 7px 7px;
margin: 5px 0;
font-size: 14px;
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
line-height: 18px;
overflow: hidden;
word-wrap: break-word;
white-space: normal;
cursor: pointer;
vertical-align: top;
}
}
}
}
.trello-single-board tbody tr td {
border: none!important;
padding: 5px 20px 5px 20px !important;
}
.trello-label {
color:white;
font-size: 11px;
padding: 4px 6px;
border-radius: 4px;
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.12);
margin-left: 14px;
}
}
}
}

View file

@ -2,89 +2,52 @@
tip="Trello is the visual way to manage your projects and organize anything (https://trello.com)"
isDirty=(action 'isDirty') onCancel=(action 'onCancel') onAction=(action 'onAction')}}
<div class="section-trello-editor">
{{#if authenticated}}
{{#if noBoards}}
<div class="pull-left width-50">
{{#if noBoards}}
<div class="input-control">
<div class="color-error">You have no team boards to share - personal boards are never shown.</div>
</div>
{{else}}
<!--
<div class="pull-left width-45">
<div class="input-form">
<div class="heading">
<div class="title">Select Board &amp; Lists</div>
<div class="tip">Choose lists to include from board</div>
</div>
<div class="input-control">
<label>Show summary from (default 7 days ago)</label>
{{input id="trello-since" value=config.since type="text" }}<br>
</div>
<div class="input-control">
<label>Select which boards you want to see</label>
<div class="tip">All boards are selectd by default</div>
<div class="trello-board">
{{#each config.boards as |board|}}
{{#if board.id}}
<div class="trello-list" {{action 'onBoardCheckbox' board.id}}>
{{#if board.included}}
<i class="material-icons widget-checkbox checkbox-gray trello-list-checkbox">check_box</i>
{{else}}
<i class="material-icons widget-checkbox checkbox-gray trello-list-checkbox">check_box_outline_blank</i>
{{/if}}
<span class="trello-label" style="background-color: {{board.prefs.backgroundColor}}">{{board.orgName}} / {{board.name}}</span>
</div>
{{/if}}
{{/each}}
<div class="clearfix" />
</div>
</div>
</div>
</div>
<div class="pull-left width-10">&nbsp;</div>
-->
<div class="pull-left width-45">
{{else}}
<div class="input-control">
<label>Individual Board</label>
<div class="tip">Select board</div>
{{ui-select id="boards-dropdown" content=boards action=(action 'onBoardChange') optionValuePath="id" optionLabelPath="namePath" selection=config.board}}
<label>Select Board</label>
<div class="tip">Choose lists to include from board</div>
{{ui-select id="boards-dropdown" content=boards action=(action 'onBoardChange') optionValuePath="id" optionLabelPath="name" selection=config.board}}
</div>
{{#if config.board.id}}
<div class="input-control">
<label>Lists</label>
<div class="tip">Select lists to include</div>
<div class="section-trello-board" style= {{boardStyle}}>
<div class="section-trello-board-title">{{config.board.name}}</div>
{{#each config.lists as |list|}}
<div class="section-trello-list" {{action 'onListCheckbox' list.id}}>
{{#if list.included}}
<i class="material-icons widget-checkbox checkbox-gray section-trello-list-checkbox">check_box</i>
{{else}}
<i class="material-icons widget-checkbox checkbox-gray section-trello-list-checkbox">check_box_outline_blank</i>
{{/if}}
<span class="trello-list-title">{{list.name}}</span>
</div>
{{/each}}
</div>
<div class="input-control">
<label>Lists</label>
<div class="tip">Select lists to include</div>
<div class="section-trello-board" style= {{boardStyle}}>
<div class="section-trello-board-title">{{config.board.name}}</div>
{{#each config.lists as |list|}}
<div class="section-trello-list" {{action 'onListCheckbox' list.id}}>
{{#if list.included}}
<i class="material-icons widget-checkbox checkbox-gray section-trello-list-checkbox">check_box</i>
{{else}}
<i class="material-icons widget-checkbox checkbox-gray section-trello-list-checkbox">check_box_outline_blank</i>
{{/if}}
<span class="trello-list-title">{{list.name}}</span>
</div>
{{/each}}
<div class="clearfix" />
</div>
{{/if}}
</div>
{{/if}}
{{else}}
<div class="pull-left width-50">
<div class="input-control">
<label>Authentication</label>
<div class="tip">Click to authenticate with Trello</div>
</div>
<div class="regular-button button-blue" {{ action 'auth' }}>Authenticate</div>
</div>
{{/if}}
</div>
{{/if}}
</div>
{{/section/base-editor}}
{{else}}
<div class="pull-left width-50">
<form>
<div class="form-header">
<div class="title">Authentication</div>
<div class="tip">Click to authenticate with Trello</div>
</div>
<div class="regular-button button-blue" {{ action 'auth' }}>Authenticate</div>
</form>
</div>
{{/if}}
{{/section/base-editor}}

View file

@ -1 +1,3 @@
{{{page.body}}}
<div class="non-printable">
{{{page.body}}}
</div>

View file

@ -27,7 +27,7 @@ import (
const me = "papertrail"
// Provider represents Gemini
// Provider represents Papertrail
type Provider struct {
}

View file

@ -1,30 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
var activityTranslation = map[string]string{
"add checklist to card": "checklist added to card",
"add member to card": "member added to card",
"comment card": "commented on card",
"create card": "created card",
"create list": "created list",
"delete card": "deleted card",
"update board": "updated board",
"add to team board": "",
"create board": "",
"update card": "updated card",
"update check item state on card": "check item updated on card",
"update list": "updated list",
"add attachment to card": "attachment added to card",
"copy card": "copied card",
"copy comment card": "copied comment on card",
}

View file

@ -1,62 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const archiveTemplate = `
{{if false}}
<div class="section-trello-render">
{{if gt (len .Boards) 0}}
<div class="heading">Deleted and Archived Cards</div>
<p>Changes since {{.Since}}.</p>
<div class="section-trello-render">
<table class="trello-table" class="width-100">
<tbody class="trello">
{{range $b := .Boards}}
<tr>
<td>
<a href="{{ $b.Board.URL }}">
<span class="trello-board" style="background-color: {{$b.Board.Prefs.BackgroundColor}}">{{$b.Board.Name}}</span>
</a>
</td>
<td>
{{range $act := $b.Actions}}
{{if eq $act.Type "deleteCard" }}
Deleted:
{{$act.Data.List.Name}}
{{if ne $act.Data.Card.Name ""}}
: {{$act.Data.Card.Name}}
{{if ne $act.Data.Text ""}}
- {{$act.Data.Text}}
{{end}}
{{end}}
<br>
{{end}}
{{end}}
{{range $arch := $b.Archived}}
Archived:
{{$arch.Name}}
{{if ne $arch.Desc ""}}
- {{$arch.Desc}}
{{end}}
<br>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
{{end}}
`

View file

@ -1,55 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const boardsTemplate = `
<div class="section-trello-render">
{{if gt (len .Boards) 0}}
<div class="heading">Boards</div>
<p>There are {{len .Boards}} boards, {{.ListTotal}} lists, {{.CardTotal}} cards and {{len .MemberBoardAssign}} members. Activity since {{.Since}}</p>
<div class="section-trello-render">
<table class="trello-table" class="width-100">
<tbody class="trello">
{{range $b := .Boards}}
<tr>
<td>
<a href="{{ $b.Board.URL }}">
<div class="trello-board" style="background-color: {{$b.Board.Prefs.BackgroundColor}}">
{{$b.Board.Name}}
<span>{{$b.Board.OrgName}}</span>
</div>
</a>
</td>
<td>
<div class="board-summary">
<!-- {{ len $b.Actions }}{{if eq 1 (len $b.Actions)}} action {{else}} actions {{end}} -->
</div>
<span class="board-meta">
{{range $idx, $act := $b.ActionSummary}}{{if ne $idx 0}}{{- ","}} {{end}}{{$act.Count}} {{$act.Name -}}{{if ne 1 $act.Count}}{{"s" -}}{{end}}{{end}}{{if gt (len $b.Archived) 0}}, {{end}}
{{if gt (len $b.Archived) 0}}
{{len $b.Archived}} {{if eq 1 (len $b.Archived)}}card {{else}} cards {{end}}archived
{{else}}
{{if eq (len $b.ActionSummary) 0}}
no activity
{{end}}
{{end}}
<br>
</span>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
`

View file

@ -1,32 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const graphsTemplate = `
{{if false}}
<div class="heading">Single Boards (graphs)</div>
{{range $b := .Boards}}
<div>
<p>There are {{ $b.CardCount }} cards across {{ $b.ListCount }} lists for board <a href="{{ $b.Board.URL }}">{{$b.Board.Name}}.</a></p>
<div>
{{range $data := $b.Data}}
<div style="background-color: {{$b.Board.Prefs.BackgroundColor}}">
<progress value="{{len $data.Cards}}" max="{{ $b.CardCount }}"></progress> {{ $data.List.Name }}
</div>
{{end}}
</div>
</div>
{{end}}
{{end}}
`

View file

@ -1,37 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const labelsTemplate = `
<div class="section-trello-render">
{{if gt (len .SharedLabels) 0}}
<div class="heading">Labels</div>
<p>There are {{len .SharedLabels}} common labels across the boards.</p>
<div class="section-trello-render">
<table class="trello-table" class="width-100">
<tbody class="trello">
{{range $l := .SharedLabels}}
<tr>
<td class="no-width">
<span class="trello-label" style="background-color: {{ $l.Color }}">{{ $l.Name }} ({{len $l.Boards}})</span>
</td>
<td>
{{range $idx, $brd := $l.Boards}}{{if gt $idx 0}}, {{end}}<a class="link" href="{{$brd.URL}}">{{$brd.OrgName}}/{{$brd.Name}}</a>{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
`

View file

@ -1,27 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const renderTemplate = `
{{if eq .Since ""}}
<p>Preparing...</p>
{{else}}
<!-- <p>Activity since {{.Since}} for boards:
{{range $idx, $brd := .Boards}}{{if gt $idx 0}}, {{end}}<a class="link" href="{{$brd.Board.URL}}">{{$brd.Board.OrgName}}/{{$brd.Board.Name}}</a>{{end}}.</p> -->
{{end}}` +
//labelsTemplate +
//boardsTemplate +
//graphsTemplate +
//membersTemplate +
//archiveTemplate +
tradTemplate +
``

View file

@ -1,44 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const membersTemplate = `
<div class="section-trello-render">
{{if gt (len .Boards) 0}}
<div class="heading">Members</div>
<p>
There {{if eq 1 (len .MemberBoardAssign)}} is one member {{else}} are {{len .MemberBoardAssign}} members {{end}} assigned to {{.CardAssignTotal}} cards of the total {{.CardTotal}} cards across {{len .Boards}} boards.
</p>
<div class="section-trello-render">
<table class="trello-table no-width">
<tbody>
{{range $m := .MemberBoardAssign}}
<tr>
<td class="no-width">
<img class="trello-avatar" src="https://trello-avatars.s3.amazonaws.com/{{$m.AvatarHash}}/50.png" alt="Member Avatar">
</td>
<td>
<div class="member-name">{{$m.MemberName}}</div>
<div class="member-meta">
{{range $idx, $ac := $m.AssignCounts}}{{if gt $idx 0}}, {{end}}{{$ac.BoardName}} ({{$ac.Count}}){{end}}
</div>
<div class="margin-top-10"></div>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</div>
`

View file

@ -11,25 +11,38 @@
package trello
import (
"strings"
"time"
)
import "strings"
const renderTemplate = `
<div class="section-trello-render">
<p>There are {{ .CardCount }} cards across {{ .ListCount }} lists for board <a href="{{ .Board.URL }}">{{.Board.Name}}.</a></p>
<div class="trello-board" style="background-color: {{.Board.Prefs.BackgroundColor}}">
<a href="{{ .Board.URL }}"><div class="trello-board-title">{{.Board.Name}}</div></a>
{{range $data := .Data}}
<div class="trello-list">
<div class="trello-list-title">{{ $data.List.Name }}</div>
{{range $card := $data.Cards}}
<a href="{{ $card.URL }}">
<div class="trello-card">
{{ $card.Name }}
</div>
</a>
{{end}}
</div>
{{end}}
</div>
</div>
`
type secrets struct {
Token string `json:"token"`
}
type trelloConfig struct {
AppKey string `json:"appKey"`
Token string `json:"token"`
Board trelloBoard `json:"board"`
Lists []trelloList `json:"lists"`
Boards []trelloBoard `json:"boards"`
Since string `json:"since,omitempty"`
SincePtr *time.Time `json:"-"`
OrgByID map[string]trelloOrganization `json:"-"`
AppKey string `json:"appKey"`
Token string `json:"token"`
Board trelloBoard `json:"board"`
Lists []trelloList `json:"lists"`
}
func (c *trelloConfig) Clean() {
@ -38,73 +51,6 @@ func (c *trelloConfig) Clean() {
}
// Trello objects based upon https://github.com/VojtechVitek/go-trello
type trelloOrganization struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
Desc string `json:"desc"`
DescData string `json:"descData"`
URL string `json:"url"`
Website string `json:"website"`
LogoHash string `json:"logoHash"`
Products []string `json:"products"`
PowerUps []string `json:"powerUps"`
}
type trelloAction struct {
ID string `json:"id"`
IDMemberCreator string `json:"idMemberCreator"`
Data struct {
DateLastEdited string `json:"dateLastEdited"`
ListBefore struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"listBefore"`
ListAfter struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"listAfter"`
CheckItem struct {
ID string `json:"id"`
State string `json:"state"`
Name string `json:"name"`
} `json:"checkItem"`
CheckList struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"checklist"`
List struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"list"`
TextData struct {
Emoji struct{} `json:"emoji"`
} `json:"textData"`
Board struct {
ID string `json:"id"`
Name string `json:"name"`
ShortLink string `json:"shortLink"`
} `json:"board"`
Card struct {
ID string `json:"id"`
Name string `json:"name"`
ShortLink string `json:"shortLink"`
IDShort int `json:"idShort"`
} `json:"card"`
Text string `json:"text"`
} `json:"data"`
Type string `json:"type"`
Date string `json:"date"`
MemberCreator struct {
ID string `json:"id"`
AvatarHash string `json:"avatarHash"`
FullName string `json:"fullName"`
Initials string `json:"initials"`
Username string `json:"username"`
} `json:"memberCreator"`
}
type trelloMember struct {
ID string `json:"id"`
AvatarHash string `json:"avatarHash"`
@ -147,7 +93,6 @@ type trelloBoard struct {
Name string `json:"name"`
Closed bool `json:"closed"`
OrganizationID string `json:"idOrganization"`
OrgName string `json:"orgName"`
Pinned bool `json:"pinned"`
URL string `json:"url"`
ShortURL string `json:"shortUrl"`
@ -183,8 +128,6 @@ type trelloBoard struct {
Blue string `json:"blue"`
Purple string `json:"purple"`
} `json:"labelNames"`
Included bool `json:"included"` // indicates whether we display this board
NamePath string `json:"namePath"` // the "team / board" form
}
type trelloBoardBackground struct {
@ -253,48 +196,9 @@ type trelloListCards struct {
Cards []trelloCard
}
type trelloActionSummaryEntry struct {
Name string
Count int
}
type trelloRenderBoard struct {
Board trelloBoard
Data []trelloListCards
CardCount int
ListCount int
Actions []trelloAction
ActionSummary []trelloActionSummaryEntry
Archived []trelloCard
}
type trelloSharedLabel struct {
Name string
Color string
Boards []trelloBoard
}
type trelloBoardAssignCount struct {
BoardName string
Count int
}
type trelloBoardAssign struct {
AvatarHash string
MemberName string
AssignCounts []trelloBoardAssignCount
}
type trelloRender struct {
Boards []trelloRenderBoard
Since string
Detail trelloRenderBoard
// items below are generated during the render phase
SharedLabels []trelloSharedLabel
MembersByID map[string]trelloMember
MemberBoardAssign []trelloBoardAssign
CardAssignTotal int
CardTotal int
ListTotal int
Board trelloBoard
Data []trelloListCards
CardCount int
ListCount int
}

View file

@ -1,57 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. 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 <sales@documize.com>.
//
// https://documize.com
package trello
const tradTemplate = `
<div class="section-trello-render">
{{if ne .Detail.Board.ID ""}}
<div class="heading">
<p>There are {{ .Detail.CardCount }} cards across {{ .Detail.ListCount }} lists on <a href="{{ .Detail.Board.URL }}">{{.Detail.Board.Name}} Board</a></p>
</div>
<div class="section-trello-render non-printable">
<div class="single-trello-board">
<table class="trello-single-board" style="width: 100%;">
{{range $data := .Detail.Data}}
<thead>
<tr>
<th class="title">{{ $data.List.Name }} <span>&middot; {{len $data.Cards}} cards</span></th>
<th></th>
</tr>
</thead>
<tbody>
{{range $card := $data.Cards}}
<tr>
<td>
<a href="{{ $card.URL }}">
<div class="trello-card">
{{ $card.Name }}
</div>
</a>
</td>
<td style="text-align:right;">
{{range $label := $card.Labels}}
<span class="trello-label" style="background-color:{{$label.Color}}"> {{$label.Name}} </span>
{{end}}
</td>
</tr>
{{end}}
</tbody>
{{end}}
</table>
</div>
</div>
{{end}}
</div>
`

View file

@ -18,10 +18,6 @@ import (
"html/template"
"io/ioutil"
"net/http"
"sort"
"strings"
"time"
"unicode"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/log"
@ -32,6 +28,7 @@ var meta provider.TypeMeta
func init() {
meta = provider.TypeMeta{}
meta.ID = "c455a552-202e-441c-ad79-397a8152920b"
meta.Title = "Trello"
meta.Description = "Embed cards from boards and lists"
@ -39,11 +36,11 @@ func init() {
meta.PageType = "tab"
}
// Provider represents Trello
// Provider represents GitHub
type Provider struct {
}
// Meta describes us
// Meta describes us.
func (*Provider) Meta() provider.TypeMeta {
return meta
}
@ -103,7 +100,7 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R
provider.WriteJSON(w, render)
case "boards":
render, err := getBoards(&config)
render, err := getBoards(config)
if err != nil {
log.IfErr(err)
@ -152,32 +149,28 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R
log.IfErr(ctx.SaveSecrets(string(b)))
}
// Render the payload using the template.
// Render just sends back HMTL as-is.
func (*Provider) Render(ctx *provider.Context, config, data string) string {
var payload = trelloRender{}
raw := []trelloListCards{}
payload := trelloRender{}
var c = trelloConfig{}
json.Unmarshal([]byte(data), &payload)
json.Unmarshal([]byte(data), &raw)
json.Unmarshal([]byte(config), &c)
buildPayloadAnalysis(&c, &payload)
payload.Board = c.Board
payload.Data = raw
payload.ListCount = len(raw)
for _, list := range raw {
payload.CardCount += len(list.Cards)
}
t := template.New("trello")
var err error
t, err = t.Parse(renderTemplate)
if err != nil {
log.IfErr(err)
return ""
}
t, _ = t.Parse(renderTemplate)
buffer := new(bytes.Buffer)
err = t.Execute(buffer, payload)
if err != nil {
log.IfErr(err)
return ""
}
t.Execute(buffer, payload)
return buffer.String()
}
@ -185,80 +178,15 @@ func (*Provider) Render(ctx *provider.Context, config, data string) string {
// Refresh just sends back data as-is.
func (*Provider) Refresh(ctx *provider.Context, config, data string) string {
var c = trelloConfig{}
log.IfErr(json.Unmarshal([]byte(config), &c))
json.Unmarshal([]byte(config), &c)
save := trelloRender{}
save.Boards = make([]trelloRenderBoard, 0, len(c.Boards))
refreshed, err := getCards(c)
if len(c.Since) >= len("yyyy/mm/dd hh:ss") {
var since time.Time
tt := []byte("yyyy-mm-ddThh:mm:00Z")
for _, i := range []int{0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15} {
tt[i] = c.Since[i]
}
err := since.UnmarshalText(tt)
if err != nil {
log.ErrorString("Date unmarshall '" + c.Since + "'->'" + string(tt) + "' error: " + err.Error())
} else {
c.SincePtr = &since
}
}
dateMessage := ""
if c.SincePtr == nil {
dateMessage = " (the last 7 days)"
since := time.Now().AddDate(0, 0, -7)
c.SincePtr = &since
c.Since = (*c.SincePtr).Format("2006/01/02 ")
}
save.Since = (*c.SincePtr).Format("January 2, 2006") + dateMessage
c.AppKey = request.ConfigString(meta.ConfigHandle(), "appKey")
if c.Board.ID != "" { // set up detail board
var err error
save.Detail.Board = c.Board
save.Detail.Data, err = getCards(c)
log.IfErr(err)
save.Detail.ListCount = len(save.Detail.Data)
for _, list := range save.Detail.Data {
save.Detail.CardCount += len(list.Cards)
}
if err != nil {
return data
}
for _, board := range c.Boards {
if board.Included && board.ID != "" {
var payload = trelloRenderBoard{}
c.Board = board
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)
}
payload.Actions, payload.Archived = fetchBoardActions(&c, &save, board.ID, c.Since)
save.Boards = append(save.Boards, payload)
}
}
j, err := json.Marshal(save)
j, err := json.Marshal(refreshed)
if err != nil {
log.Error("unable to marshall trello cards", err)
@ -269,48 +197,8 @@ func (*Provider) Refresh(ctx *provider.Context, config, data string) string {
}
// Helpers
func getOrg(config *trelloConfig, orgID string) (*trelloOrganization, error) {
if config.OrgByID == nil {
config.OrgByID = make(map[string]trelloOrganization)
}
if org, found := config.OrgByID[orgID]; found {
return &org, nil
}
req, err := http.NewRequest("GET", fmt.Sprintf(
"https://api.trello.com/1/organizations/%s?fields=name,desc&key=%s&token=%s",
orgID, config.AppKey, config.Token), nil)
log.IfErr(err)
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error: HTTP status code %d", res.StatusCode)
}
b := trelloOrganization{}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
err = dec.Decode(&b)
if err != nil {
fmt.Println(err)
return nil, err
}
config.OrgByID[orgID] = b
return &b, nil
}
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)
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)
client := &http.Client{}
res, err := client.Do(req)
@ -327,45 +215,24 @@ func getBoards(config *trelloConfig) (boards []trelloBoard, err error) {
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
err = dec.Decode(&b)
if err != nil {
fmt.Println(err)
return nil, err
}
// we only show open, team boards (not personal)
for _, b := range b {
if !b.Closed && len(b.OrganizationID) > 0 {
if o, e := getOrg(config, b.OrganizationID); e == nil {
b.OrgName = o.Name
b.NamePath = o.Name + " / " + b.Name
} else {
log.Error("failed to get organisation infomation", e)
}
boards = append(boards, b)
}
}
for bx, bd := range boards {
for _, cd := range config.Boards {
if bd.ID == cd.ID {
boards[bx].Included = cd.Included // to pick up the previous selection or not
goto foundID
}
}
boards[bx].Included = false // don't include boards by default
foundID:
if err != nil {
fmt.Println(err)
return nil, err
}
return boards, nil
}
func getLists(config trelloConfig) (lists []trelloList, err error) {
if config.Board.ID == "" {
return
}
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)
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)
client := &http.Client{}
res, err := client.Do(req)
@ -399,7 +266,6 @@ 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)
@ -430,257 +296,3 @@ 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 fetchBoardActions(config *trelloConfig, render *trelloRender, boardID string, since string) (actions []trelloAction, archived []trelloCard) {
sinceString := since[:10]
if len(config.AppKey) == 0 {
config.AppKey = request.ConfigString(meta.ConfigHandle(), "appKey")
}
{
uri := fmt.Sprintf("https://api.trello.com/1/boards/%s/actions?limit=1000&since=%s&key=%s&token=%s", boardID, sinceString, 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 board actions HTTP status not OK")
return
}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
err = dec.Decode(&actions)
if err != nil {
log.IfErr(err)
return
}
}
{
uri := fmt.Sprintf("https://api.trello.com/1/boards/%s/cards?filter=closed&since=%s&key=%s&token=%s",
boardID, sinceString, 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 {
msg := ""
txt, err := ioutil.ReadAll(res.Body)
if err == nil {
msg = string(txt)
} else {
msg = err.Error()
}
log.ErrorString("Trello fetch board archived HTTP status not OK - " + msg)
return
}
defer res.Body.Close()
dec := json.NewDecoder(res.Body)
err = dec.Decode(&archived)
if err != nil {
log.IfErr(err)
return
}
}
return
}
func buildPayloadAnalysis(config *trelloConfig, render *trelloRender) {
//totals
render.CardTotal = 0
render.CardAssignTotal = 0
render.ListTotal = 0
// pre-process labels
type labT struct {
color string
boards map[string]trelloBoard
}
labels := make(map[string]labT)
// pre-process member stats
memberBoardCount := make(map[string]map[string]int)
// main loop
for brdIdx, brd := range render.Boards {
for _, lst := range brd.Data {
render.ListTotal++
for _, crd := range lst.Cards {
render.CardTotal++
if len(crd.MembersID) > 0 {
render.CardAssignTotal++
}
// process labels
for _, lab := range crd.Labels {
if _, exists := labels[lab.Name]; !exists {
labels[lab.Name] = labT{color: lab.Color, boards: make(map[string]trelloBoard)}
}
labels[lab.Name].boards[brd.Board.URL+" / "+brd.Board.Name] = brd.Board
}
// process member stats
for _, mem := range crd.MembersID {
if _, exists := memberBoardCount[mem]; !exists {
memberBoardCount[mem] = make(map[string]int)
}
memberBoardCount[mem][brd.Board.ID]++
}
}
}
// ActionSummary
actionSummaryMap := make(map[string]int)
for _, act := range brd.Actions {
englishType := ""
for _, c := range act.Type {
if unicode.IsUpper(c) {
englishType += " "
englishType += string(unicode.ToLower(c))
} else {
englishType += string(c)
}
}
englishType = strings.Replace(englishType, "organization", "team", -1)
if newTxt, found := activityTranslation[englishType]; found {
englishType = newTxt
}
if len(englishType) > 0 {
actionSummaryMap[englishType]++
}
}
acts := make([]string, 0, len(actionSummaryMap))
for a := range actionSummaryMap {
acts = append(acts, a)
}
sort.Strings(acts)
render.Boards[brdIdx].ActionSummary = make([]trelloActionSummaryEntry, len(acts))
for k, v := range acts {
render.Boards[brdIdx].ActionSummary[k] = trelloActionSummaryEntry{Name: v, Count: actionSummaryMap[v]}
}
}
//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)
lbrds := []trelloBoard{}
for _, h := range brds {
lbrds = append(lbrds, labels[lname].boards[h])
}
render.SharedLabels = append(render.SharedLabels, trelloSharedLabel{
Name: lname, Color: labels[lname].color, Boards: lbrds,
})
}
}
//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 { // these are already in order
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:
}
}