Bläddra i källkod

Merge branch 'master' into search-debug

John Supplee 3 år sedan
förälder
incheckning
6ef612d04e

+ 65 - 4
client/components/settings/adminReports.jade

@@ -1,5 +1,5 @@
 template(name="adminReports")
-  .setting-content
+  .setting-content.admin-reports-content
     unless currentUser.isAdmin
       | {{_ 'error-notAuthorized'}}
     else
@@ -26,6 +26,16 @@ 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
+                | {{_ 'cardsReportTitle'}}
+
         .main-body
           if loading.get
             +spinner
@@ -37,6 +47,10 @@ template(name="adminReports")
             +orphanedFilesReport
           else if showRulesReport.get
             +rulesReport
+          else if showBoardsReport.get
+            +boardsReport
+          else if showCardsReport.get
+            +cardsReport
 
 
 template(name="brokenCardsReport")
@@ -57,7 +71,7 @@ template(name="rulesReport")
         th actionType
         th activityType
 
-      each rule in rows
+      each rule in results
         tr
           td {{ rule.title }}
           td {{ rule.boardTitle }}
@@ -78,7 +92,7 @@ template(name="filesReport")
         th MD5 Sum
         th ID
 
-      each att in attachmentFiles
+      each att in results
         tr
           td {{ att.filename }}
           td.right {{fileSize att.length }}
@@ -100,7 +114,7 @@ template(name="orphanedFilesReport")
         th MD5 Sum
         th ID
 
-      each att in attachmentFiles
+      each att in results
         tr
           td {{ att.filename }}
           td.right {{fileSize att.length }}
@@ -109,3 +123,50 @@ template(name="orphanedFilesReport")
           td {{ att._id.toHexString }}
   else
     div {{_ 'no-results' }}
+
+template(name="cardsReport")
+  h1 {{_ 'cardsReportTitle'}}
+  if resultsCount
+    table.table
+      tr
+        th Card Title
+        th Board
+        th Swimlane
+        th List
+        th Members
+        th Assignees
+
+      each card in results
+        tr
+          td {{abbreviate card.title }}
+          td {{abbreviate card.board.title }}
+          td {{abbreviate card.swimlane.title }}
+          td {{abbreviate card.list.title }}
+          td {{userNames card.members }}
+          td {{userNames card.assignees }}
+  else
+    div {{_ 'no-results' }}
+
+template(name="boardsReport")
+  h1 {{_ 'boardsReportTitle'}}
+  if resultsCount
+    table.table
+      tr
+        th Title
+        th Id
+        th Permission
+        th Archived?
+        th Members
+        th Organizations
+        th Teams
+
+      each board in results
+        tr
+          td {{abbreviate board.title }}
+          td {{abbreviate board._id }}
+          td {{ board.permission }}
+          td
+            = yesOrNo(board.archived)
+          td {{userNames board.members }}
+  else
+    div {{_ 'no-results' }}

+ 97 - 46
client/components/settings/adminReports.js

@@ -1,6 +1,8 @@
 import { AttachmentStorage } from '/models/attachments';
 import { CardSearchPagedComponent } from '/client/lib/cardSearch';
 import SessionData from '/models/usersessiondata';
+import { QueryParams } from '/config/query-classes';
+import { OPERATOR_LIMIT } from '/config/search-const';
 
 BlazeComponent.extendComponent({
   subscription: null,
@@ -8,10 +10,14 @@ BlazeComponent.extendComponent({
   showBrokenCardsReport: new ReactiveVar(false),
   showOrphanedFilesReport: new ReactiveVar(false),
   showRulesReport: new ReactiveVar(false),
+  showCardsReport: new ReactiveVar(false),
+  showBoardsReport: new ReactiveVar(false),
+  sessionId: null,
 
   onCreated() {
     this.error = new ReactiveVar('');
     this.loading = new ReactiveVar(false);
+    this.sessionId = SessionData.getSessionId();
   },
 
   events() {
@@ -21,6 +27,8 @@ BlazeComponent.extendComponent({
         'click a.js-report-files': this.switchMenu,
         '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,
       },
     ];
   },
@@ -32,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();
       }
@@ -64,68 +75,79 @@ 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);
+        this.subscription = Meteor.subscribe(
+          'globalSearch',
+          this.sessionId,
+          qp.getParams(),
+          qp.text,
+          () => {
+            this.showCardsReport.set(true);
+            this.loading.set(false);
+          },
+        );
       }
     }
   },
 }).register('adminReports');
 
