浏览代码

Merge pull request #4252 from jrsupplee/search-debug

Search debug
Lauri Ojansivu 3 年之前
父节点
当前提交
8804ca84ec

+ 18 - 0
client/components/main/globalSearch.jade

@@ -42,6 +42,24 @@ template(name="globalSearch")
           autofocus dir="auto"
           autofocus dir="auto"
         )
         )
         a.js-new-search.fa.fa-eraser
         a.js-new-search.fa.fa-eraser
+      if debug.get.show
+        h1 Debug
+        if debug.get.showSelector
+          h2 Selector
+            button.js-copy-debug-selector
+              = 'Copy'
+          pre(
+            id="debug-selector"
+          )
+            = sessionData.selector
+        if debug.get.showProjection
+          h2 Projection
+            button.js-copy-debug-projection
+              = 'Copy'
+          pre(
+            id="debug-projection"
+          )
+            = sessionData.projection
       if searching.get
       if searching.get
         +spinner
         +spinner
       else if hasResults.get
       else if hasResults.get

+ 24 - 0
client/components/main/globalSearch.js

@@ -222,6 +222,30 @@ class GlobalSearchComponent extends CardSearchPagedComponent {
           );
           );
           document.getElementById('global-search-input').focus();
           document.getElementById('global-search-input').focus();
         },
         },
