diff --git a/client/components/settings/adminReports.jade b/client/components/settings/adminReports.jade index a24b73b95..c9df62892 100644 --- a/client/components/settings/adminReports.jade +++ b/client/components/settings/adminReports.jade @@ -26,6 +26,11 @@ template(name="adminReports") i.fa.fa-magic | {{_ 'rulesReportTitle'}} + li + a.js-report-boards(data-id="report-boards") + i.fa.fa-magic + | {{_ 'boardsReportTitle'}} + li a.js-report-cards(data-id="report-cards") i.fa.fa-magic @@ -42,6 +47,8 @@ template(name="adminReports") +orphanedFilesReport else if showRulesReport.get +rulesReport + else if showBoardsReport.get + +boardsReport else if showCardsReport.get +cardsReport @@ -139,3 +146,20 @@ template(name="cardsReport") td {{userNames card.assignees }} else div {{_ 'no-results' }} + +template(name="boardsReport") + h1 {{_ 'boardsReportTitle'}} + if resultsCount + table.table + tr + th Title + th Id + th Members + + each board in results + tr + td {{abbreviate board.title }} + td {{abbreviate board._id }} + td {{userNames board.members }} + else + div {{_ 'no-results' }} diff --git a/client/components/settings/adminReports.js b/client/components/settings/adminReports.js index fb44e5041..68414fd9c 100644 --- a/client/components/settings/adminReports.js +++ b/client/components/settings/adminReports.js @@ -11,6 +11,7 @@ BlazeComponent.extendComponent({ showOrphanedFilesReport: new ReactiveVar(false), showRulesReport: new ReactiveVar(false), showCardsReport: new ReactiveVar(false), + showBoardsReport: new ReactiveVar(false), sessionId: null, onCreated() { @@ -27,6 +28,7 @@ BlazeComponent.extendComponent({ 'click a.js-report-orphaned-files': this.switchMenu, 'click a.js-report-rules': this.switchMenu, 'click a.js-report-cards': this.switchMenu, + 'click a.js-report-boards': this.switchMenu, }, ]; }, @@ -38,6 +40,9 @@ BlazeComponent.extendComponent({ this.showFilesReport.set(false); this.showBrokenCardsReport.set(false); this.showOrphanedFilesReport.set(false); + this.showRulesReport.set(false) + this.showBoardsReport.set(false); + this.showCardsReport.set(false); if (this.subscription) { this.subscription.stop(); } @@ -70,6 +75,11 @@ BlazeComponent.extendComponent({ this.showRulesReport.set(true); this.loading.set(false); }); + } else if ('report-boards' === targetID) { + this.subscription = Meteor.subscribe('boardsReport', () => { + this.showBoardsReport.set(true); + this.loading.set(false); + }); } else if ('report-cards' === targetID) { const qp = new QueryParams(); qp.addPredicate(OPERATOR_LIMIT, 300); @@ -149,6 +159,24 @@ class AdminReport extends BlazeComponent { } }.register('rulesReport')); +(class extends AdminReport { + collection = Boards; + + userNames(members) { + let text = ''; + members.forEach(member => { + const user = Users.findOne(member.userId); + text += text ? ', ' : ''; + if (user) { + text += user.username; + } else { + text += member.userId + } + }); + return text; + } +}.register('boardsReport')); + (class extends AdminReport { collection = Cards; diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 849a30b91..164817ede 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -1061,6 +1061,7 @@ "orphanedFilesReportTitle": "Orphaned Files Report", "reports": "Reports", "rulesReportTitle": "Rules Report", + "boardsReportTitle": "Boards Report", "cardsReportTitle": "Cards Report", "copy-swimlane": "Copy Swimlane", "copySwimlanePopup-title": "Copy Swimlane", diff --git a/server/publications/boards.js b/server/publications/boards.js index 23306e087..045c1c810 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -2,6 +2,8 @@ // non-archived boards: // 1. that the user is a member of // 2. the user has starred +import Users from "../../models/users"; + Meteor.publish('boards', function() { const userId = this.userId; // Ensure that the user is connected. If it is not, we need to return an empty @@ -58,6 +60,58 @@ Meteor.publish('boards', function() { ); }); +Meteor.publish('boardsReport', function() { + const userId = this.userId; + // Ensure that the user is connected. If it is not, we need to return an empty + // array to tell the client to remove the previously published docs. + if (!Match.test(userId, String) || !userId) return []; + + boards = Boards.find( + { + archived: false, + $or: [ + { + // _id: { $in: starredBoards }, // Commented out, to get a list of all public boards + permission: 'public', + }, + { members: { $elemMatch: { userId, isActive: true } } }, + ], + }, + { + fields: { + _id: 1, + boardId: 1, + archived: 1, + slug: 1, + title: 1, + description: 1, + color: 1, + members: 1, + orgs: 1, + teams: 1, + permission: 1, + type: 1, + sort: 1, + }, + sort: { sort: 1 /* boards default sorting */ }, + }, + ); + + const users = []; + boards.forEach(board => { + if (board.members) { + board.members.forEach(member => { + users.push(member.userId); + }); + } + }) + + return [ + boards, + Users.find({ _id: { $in: users } }, { fields: Users.safeFields }), + ] +}); + Meteor.publish('archivedBoards', function() { const userId = this.userId; if (!Match.test(userId, String)) return [];