-Template.filesReport.helpers({
-  attachmentFiles() {
+class AdminReport extends BlazeComponent {
+  collection;
+
+  results() {
     // eslint-disable-next-line no-console
     // console.log('attachments:', AttachmentStorage.find());
     // console.log('attachments.count:', AttachmentStorage.find().count());
-    return AttachmentStorage.find();
-  },
-
-  rulesReport() {
-    const rules = [];
-
-    Rules.find().forEach(rule => {
-      rules.push({
-        _id: rule._id,
-        title: rule.title,
-        boardId: rule.boardId,
-        boardTitle: rule.board().title,
-        action: rule.action().fetch(),
-        trigger: rule.trigger().fetch(),
-      });
-    });
+    return this.collection.find();
+  }
 
-    return rules;
-  },
+  yesOrNo(value) {
+    if (value) {
+      return TAPi18n.__('yes');
+    } else {
+      return TAPi18n.__('no');
+    }
+  }
 
   resultsCount() {
-    return AttachmentStorage.find().count();
-  },
+    return this.collection.find().count();
+  }
 
   fileSize(size) {
     return Math.round(size / 1024);
-  },
+  }
 
   usageCount(key) {
     return Attachments.find({ 'copies.attachments.key': key }).count();
-  },
-});
+  }
 