+        'click .js-copy-debug-selector'(evt) {
+          /* Get the text field */
+          const selector = document.getElementById("debug-selector");
+
+          try {
+            navigator.clipboard.writeText(selector.textContent);
+            alert("Selector copied to clipboard");
+          } catch(err) {
+            alert("Error copying text: " + err);
+          }
+
+        },
+        'click .js-copy-debug-projection'(evt) {
+          /* Get the text field */
+          const projection = document.getElementById("debug-projection");
+
+          try {
+            navigator.clipboard.writeText(projection.textContent);
+            alert("Projection copied to clipboard");
+          } catch(err) {
+            alert("Error copying text: " + err);
+          }
+
+        },
         'click .js-board-title'(evt) {
         'click .js-board-title'(evt) {
           evt.preventDefault();
           evt.preventDefault();
           const input = document.getElementById('global-search-input');
           const input = document.getElementById('global-search-input');

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

@@ -1,5 +1,5 @@
 template(name="adminReports")
 template(name="adminReports")
-  .setting-content
+  .setting-content.admin-reports-content
     unless currentUser.isAdmin
     unless currentUser.isAdmin
       | {{_ 'error-notAuthorized'}}
       | {{_ 'error-notAuthorized'}}
     else
     else
@@ -26,6 +26,16 @@ template(name="adminReports")
                 i.fa.fa-magic
                 i.fa.fa-magic
                 | {{_ 'rulesReportTitle'}}
                 | {{_ '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
         .main-body
           if loading.get
           if loading.get
             +spinner
             +spinner
@@ -37,6 +47,10 @@ template(name="adminReports")
             +orphanedFilesReport
             +orphanedFilesReport
           else if showRulesReport.get
           else if showRulesReport.get
             +rulesReport
             +rulesReport
+          else if showBoardsReport.get
+            +boardsReport
+          else if showCardsReport.get
+            +cardsReport
 
 
 
 
 template(name="brokenCardsReport")
 template(name="brokenCardsReport")
@@ -57,7 +71,7 @@ template(name="rulesReport")
         th actionType
         th actionType
         th activityType
         th activityType
 
 
-      each rule in rows
+      each rule in results
         tr
         tr
           td {{ rule.title }}
           td {{ rule.title }}
           td {{ rule.boardTitle }}
           td {{ rule.boardTitle }}
@@ -78,7 +92,7 @@ template(name="filesReport")
         th MD5 Sum
         th MD5 Sum
         th ID
         th ID
 
 
-      each att in attachmentFiles
+      each att in results
         tr
         tr
           td {{ att.filename }}
           td {{ att.filename }}
           td.right {{fileSize att.length }}
           td.right {{fileSize att.length }}
@@ -100,7 +114,7 @@ template(name="orphanedFilesReport")
         th MD5 Sum
         th MD5 Sum
         th ID
         th ID
 
 
-      each att in attachmentFiles
+      each att in results
         tr
         tr
           td {{ att.filename }}
           td {{ att.filename }}
           td.right {{fileSize att.length }}
           td.right {{fileSize att.length }}
@@ -109,3 +123,50 @@ template(name="orphanedFilesReport")
           td {{ att._id.toHexString }}
           td {{ att._id.toHexString }}
   else
   else
     div {{_ 'no-results' }}
     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 { AttachmentStorage } from '/models/attachments';
 import { CardSearchPagedComponent } from '/client/lib/cardSearch';
 import { CardSearchPagedComponent } from '/client/lib/cardSearch';
 import SessionData from '/models/usersessiondata';
 import SessionData from '/models/usersessiondata';
+import { QueryParams } from '/config/query-classes';
+import { OPERATOR_LIMIT } from '/config/search-const';
 
 
 BlazeComponent.extendComponent({
 BlazeComponent.extendComponent({
   subscription: null,
   subscription: null,
@@ -8,10 +10,14 @@ BlazeComponent.extendComponent({
   showBrokenCardsReport: new ReactiveVar(false),
   showBrokenCardsReport: new ReactiveVar(false),
   showOrphanedFilesReport: new ReactiveVar(false),
   showOrphanedFilesReport: new ReactiveVar(false),
   showRulesReport: new ReactiveVar(false),
   showRulesReport: new ReactiveVar(false),
+  showCardsReport: new ReactiveVar(false),
+  showBoardsReport: new ReactiveVar(false),
+  sessionId: null,
 
 
   onCreated() {
   onCreated() {
     this.error = new ReactiveVar('');
     this.error = new ReactiveVar('');
     this.loading = new ReactiveVar(false);
     this.loading = new ReactiveVar(false);
+    this.sessionId = SessionData.getSessionId();
   },
   },
 
 
   events() {
   events() {
@@ -21,6 +27,8 @@ BlazeComponent.extendComponent({
         'click a.js-report-files': this.switchMenu,
         'click a.js-report-files': this.switchMenu,
         'click a.js-report-orphaned-files': this.switchMenu,
         'click a.js-report-orphaned-files': this.switchMenu,
         'click a.js-report-rules': 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.showFilesReport.set(false);
       this.showBrokenCardsReport.set(false);
       this.showBrokenCardsReport.set(false);
       this.showOrphanedFilesReport.set(false);
       this.showOrphanedFilesReport.set(false);
+      this.showRulesReport.set(false)
+      this.showBoardsReport.set(false);
+      this.showCardsReport.set(false);
       if (this.subscription) {
       if (this.subscription) {
         this.subscription.stop();
         this.subscription.stop();
       }
       }
@@ -64,68 +75,79 @@ BlazeComponent.extendComponent({
           this.showRulesReport.set(true);
           this.showRulesReport.set(true);
           this.loading.set(false);
           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');
 }).register('adminReports');
 
 
-Template.filesReport.helpers({
-  attachmentFiles() {
+class AdminReport extends BlazeComponent {
+  collection;
+
+  results() {
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
     // console.log('attachments:', AttachmentStorage.find());
     // console.log('attachments:', AttachmentStorage.find());
     // console.log('attachments.count:', AttachmentStorage.find().count());
     // 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() {
   resultsCount() {
-    return AttachmentStorage.find().count();
-  },
+    return this.collection.find().count();
+  }
 
 
   fileSize(size) {
   fileSize(size) {
     return Math.round(size / 1024);
     return Math.round(size / 1024);
-  },
+  }
 
 
   usageCount(key) {
   usageCount(key) {
     return Attachments.find({ 'copies.attachments.key': key }).count();
     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 = [];
     const rules = [];
 
 
     Rules.find().forEach(rule => {
     Rules.find().forEach(rule => {
@@ -139,14 +161,43 @@ Template.rulesReport.helpers({
       });
       });
     });
     });
 
 
+    // eslint-disable-next-line no-console
     console.log('rows:', rules);
     console.log('rows:', rules);
     return 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 {
 class BrokenCardsComponent extends CardSearchPagedComponent {
   onCreated() {
   onCreated() {

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

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

+ 19 - 9
client/lib/cardSearch.js

@@ -1,5 +1,7 @@
 import Cards from '../../models/cards';
 import Cards from '../../models/cards';
 import SessionData from '../../models/usersessiondata';
 import SessionData from '../../models/usersessiondata';
+import {QueryDebug} from "../../config/query-classes";
+import {OPERATOR_DEBUG} from "../../config/search-const";
 
 
 export class CardSearchPagedComponent extends BlazeComponent {
 export class CardSearchPagedComponent extends BlazeComponent {
   onCreated() {
   onCreated() {
@@ -19,6 +21,8 @@ export class CardSearchPagedComponent extends BlazeComponent {
     this.sessionId = SessionData.getSessionId();
     this.sessionId = SessionData.getSessionId();
     this.subscriptionHandle = null;
     this.subscriptionHandle = null;
     this.serverError = new ReactiveVar(false);
     this.serverError = new ReactiveVar(false);
+    this.sessionData = null;
+    this.debug = new ReactiveVar(new QueryDebug());
 
 
     const that = this;
     const that = this;
     this.subscriptionCallbacks = {
     this.subscriptionCallbacks = {
@@ -52,6 +56,7 @@ export class CardSearchPagedComponent extends BlazeComponent {
     this.resultsCount = 0;
     this.resultsCount = 0;
     this.totalHits = 0;
     this.totalHits = 0;
     this.queryErrors = null;
     this.queryErrors = null;
+    this.debug.set(new QueryDebug());
   }
   }
 
 
   getSessionData(sessionId) {
   getSessionData(sessionId) {
@@ -63,30 +68,34 @@ export class CardSearchPagedComponent extends BlazeComponent {
   getResults() {
   getResults() {
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
     // console.log('getting results');
     // console.log('getting results');
-    const sessionData = this.getSessionData();
+    this.sessionData = this.getSessionData();
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
-    console.log('session data:', sessionData);
+    console.log('session data:', this.sessionData);
     const cards = [];
     const cards = [];
-    sessionData.cards.forEach(cardId => {
+    this.sessionData.cards.forEach(cardId => {
       cards.push(Cards.findOne({ _id: cardId }));
       cards.push(Cards.findOne({ _id: cardId }));
     });
     });
-    this.queryErrors = sessionData.errors;
+    this.queryErrors = this.sessionData.errors;
     if (this.queryErrors.length) {
     if (this.queryErrors.length) {
       // console.log('queryErrors:', this.queryErrorMessages());
       // console.log('queryErrors:', this.queryErrorMessages());
       this.hasQueryErrors.set(true);
       this.hasQueryErrors.set(true);
       // return null;
       // return null;
     }
     }
+    this.debug.set(new QueryDebug(this.sessionData.debug));
+    console.log('debug:', this.debug.get().get());
+    console.log('debug.show():', this.debug.get().show());
+    console.log('debug.showSelector():', this.debug.get().showSelector());
 
 
     if (cards) {
     if (cards) {
-      this.totalHits = sessionData.totalHits;
+      this.totalHits = this.sessionData.totalHits;
       this.resultsCount = cards.length;
       this.resultsCount = cards.length;
-      this.resultsStart = sessionData.lastHit - this.resultsCount + 1;
-      this.resultsEnd = sessionData.lastHit;
+      this.resultsStart = this.sessionData.lastHit - this.resultsCount + 1;
+      this.resultsEnd = this.sessionData.lastHit;
       this.resultsHeading.set(this.getResultsHeading());
       this.resultsHeading.set(this.getResultsHeading());
       this.results.set(cards);
       this.results.set(cards);
-      this.hasNextPage.set(sessionData.lastHit < sessionData.totalHits);
+      this.hasNextPage.set(this.sessionData.lastHit < this.sessionData.totalHits);
       this.hasPreviousPage.set(
       this.hasPreviousPage.set(
-        sessionData.lastHit - sessionData.resultsCount > 0,
+        this.sessionData.lastHit - this.sessionData.resultsCount > 0,
       );
       );
       return cards;
       return cards;
     }
     }
@@ -113,6 +122,7 @@ export class CardSearchPagedComponent extends BlazeComponent {
 
 
   runGlobalSearch(queryParams) {
   runGlobalSearch(queryParams) {
     this.searching.set(true);
     this.searching.set(true);
+    this.debug.set(new QueryDebug());
     this.stopSubscription();
     this.stopSubscription();
     this.subscriptionHandle = this.getSubscription(queryParams);
     this.subscriptionHandle = this.getSubscription(queryParams);
   }
   }

+ 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_LINKED_CARD = 'cardType-linkedCard';
 export const TYPE_TEMPLATE_BOARD = 'template-board';
 export const TYPE_TEMPLATE_BOARD = 'template-board';
 export const TYPE_TEMPLATE_CONTAINER = 'template-container';
 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 = [
 export const ALLOWED_WAIT_SPINNERS = [
   'Bounce',
   'Bounce',
   'Cube',
   'Cube',

+ 122 - 45
config/query-classes.js

@@ -4,6 +4,7 @@ import {
   OPERATOR_COMMENT,
   OPERATOR_COMMENT,
   OPERATOR_CREATED_AT,
   OPERATOR_CREATED_AT,
   OPERATOR_CREATOR,
   OPERATOR_CREATOR,
+  OPERATOR_DEBUG,
   OPERATOR_DUE,
   OPERATOR_DUE,
   OPERATOR_HAS,
   OPERATOR_HAS,
   OPERATOR_LABEL,
   OPERATOR_LABEL,
@@ -11,9 +12,11 @@ import {
   OPERATOR_LIST,
   OPERATOR_LIST,
   OPERATOR_MEMBER,
   OPERATOR_MEMBER,
   OPERATOR_MODIFIED_AT,
   OPERATOR_MODIFIED_AT,
+  OPERATOR_ORG,
   OPERATOR_SORT,
   OPERATOR_SORT,
   OPERATOR_STATUS,
   OPERATOR_STATUS,
   OPERATOR_SWIMLANE,
   OPERATOR_SWIMLANE,
+  OPERATOR_TEAM,
   OPERATOR_UNKNOWN,
   OPERATOR_UNKNOWN,
   OPERATOR_USER,
   OPERATOR_USER,
   ORDER_ASCENDING,
   ORDER_ASCENDING,
@@ -34,8 +37,10 @@ import {
   PREDICATE_OPEN,
   PREDICATE_OPEN,
   PREDICATE_OVERDUE,
   PREDICATE_OVERDUE,
   PREDICATE_PRIVATE,
   PREDICATE_PRIVATE,
+  PREDICATE_PROJECTION,
   PREDICATE_PUBLIC,
   PREDICATE_PUBLIC,
   PREDICATE_QUARTER,
   PREDICATE_QUARTER,
+  PREDICATE_SELECTOR,
   PREDICATE_START_AT,
   PREDICATE_START_AT,
   PREDICATE_WEEK,
   PREDICATE_WEEK,
   PREDICATE_YEAR,
   PREDICATE_YEAR,
@@ -43,6 +48,46 @@ import {
 import Boards from '../models/boards';
 import Boards from '../models/boards';
 import moment from 'moment';
 import moment from 'moment';
 
 
+export class QueryDebug {
+  predicate = null;
+
+  constructor(predicate) {
+    if (predicate) {
+      this.set(predicate)
+    }
+  }
+
+  get() {
+    return this.predicate;
+  }
+
+  set(predicate) {
+    if ([PREDICATE_ALL, PREDICATE_SELECTOR, PREDICATE_PROJECTION].includes(
+      predicate
+    )) {
+      this.predicate = predicate;
+    } else {
+      this.predicate = null;
+    }
+  }
+
+  show() {
+    return (this.predicate !== null);
+  }
+
+  showAll() {
+    return (this.predicate === PREDICATE_ALL);
+  }
+
+  showSelector() {
+    return (this.predicate === PREDICATE_ALL || this.predicate === PREDICATE_SELECTOR);
+  }
+
+  showProjection() {
+    return (this.predicate === PREDICATE_ALL || this.predicate === PREDICATE_PROJECTION);
+  }
+}
+
 export class QueryParams {
 export class QueryParams {
   text = '';
   text = '';
 
 
@@ -71,11 +116,14 @@ export class QueryParams {
   }
   }
 
 
   getPredicate(operator) {
   getPredicate(operator) {
-    if (typeof this.params[operator] === 'object') {
-      return this.params[operator][0];
-    } else {
-      return this.params[operator];
+    if (this.hasOperator(operator)){
+      if (typeof this.params[operator] === 'object') {
+        return this.params[operator][0];
+      } else {
+        return this.params[operator];
+      }
     }
     }
+    return null;
   }
   }
 
 
   getPredicates(operator) {
   getPredicates(operator) {
@@ -115,6 +163,8 @@ export class QueryErrors {
     [OPERATOR_ASSIGNEE, 'user-username-not-found'],
     [OPERATOR_ASSIGNEE, 'user-username-not-found'],
     [OPERATOR_MEMBER, 'user-username-not-found'],
     [OPERATOR_MEMBER, 'user-username-not-found'],
     [OPERATOR_CREATOR, 'user-username-not-found'],
     [OPERATOR_CREATOR, 'user-username-not-found'],
+    [OPERATOR_ORG, 'org-name-not-found'],
+    [OPERATOR_TEAM, 'team-name-not-found'],
   ];
   ];
 
 
   constructor() {
   constructor() {
@@ -196,6 +246,10 @@ export class Query {
     return this._errors.errors();
     return this._errors.errors();
   }
   }
 
 
+  addError(operator, error) {
+    this._errors.addError(operator, error)
+  }
+
   errorMessages() {
   errorMessages() {
     return this._errors.errorMessages();
     return this._errors.errorMessages();
   }
   }
@@ -213,6 +267,8 @@ export class Query {
   }
   }
 
 
   buildParams(queryText) {
   buildParams(queryText) {
+    this.queryParams = new QueryParams();
+
     queryText = queryText.trim();
     queryText = queryText.trim();
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
     //console.log('query:', query);
     //console.log('query:', query);
@@ -260,42 +316,51 @@ export class Query {
       'operator-has': OPERATOR_HAS,
       'operator-has': OPERATOR_HAS,
       'operator-sort': OPERATOR_SORT,
       'operator-sort': OPERATOR_SORT,
       'operator-limit': OPERATOR_LIMIT,
       'operator-limit': OPERATOR_LIMIT,
+      'operator-debug': OPERATOR_DEBUG,
+      'operator-org': OPERATOR_ORG,
+      'operator-team': OPERATOR_TEAM,
     };
     };
 
 
     const predicates = {
     const predicates = {
-      due: {
-        'predicate-overdue': PREDICATE_OVERDUE,
-      },
       durations: {
       durations: {
         'predicate-week': PREDICATE_WEEK,
         'predicate-week': PREDICATE_WEEK,
         'predicate-month': PREDICATE_MONTH,
         'predicate-month': PREDICATE_MONTH,
         'predicate-quarter': PREDICATE_QUARTER,
         'predicate-quarter': PREDICATE_QUARTER,
         'predicate-year': PREDICATE_YEAR,
         'predicate-year': PREDICATE_YEAR,
       },
       },
-      status: {
-        'predicate-archived': PREDICATE_ARCHIVED,
-        'predicate-all': PREDICATE_ALL,
-        'predicate-open': PREDICATE_OPEN,
-        'predicate-ended': PREDICATE_ENDED,
-        'predicate-public': PREDICATE_PUBLIC,
-        'predicate-private': PREDICATE_PRIVATE,
-      },
-      sorts: {
-        'predicate-due': PREDICATE_DUE_AT,
-        'predicate-created': PREDICATE_CREATED_AT,
-        'predicate-modified': PREDICATE_MODIFIED_AT,
-      },
-      has: {
-        'predicate-description': PREDICATE_DESCRIPTION,
-        'predicate-checklist': PREDICATE_CHECKLIST,
-        'predicate-attachment': PREDICATE_ATTACHMENT,
-        'predicate-start': PREDICATE_START_AT,
-        'predicate-end': PREDICATE_END_AT,
-        'predicate-due': PREDICATE_DUE_AT,
-        'predicate-assignee': PREDICATE_ASSIGNEES,
-        'predicate-member': PREDICATE_MEMBERS,
-      },
     };
     };
+    predicates[OPERATOR_DUE] = {
+      'predicate-overdue': PREDICATE_OVERDUE,
+    };
+    predicates[OPERATOR_STATUS] = {
+      'predicate-archived': PREDICATE_ARCHIVED,
+      'predicate-all': PREDICATE_ALL,
+      'predicate-open': PREDICATE_OPEN,
+      'predicate-ended': PREDICATE_ENDED,
+      'predicate-public': PREDICATE_PUBLIC,
+      'predicate-private': PREDICATE_PRIVATE,
+    };
+    predicates[OPERATOR_SORT] = {
+      'predicate-due': PREDICATE_DUE_AT,
+      'predicate-created': PREDICATE_CREATED_AT,
+      'predicate-modified': PREDICATE_MODIFIED_AT,
+    };
+    predicates[OPERATOR_HAS] = {
+      'predicate-description': PREDICATE_DESCRIPTION,
+      'predicate-checklist': PREDICATE_CHECKLIST,
+      'predicate-attachment': PREDICATE_ATTACHMENT,
+      'predicate-start': PREDICATE_START_AT,
+      'predicate-end': PREDICATE_END_AT,
+      'predicate-due': PREDICATE_DUE_AT,
+      'predicate-assignee': PREDICATE_ASSIGNEES,
+      'predicate-member': PREDICATE_MEMBERS,
+    };
+    predicates[OPERATOR_DEBUG] = {
+      'predicate-all': PREDICATE_ALL,
+      'predicate-selector': PREDICATE_SELECTOR,
+      'predicate-projection': PREDICATE_PROJECTION,
+    };
+
     const predicateTranslations = {};
     const predicateTranslations = {};
     Object.entries(predicates).forEach(([category, catPreds]) => {
     Object.entries(predicates).forEach(([category, catPreds]) => {
       predicateTranslations[category] = {};
       predicateTranslations[category] = {};
@@ -403,7 +468,7 @@ export class Query {
                   value: moment().format('YYYY-MM-DD'),
                   value: moment().format('YYYY-MM-DD'),
                 };
                 };
               } else {
               } else {
-                this.errors.addError(OPERATOR_DUE, {
+                this.addError(OPERATOR_DUE, {
                   tag: 'operator-number-expected',
                   tag: 'operator-number-expected',
                   value: { operator: op, value },
                   value: { operator: op, value },
                 });
                 });
@@ -431,27 +496,27 @@ export class Query {
               value = m.groups.operator;
               value = m.groups.operator;
               negated = true;
               negated = true;
             }
             }
-            if (!predicateTranslations.sorts[value]) {
-              this.errors.addError(OPERATOR_SORT, {
+            if (!predicateTranslations[OPERATOR_SORT][value]) {
+              this.addError(OPERATOR_SORT, {
                 tag: 'operator-sort-invalid',
                 tag: 'operator-sort-invalid',
                 value,
                 value,
               });
               });
               continue;
               continue;
             } else {
             } else {
               value = {
               value = {
-                name: predicateTranslations.sorts[value],
+                name: predicateTranslations[OPERATOR_SORT][value],
                 order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
                 order: negated ? ORDER_DESCENDING : ORDER_ASCENDING,
               };
               };
             }
             }
           } else if (operator === OPERATOR_STATUS) {
           } else if (operator === OPERATOR_STATUS) {
-            if (!predicateTranslations.status[value]) {
-              this.errors.addError(OPERATOR_STATUS, {
+            if (!predicateTranslations[OPERATOR_STATUS][value]) {
+              this.addError(OPERATOR_STATUS, {
                 tag: 'operator-status-invalid',
                 tag: 'operator-status-invalid',
                 value,
                 value,
               });
               });
               continue;
               continue;
             } else {
             } else {
-              value = predicateTranslations.status[value];
+              value = predicateTranslations[OPERATOR_STATUS][value];
             }
             }
           } else if (operator === OPERATOR_HAS) {
           } else if (operator === OPERATOR_HAS) {
             let negated = false;
             let negated = false;
@@ -460,22 +525,22 @@ export class Query {
               value = m.groups.operator;
               value = m.groups.operator;
               negated = true;
               negated = true;
             }
             }
-            if (!predicateTranslations.has[value]) {
-              this.errors.addError(OPERATOR_HAS, {
+            if (!predicateTranslations[OPERATOR_HAS][value]) {
+              this.addError(OPERATOR_HAS, {
                 tag: 'operator-has-invalid',
                 tag: 'operator-has-invalid',
                 value,
                 value,
               });
               });
               continue;
               continue;
             } else {
             } else {
               value = {
               value = {
-                field: predicateTranslations.has[value],
+                field: predicateTranslations[OPERATOR_HAS][value],
                 exists: !negated,
                 exists: !negated,
               };
               };
             }
             }
           } else if (operator === OPERATOR_LIMIT) {
           } else if (operator === OPERATOR_LIMIT) {
             const limit = parseInt(value, 10);
             const limit = parseInt(value, 10);
             if (isNaN(limit) || limit < 1) {
             if (isNaN(limit) || limit < 1) {
-              this.errors.addError(OPERATOR_LIMIT, {
+              this.addError(OPERATOR_LIMIT, {
                 tag: 'operator-limit-invalid',
                 tag: 'operator-limit-invalid',
                 value,
                 value,
               });
               });
@@ -483,11 +548,21 @@ export class Query {
             } else {
             } else {
               value = limit;
               value = limit;
             }
             }
+          } else if (operator === OPERATOR_DEBUG) {
+            if (!predicateTranslations[OPERATOR_DEBUG][value]) {
+              this.addError(OPERATOR_DEBUG, {
+                tag: 'operator-debug-invalid',
+                value,
+              });
+              continue;
+            } else {
+              value = predicateTranslations[OPERATOR_DEBUG][value];
+            }
           }
           }
 
 
           this.queryParams.addPredicate(operator, value);
           this.queryParams.addPredicate(operator, value);
         } else {
         } else {
-          this.errors.addError(OPERATOR_UNKNOWN, {
+          this.addError(OPERATOR_UNKNOWN, {
             tag: 'operator-unknown-error',
             tag: 'operator-unknown-error',
             value: op,
             value: op,
           });
           });
@@ -509,11 +584,13 @@ export class Query {
       }
       }
     }
     }
 
 
-    // eslint-disable-next-line no-console
-    // console.log('text:', text);
     this.queryParams.text = text;
     this.queryParams.text = text;
 
 
     // eslint-disable-next-line no-console
     // eslint-disable-next-line no-console
-    //console.log('queryParams:', this.queryParams);
+    if (this.queryParams.hasOperator(OPERATOR_DEBUG)) {
+      // eslint-disable-next-line no-console
+      console.log('text:', this.queryParams.text);
+      console.log('queryParams:', this.queryParams);
+    }
   }
   }
 }
 }

+ 5 - 0
config/search-const.js

@@ -3,6 +3,7 @@ export const OPERATOR_ASSIGNEE = 'assignees';
 export const OPERATOR_COMMENT = 'comment';
 export const OPERATOR_COMMENT = 'comment';
 export const OPERATOR_CREATED_AT = 'createdAt';
 export const OPERATOR_CREATED_AT = 'createdAt';
 export const OPERATOR_CREATOR = 'userId';
 export const OPERATOR_CREATOR = 'userId';
+export const OPERATOR_DEBUG = 'debug';
 export const OPERATOR_DUE = 'dueAt';
 export const OPERATOR_DUE = 'dueAt';
 export const OPERATOR_BOARD = 'board';
 export const OPERATOR_BOARD = 'board';
 export const OPERATOR_HAS = 'has';
 export const OPERATOR_HAS = 'has';
@@ -11,9 +12,11 @@ export const OPERATOR_LIMIT = 'limit';
 export const OPERATOR_LIST = 'list';
 export const OPERATOR_LIST = 'list';
 export const OPERATOR_MEMBER = 'members';
 export const OPERATOR_MEMBER = 'members';
 export const OPERATOR_MODIFIED_AT = 'modifiedAt';
 export const OPERATOR_MODIFIED_AT = 'modifiedAt';
+export const OPERATOR_ORG = 'org';
 export const OPERATOR_SORT = 'sort';
 export const OPERATOR_SORT = 'sort';
 export const OPERATOR_STATUS = 'status';
 export const OPERATOR_STATUS = 'status';
 export const OPERATOR_SWIMLANE = 'swimlane';
 export const OPERATOR_SWIMLANE = 'swimlane';
+export const OPERATOR_TEAM = 'team';
 export const OPERATOR_UNKNOWN = 'unknown';
 export const OPERATOR_UNKNOWN = 'unknown';
 export const OPERATOR_USER = 'user';
 export const OPERATOR_USER = 'user';
 export const ORDER_ASCENDING = 'asc';
 export const ORDER_ASCENDING = 'asc';
@@ -33,9 +36,11 @@ export const PREDICATE_MODIFIED_AT = 'modifiedAt';
 export const PREDICATE_MONTH = 'month';
 export const PREDICATE_MONTH = 'month';
 export const PREDICATE_OPEN = 'open';
 export const PREDICATE_OPEN = 'open';
 export const PREDICATE_OVERDUE = 'overdue';
 export const PREDICATE_OVERDUE = 'overdue';
+export const PREDICATE_PROJECTION = 'projection';
 export const PREDICATE_PRIVATE = 'private';
 export const PREDICATE_PRIVATE = 'private';
 export const PREDICATE_PUBLIC = 'public';
 export const PREDICATE_PUBLIC = 'public';
 export const PREDICATE_QUARTER = 'quarter';
 export const PREDICATE_QUARTER = 'quarter';
+export const PREDICATE_SELECTOR = 'selector';
 export const PREDICATE_START_AT = 'startAt';
 export const PREDICATE_START_AT = 'startAt';
 export const PREDICATE_SYSTEM = 'system';
 export const PREDICATE_SYSTEM = 'system';
 export const PREDICATE_WEEK = 'week';
 export const PREDICATE_WEEK = 'week';

+ 10 - 0
i18n/en.i18n.json

@@ -943,6 +943,8 @@
   "label-color-not-found": "Label color %s not found.",
   "label-color-not-found": "Label color %s not found.",
   "user-username-not-found": "Username '%s' not found.",
   "user-username-not-found": "Username '%s' not found.",
   "comment-not-found": "Card with comment containing text '%s' not found.",
   "comment-not-found": "Card with comment containing text '%s' not found.",
+  "org-name-not-found": "Organization '%s' not found.",
+  "team-name-not-found": "Team '%s' not found.",
   "globalSearch-title": "Search All Boards",
   "globalSearch-title": "Search All Boards",
   "no-cards-found": "No Cards Found",
   "no-cards-found": "No Cards Found",
   "one-card-found": "One Card Found",
   "one-card-found": "One Card Found",
@@ -971,6 +973,9 @@
   "operator-comment": "comment",
   "operator-comment": "comment",
   "operator-has": "has",
   "operator-has": "has",
   "operator-limit": "limit",
   "operator-limit": "limit",
+  "operator-debug": "debug",
+  "operator-org": "org",
+  "operator-team": "team",
   "predicate-archived": "archived",
   "predicate-archived": "archived",
   "predicate-open": "open",
   "predicate-open": "open",
   "predicate-ended": "ended",
   "predicate-ended": "ended",
@@ -992,12 +997,15 @@
   "predicate-member": "member",
   "predicate-member": "member",
   "predicate-public": "public",
   "predicate-public": "public",
   "predicate-private": "private",
   "predicate-private": "private",
+  "predicate-selector": "selector",
+  "predicate-projection": "projection",
   "operator-unknown-error": "%s is not an operator",
   "operator-unknown-error": "%s is not an operator",
   "operator-number-expected": "operator __operator__ expected a number, got '__value__'",
   "operator-number-expected": "operator __operator__ expected a number, got '__value__'",
   "operator-sort-invalid": "sort of '%s' is invalid",
   "operator-sort-invalid": "sort of '%s' is invalid",
   "operator-status-invalid": "'%s' is not a valid status",
   "operator-status-invalid": "'%s' is not a valid status",
   "operator-has-invalid": "%s is not a valid existence check",
   "operator-has-invalid": "%s is not a valid existence check",
   "operator-limit-invalid": "%s is not a valid limit.  Limit should be a positive integer.",
   "operator-limit-invalid": "%s is not a valid limit.  Limit should be a positive integer.",
+  "operator-debug-invalid": "%s is not a valid debug predicate",
   "next-page": "Next Page",
   "next-page": "Next Page",
   "previous-page": "Previous Page",
   "previous-page": "Previous Page",
   "heading-notes": "Notes",
   "heading-notes": "Notes",
@@ -1061,6 +1069,8 @@
   "orphanedFilesReportTitle": "Orphaned Files Report",
   "orphanedFilesReportTitle": "Orphaned Files Report",
   "reports": "Reports",
   "reports": "Reports",
   "rulesReportTitle": "Rules Report",
   "rulesReportTitle": "Rules Report",
+  "boardsReportTitle": "Boards Report",
+  "cardsReportTitle": "Cards Report",
   "copy-swimlane": "Copy Swimlane",
   "copy-swimlane": "Copy Swimlane",
   "copySwimlanePopup-title": "Copy Swimlane",
   "copySwimlanePopup-title": "Copy Swimlane",
   "display-card-creator": "Display Card Creator",
   "display-card-creator": "Display Card Creator",

+ 23 - 7
models/boards.js

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

+ 11 - 0
models/lists.js

@@ -345,6 +345,17 @@ Lists.mutations({
   },
   },
 });
 });
 
 
+Lists.userArchivedLists = userId => {
+  return Lists.find({
+    boardId: { $in: Boards.userBoardIds(userId, null) },
+    archived: true,
+  })
+};
+
+Lists.userArchivedListIds = () => {
+  return Lists.userArchivedLists().map(list => { return list._id; });
+};
+
 Lists.archivedLists = () => {
 Lists.archivedLists = () => {
   return Lists.find({ archived: true });
   return Lists.find({ archived: true });
 };
 };

+ 11 - 0
models/swimlanes.js

@@ -306,6 +306,17 @@ Swimlanes.mutations({
   },
   },
 });
 });
 
 
+Swimlanes.userArchivedSwimlanes = userId => {
+  return Swimlanes.find({
+    boardId: { $in: Boards.userBoardIds(userId, null) },
+    archived: true,
+  })
+};
+
+Swimlanes.userArchivedSwimlaneIds = () => {
+  return Swimlanes.userArchivedSwimlanes().map(swim => { return swim._id; });
+};
+
 Swimlanes.archivedSwimlanes = () => {
 Swimlanes.archivedSwimlanes = () => {
   return Swimlanes.find({ archived: true });
   return Swimlanes.find({ archived: true });
 };
 };

+ 25 - 34
models/users.js

@@ -519,6 +519,20 @@ Users.helpers({
     }
     }
     return '';
     return '';
   },
   },
+  teamIds() {
+    if (this.teams) {
+      // TODO: Should the Team collection be queried to determine if the team isActive?
+      return this.teams.map(team => { return team.teamId });
+    }
+    return [];
+  },
+  orgIds() {
+    if (this.orgs) {
+      // TODO: Should the Org collection be queried to determine if the organization isActive?
+      return this.orgs.map(org => { return org.orgId });
+    }
+    return [];
+  },
   orgsUserBelongs() {
   orgsUserBelongs() {
     if (this.orgs) {
     if (this.orgs) {
       return this.orgs.map(function(org){return org.orgDisplayName}).sort().join(',');
       return this.orgs.map(function(org){return org.orgDisplayName}).sort().join(',');
@@ -544,32 +558,16 @@ Users.helpers({
     return '';
     return '';
   },
   },
   boards() {
   boards() {
-    return Boards.find(
-      {
-        'members.userId': this._id,
-      },
-      {
-        sort: {
-          sort: 1 /* boards default sorting */,
-        },
-      },
-    );
+    return Boards.userBoards(this._id, null, {}, { sort: { sort: 1 } })
   },
   },
 
 
   starredBoards() {
   starredBoards() {
     const { starredBoards = [] } = this.profile || {};
     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 +578,11 @@ Users.helpers({
 
 
   invitedBoards() {
   invitedBoards() {
     const { invitedBoards = [] } = this.profile || {};
     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 } }
     );
     );
   },
   },
 
 

+ 5 - 1
models/usersessiondata.js

@@ -77,6 +77,10 @@ SessionData.attachSchema(
       optional: true,
       optional: true,
       defaultValue: [],
       defaultValue: [],
     },
     },
+    debug: {
+      type: String,
+      optional: true,
+    },
     'errors.$': {
     'errors.$': {
       type: new SimpleSchema({
       type: new SimpleSchema({
         tag: {
         tag: {
@@ -177,7 +181,7 @@ function unpickleObject(obj) {
 SessionData.pickle = value => {
 SessionData.pickle = value => {
   return JSON.stringify(value, (key, value) => {
   return JSON.stringify(value, (key, value) => {
     return pickleValue(value);
     return pickleValue(value);
-  });
+  }, 2);
 };
 };
 
 
 function pickleValue(value) {
 function pickleValue(value) {

+ 97 - 30
server/publications/boards.js

@@ -2,40 +2,47 @@
 // non-archived boards:
 // non-archived boards:
 // 1. that the user is a member of
 // 1. that the user is a member of
 // 2. the user has starred
 // 2. the user has starred
+import Users from "../../models/users";
+import Org from "../../models/org";
+import Team from "../../models/team";
+
 Meteor.publish('boards', function() {
 Meteor.publish('boards', function() {
   const userId = this.userId;
   const userId = this.userId;
   // Ensure that the user is connected. If it is not, we need to return an empty
   // 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.
   // 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
   // Defensive programming to verify that starredBoards has the expected
   // format -- since the field is in the `profile` a user can modify it.
   // 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(
   return Boards.find(
     {
     {
       archived: false,
       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: {
       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() {
 Meteor.publish('archivedBoards', function() {
   const userId = this.userId;
   const userId = this.userId;
   if (!Match.test(userId, String)) return [];
   if (!Match.test(userId, String)) return [];
 
 
   return Boards.find(
   return Boards.find(
     {
     {
-      archived: true,
-      members: {
-        $elemMatch: {
-          userId,
-          isAdmin: true,
-        },
-      },
+      _id: { $in: Boards.userBoardIds(userId, true)},
+      // archived: true,
+      // members: {
+      //   $elemMatch: {
+      //     userId,
+      //     isAdmin: true,
+      //   },
+      // },
     },
     },
     {
     {
       fields: {
       fields: {

+ 58 - 7
server/publications/cards.js

@@ -17,16 +17,17 @@ import {
   OPERATOR_COMMENT,
   OPERATOR_COMMENT,
   OPERATOR_CREATED_AT,
   OPERATOR_CREATED_AT,
   OPERATOR_CREATOR,
   OPERATOR_CREATOR,
+  OPERATOR_DEBUG,
   OPERATOR_DUE,
   OPERATOR_DUE,
   OPERATOR_HAS,
   OPERATOR_HAS,
   OPERATOR_LABEL,
   OPERATOR_LABEL,
   OPERATOR_LIMIT,
   OPERATOR_LIMIT,
   OPERATOR_LIST,
   OPERATOR_LIST,
   OPERATOR_MEMBER,
   OPERATOR_MEMBER,
-  OPERATOR_MODIFIED_AT,
+  OPERATOR_MODIFIED_AT, OPERATOR_ORG,
   OPERATOR_SORT,
   OPERATOR_SORT,
   OPERATOR_STATUS,
   OPERATOR_STATUS,
-  OPERATOR_SWIMLANE,
+  OPERATOR_SWIMLANE, OPERATOR_TEAM,
   OPERATOR_USER,
   OPERATOR_USER,
   ORDER_ASCENDING,
   ORDER_ASCENDING,
   PREDICATE_ALL,
   PREDICATE_ALL,
@@ -47,6 +48,9 @@ import {
   PREDICATE_SYSTEM,
   PREDICATE_SYSTEM,
 } from '/config/search-const';
 } from '/config/search-const';
 import { QueryErrors, QueryParams, Query } from '/config/query-classes';
 import { QueryErrors, QueryParams, Query } from '/config/query-classes';
+import { CARD_TYPES } from '../../config/const';
+import Org from "../../models/org";
+import Team from "../../models/team";
 
 
 const escapeForRegex = require('escape-string-regexp');
 const escapeForRegex = require('escape-string-regexp');
 
 
@@ -149,6 +153,51 @@ function buildSelector(queryParams) {
         }
         }
       });
       });
     }
     }
+
+    if (queryParams.hasOperator(OPERATOR_ORG)) {
+      const orgs = [];
+      queryParams.getPredicates(OPERATOR_ORG).forEach(name => {
+        const org = Org.findOne({
+          $or: [
+            { orgDisplayName: name },
+            { orgShortName: name }
+          ]
+        });
+        if (org) {
+          orgs.push(org._id);
+        } else {
+          errors.addNotFound(OPERATOR_ORG, name);
+        }
+      });
+      if (orgs.length) {
+        boardsSelector.orgs = {
+          $elemMatch: { orgId: { $in: orgs }, isActive: true }
+        };
+      }
+    }
+
+    if (queryParams.hasOperator(OPERATOR_TEAM)) {
+      const teams = [];
+      queryParams.getPredicates(OPERATOR_TEAM).forEach(name => {
+        const team = Team.findOne({
+          $or: [
+            { teamDisplayName: name },
+            { teamShortName: name }
+          ]
+        });
+        if (team) {
+          teams.push(team._id);
+        } else {
+          errors.addNotFound(OPERATOR_TEAM, name);
+        }
+      });
+      if (teams.length) {
+        boardsSelector.teams = {
+          $elemMatch: { teamId: { $in: teams }, isActive: true }
+        };
+      }
+    }
+
     selector = {
     selector = {
       type: 'cardType-card',
       type: 'cardType-card',
       // boardId: { $in: Boards.userBoardIds(userId) },
       // boardId: { $in: Boards.userBoardIds(userId) },
@@ -167,8 +216,8 @@ function buildSelector(queryParams) {
                 $in: Boards.userBoardIds(userId, archived, boardsSelector),
                 $in: Boards.userBoardIds(userId, archived, boardsSelector),
               },
               },
             },
             },
-            { swimlaneId: { $in: Swimlanes.archivedSwimlaneIds() } },
-            { listId: { $in: Lists.archivedListIds() } },
+            { swimlaneId: { $in: Swimlanes.userArchivedSwimlaneIds(userId) } },
+            { listId: { $in: Lists.userArchivedListIds(userId) } },
             { archived: true },
             { archived: true },
           ],
           ],
         });
         });
@@ -446,8 +495,8 @@ function buildSelector(queryParams) {
           { _id: { $in: attachments.map(attach => attach.cardId) } },
           { _id: { $in: attachments.map(attach => attach.cardId) } },
           { _id: { $in: comments.map(com => com.cardId) } },
           { _id: { $in: comments.map(com => com.cardId) } },
         ];
         ];
-      if (queryParams.text == "false" || queryParams.text == "true") {
-        cardsSelector.push({ customFields: { $elemMatch: { value: queryParams.text == "true" ? true : false } } } );
+      if (queryParams.text === "false" || queryParams.text === "true") {
+        cardsSelector.push({ customFields: { $elemMatch: { value: queryParams.text === "true" } } } );
       }
       }
       selector.$and.push({ $or: cardsSelector });
       selector.$and.push({ $or: cardsSelector });
     }
     }
@@ -458,7 +507,7 @@ function buildSelector(queryParams) {
   }
   }
 
 
   // eslint-disable-next-line no-console
   // eslint-disable-next-line no-console
-  //console.log('cards selector:', JSON.stringify(selector, null, 2));
+  // console.log('cards selector:', JSON.stringify(selector, null, 2));
 
 
   const query = new Query();
   const query = new Query();
   query.selector = selector;
   query.selector = selector;
@@ -586,6 +635,7 @@ Meteor.publish('brokenCards', function(sessionId) {
     { boardId: { $in: [null, ''] } },
     { boardId: { $in: [null, ''] } },
     { swimlaneId: { $in: [null, ''] } },
     { swimlaneId: { $in: [null, ''] } },
     { listId: { $in: [null, ''] } },
     { listId: { $in: [null, ''] } },
+    { type: { $nin: CARD_TYPES } },
   ];
   ];
   // console.log('brokenCards selector:', query.selector);
   // console.log('brokenCards selector:', query.selector);
 
 
@@ -634,6 +684,7 @@ function findCards(sessionId, query) {
       selector: SessionData.pickle(query.selector),
       selector: SessionData.pickle(query.selector),
       projection: SessionData.pickle(query.projection),
       projection: SessionData.pickle(query.projection),
       errors: query.errors(),
       errors: query.errors(),
+      debug: query.getQueryParams().getPredicate(OPERATOR_DEBUG)
     },
     },
   };
   };