-Template.orphanedFilesReport.helpers({
-  attachmentFiles() {
-    // eslint-disable-next-line no-console
-    // console.log('attachments:', AttachmentStorage.find());
-    // console.log('attachments.count:', AttachmentStorage.find().count());
-    return AttachmentStorage.find();
-  },
+  abbreviate(text) {
+    if (text.length > 30) {
+      return `${text.substr(0, 29)}...`;
+    }
+    return text;
+  }
+}
 
-  resultsCount() {
-    return AttachmentStorage.find().count();
-  },
+(class extends AdminReport {
+  collection = AttachmentStorage;
+}.register('filesReport'));
 
-  fileSize(size) {
-    return Math.round(size / 1024);
-  },
-});
+(class extends AdminReport {
+  collection = AttachmentStorage;
+}.register('orphanedFilesReport'));
+
+(class extends AdminReport {
+  collection = Rules;
 
-Template.rulesReport.helpers({
-  rows() {
+  results() {
     const rules = [];
 
     Rules.find().forEach(rule => {
@@ -139,14 +161,43 @@ Template.rulesReport.helpers({
       });
     });
 
+    // eslint-disable-next-line no-console
     console.log('rows:', rules);
     return rules;
-  },
+  }
+}.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'));
 
-  resultsCount() {
-    return Rules.find().count();
-  },
-});
+(class extends AdminReport {
+  collection = Cards;
+
+  userNames(userIds) {
+    let text = '';
+    userIds.forEach(userId => {
+      const user = Users.findOne(userId);
+      text += text ? ', ' : '';
+      text += user.username;
+    });
+    return text;
+  }
+}.register('cardsReport'));
 
 class BrokenCardsComponent extends CardSearchPagedComponent {
   onCreated() {

+ 3 - 0
client/components/settings/adminReports.styl

@@ -0,0 +1,3 @@
+.admin-reports-content
+  height: auto !important
+

+ 8 - 0
config/const.js

@@ -49,6 +49,14 @@ export const TYPE_LINKED_BOARD = 'cardType-linkedBoard';
 export const TYPE_LINKED_CARD = 'cardType-linkedCard';
 export const TYPE_TEMPLATE_BOARD = 'template-board';
 export const TYPE_TEMPLATE_CONTAINER = 'template-container';
+export const TYPE_TEMPLATE_CARD = 'template-card';
+export const TYPE_TEMPLATE_LIST = 'template-list';
+export const CARD_TYPES = [
+  TYPE_CARD,
+  TYPE_LINKED_CARD,
+  TYPE_LINKED_BOARD,
+  TYPE_TEMPLATE_CARD
+];
 export const ALLOWED_WAIT_SPINNERS = [
   'Bounce',
   'Cube',

+ 2 - 0
i18n/en.i18n.json

@@ -1065,6 +1065,8 @@
   "orphanedFilesReportTitle": "Orphaned Files Report",
   "reports": "Reports",
   "rulesReportTitle": "Rules Report",
+  "boardsReportTitle": "Boards Report",
+  "cardsReportTitle": "Cards Report",
   "copy-swimlane": "Copy Swimlane",
   "copySwimlanePopup-title": "Copy Swimlane",
   "display-card-creator": "Display Card Creator",

+ 23 - 7
models/boards.js

@@ -5,6 +5,7 @@ import {
   TYPE_TEMPLATE_BOARD,
   TYPE_TEMPLATE_CONTAINER,
 } from '/config/const';
+import Users from "./users";
 
 const escapeForRegex = require('escape-string-regexp');
 Boards = new Mongo.Collection('boards');
@@ -1479,7 +1480,17 @@ Boards.userSearch = (
   return Boards.find(selector, projection);
 };
 
-Boards.userBoards = (userId, archived = false, selector = {}) => {
+Boards.userBoards = (
+  userId,
+  archived = false,
+  selector = {},
+  projection = {},
+) => {
+  const user = Users.findOne(userId);
+  if (!user) {
+    return [];
+  }
+
   if (typeof archived === 'boolean') {
     selector.archived = archived;
   }
@@ -1487,15 +1498,20 @@ Boards.userBoards = (userId, archived = false, selector = {}) => {
     selector.type = 'board';
   }
 
-  selector.$or = [{ permission: 'public' }];
-  if (userId) {
-    selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
-  }
-  return Boards.find(selector);
+  selector.$or = [
+    { permission: 'public' },
+    { members: { $elemMatch: { userId, isActive: true } } },
+    { 'orgs.orgId': { $in: user.orgIds() } },
+    { 'teams.teamId': { $in : user.teamIds() } },
+  ];
+
+  return Boards.find(selector, projection);
 };
 
 Boards.userBoardIds = (userId, archived = false, selector = {}) => {
-  return Boards.userBoards(userId, archived, selector).map(board => {
+  return Boards.userBoards(userId, archived, selector, {
+    fields: { _id: 1 },
+  }).map(board => {
     return board._id;
   });
 };

+ 23 - 34
models/users.js

@@ -519,6 +519,18 @@ Users.helpers({
     }
     return '';
   },
+  teamIds() {
+    if (this.teams) {
+      return this.teams.map(team => { return team.teamId });
+    }
+    return [];
+  },
+  orgIds() {
+    if (this.orgs) {
+      return this.orgs.map(org => { return org.orgId });
+    }
+    return [];
+  },
   orgsUserBelongs() {
     if (this.orgs) {
       return this.orgs.map(function(org){return org.orgDisplayName}).sort().join(',');
@@ -544,32 +556,16 @@ Users.helpers({
     return '';
   },
   boards() {
-    return Boards.find(
-      {
-        'members.userId': this._id,
-      },
-      {
-        sort: {
-          sort: 1 /* boards default sorting */,
-        },
-      },
-    );
+    return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } })
   },
 
   starredBoards() {
     const { starredBoards = [] } = this.profile || {};
-    return Boards.find(
-      {
-        archived: false,
-        _id: {
-          $in: starredBoards,
-        },
-      },
-      {
-        sort: {
-          sort: 1 /* boards default sorting */,
-        },
-      },
+    return Boards.userBoards(
+      this._id,
+      false,
+      { _id: { $in: starredBoards } },
+      { sort: { sort: 1 } }
     );
   },
 
@@ -580,18 +576,11 @@ Users.helpers({
 
   invitedBoards() {
     const { invitedBoards = [] } = this.profile || {};
-    return Boards.find(
-      {
-        archived: false,
-        _id: {
-          $in: invitedBoards,
-        },
-      },
-      {
-        sort: {
-          sort: 1 /* boards default sorting */,
-        },
-      },
+    return Boards.userBoards(
+      this._id,
+      false,
+      { _id: { $in: invitedBoards } },
+      { sort: { sort: 1 } }
     );
   },
 

+ 2 - 2
releases/release-bundle.sh

@@ -6,11 +6,11 @@ cd ~/repos/wekan/.build
 zip -r wekan-$1.zip bundle
 
 {
-  scp ~/repos/wekan/releases/maintainer-make-bundle-a.sh a:/home/wekan/repos/maintainer-make-bundle.sh
+  scp ~/repos/wekan/releases/maintainer-make-bundle-a.sh a:/home/wekan/maintainer-make-bundle.sh
   scp ~/repos/wekan/releases/maintainer-make-bundle-s.sh s:/home/linux1/maintainer-make-bundle.sh
   scp ~/repos/wekan/releases/maintainer-make-bundle-o.sh o:/home/ubuntu/maintainer-make-bundle.sh
   scp wekan-$1.zip x2:/var/snap/wekan/common/releases.wekan.team/
-  scp wekan-$1.zip a:/home/wekan/repos/
+  scp wekan-$1.zip a:/home/wekan/
   scp wekan-$1.zip s:/home/linux1/
   scp wekan-$1.zip o:/home/ubuntu/
 } | parallel -k

+ 1 - 1
releases/up-a.sh

@@ -11,7 +11,7 @@ if [ $# -ne 1 ]
 fi
 
 # 2) Download release from arm64 build server
-scp a:/home/wekan/repos/wekan-$1-arm64.zip .
+scp a:/home/wekan/wekan-$1-arm64.zip .
 
 # 3) Upload arm64 release to download server
 scp wekan-$1-arm64.zip x2:/var/snap/wekan/common/releases.wekan.team/raspi3/

+ 97 - 30
server/publications/boards.js

@@ -2,40 +2,47 @@
 // non-archived boards:
 // 1. that the user is a member of
 // 2. the user has starred
+import Users from "../../models/users";
+import Org from "../../models/org";
+import Team from "../../models/team";
+
 Meteor.publish('boards', 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 [];
+  if (!Match.test(userId, String) || !userId) {
+    return [];
+  }
 
   // Defensive programming to verify that starredBoards has the expected
   // format -- since the field is in the `profile` a user can modify it.
-  const { starredBoards = [] } = (Users.findOne(userId) || {}).profile || {};
-  check(starredBoards, [String]);
+  // const { starredBoards = [] } = (Users.findOne(userId) || {}).profile || {};
+  // check(starredBoards, [String]);
 
-  let currUser = Users.findOne(userId);
-  let orgIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
-  let teamIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
-  let orgsIds = [];
-  let teamsIds = [];
-  if(orgIdsUserBelongs && orgIdsUserBelongs != ''){
-    orgsIds = orgIdsUserBelongs.split(',');
-  }
-  if(teamIdsUserBelongs && teamIdsUserBelongs != ''){
-    teamsIds = teamIdsUserBelongs.split(',');
-  }
+  // let currUser = Users.findOne(userId);
+  // let orgIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : '';
+  // let teamIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : '';
+  // let orgsIds = [];
+  // let teamsIds = [];
+  // if(orgIdsUserBelongs && orgIdsUserBelongs != ''){
+  //   orgsIds = orgIdsUserBelongs.split(',');
+  // }
+  // if(teamIdsUserBelongs && teamIdsUserBelongs != ''){
+  //   teamsIds = teamIdsUserBelongs.split(',');
+  // }
   return 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 } } },
-        {'orgs.orgId': {$in : orgsIds}},
-        {'teams.teamId': {$in : teamsIds}},
-      ],
+      _id: { $in: Boards.userBoardIds(userId, false) },
+      // $or: [
+      //   {
+      //     // _id: { $in: starredBoards },  // Commented out, to get a list of all public boards
+      //     permission: 'public',
+      //   },
+      //   { members: { $elemMatch: { userId, isActive: true } } },
+      //   {'orgs.orgId': {$in : orgsIds}},
+      //   {'teams.teamId': {$in : teamsIds}},
+      // ],
     },
     {
       fields: {
@@ -58,19 +65,79 @@ 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 [];
+
+  const boards = Boards.find(
+    {
+      _id: { $in: Boards.userBoardIds(userId, null) },
+    },
+    {
+      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 userIds = [];
+  const orgIds = [];
+  const teamIds = [];
+  boards.forEach(board => {
+    if (board.members) {
+      board.members.forEach(member => {
+        userIds.push(member.userId);
+      });
+    }
+    if (board.orgs) {
+      board.orgs.forEach(org => {
+        orgIds.push(org.orgId);
+      });
+    }
+    if (board.teams) {
+      board.teams.forEach(team => {
+        teamIds.push(team.teamId);
+      });
+    }
+  })
+
+  return [
+    boards,
+    Users.find({ _id: { $in: userIds } }, { fields: Users.safeFields }),
+    Team.find({ _id: { $in: teamIds } }),
+    Org.find({ _id: { $in: orgIds } }),
+  ]
+});
+
 Meteor.publish('archivedBoards', function() {
   const userId = this.userId;
   if (!Match.test(userId, String)) return [];
 
   return Boards.find(
     {
-      archived: true,
-      members: {
-        $elemMatch: {
-          userId,
-          isAdmin: true,
-        },
-      },
+      _id: { $in: Boards.userBoardIds(userId, true)},
+      // archived: true,
+      // members: {
+      //   $elemMatch: {
+      //     userId,
+      //     isAdmin: true,
+      //   },
+      // },
     },
     {
       fields: {

+ 2 - 0
server/publications/cards.js

@@ -48,6 +48,7 @@ import {
   PREDICATE_SYSTEM,
 } from '/config/search-const';
 import { QueryErrors, QueryParams, Query } from '/config/query-classes';
+import { CARD_TYPES } from '../../config/const';
 
 const escapeForRegex = require('escape-string-regexp');
 
@@ -587,6 +588,7 @@ Meteor.publish('brokenCards', function(sessionId) {
     { boardId: { $in: [null, ''] } },
     { swimlaneId: { $in: [null, ''] } },
     { listId: { $in: [null, ''] } },
+    { type: { $nin: CARD_TYPES } },
   ];
   // console.log('brokenCards selector:', query.selector);