/**
* These are the classes that form the basis
* of the Trellinator, concerning the execution
* of functions in response to Notifications
* or as part of Time Triggers or in the
* Execution Queue
* @module TrellinatorCore
*/
/**
* @class Notification
* @memberof module:TrellinatorCore
* @constructor
* @param notification {Object} an object that has been posted
* to a webhook from the Trello API
* @classdesc The Notification object takes a
* notification from the Trello API sent to a
* webhook and makes it easy to determine what
* type of event took place and to get access to
* the entities that were part of
* the notification. The notifications passed into
* this constructor are always sent to webhooks registered
* at the board level. There are 4 types of method:
*
* - methods that return an object that was the source
* or origination of this notification, throwing an
* exception if that object is not present which
* indicates in turn that type of notification did
* not occur. The first part of the function name
* is the verb, with the object coming after,
* eg. archivedCard or completedChecklistItem.
*
* - methods that return an entity that was part of
* the notification, such as board(), card() and
* member(). Given these notifications are always
* at the board level, there is always a board()
* and given every notification originates with an
* action by a member there is always a member()
* however there is not always a card(), so if
* for example this notification is a list name
* being updated the card() method will throw an
* InvalidActionException
*
* - Finally there are a couple of convenience
* functions that are just shorthand for a
* few different common scenarios such as
* actionOnDueDate and replyToMember
*
* - Static methods that are used globally and
* are simply grouped within this class for
* logical reasons
*
* There are some methods that are deprecated or
* used internally by other class methods so you can
* ignore those, they are marked as such in the
* code comments, but aren't included in the API
* documentation.
*
* Even though all methods are marked as "static"
* by jsdoc, the ones that start with "this."
* aren't static. I don't know how to make jsdoc
* display them as non-static, so if you do then
* please send a pull request :)
*
* Methods that deal with detecting whether an
* action took place will throw an InvalidActionException
* if that action did not take place.
*
* Unless you want to explicitly take action in the case
* that some action didn't occur, you don't need to
* catch these exceptions; Trellinator will catch them
* for you. An InvalidActionException is not considered
* "fatal", neither is an InvalidDataException (typically
* thrown by entity classes or the IterableCollection).
*
* If you do want to catch exceptions, you should look
* at the Exceptions.js documentation and the two static methods
* on this class Notification.logException and Notification.expectException
* to see how to do this according to Trellinator best practices
*/
var Notification = function(notification)
{
this.notification = notification;
this.board_object = null;
this.member_object = null;
this.card_object = null;
/**
* If this notification was the result of
* a member being added to a card, return
* an object of type Member, otherwise
* throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* var notif = new Notification(posted);
* var comment = "@"+notif.addedMemberToCard().name()+" welcome!";
* notif.card().postComment(comment);
*/
this.addedMemberToCard = function(name)
{
return this.memberAddedToCard(name);
}
/**
* If this notification was the result of
* a member being removed from a card, return
* an object of type Member, otherwise
* throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* var notif = new Notification(posted);
* var comment = "@"+notif.removedMemberFromCard().name()+" welcome!";
* notif.card().postComment(comment);
*/
this.removedMemberFromCard = function(name)
{
if(
(this.notification.action.display.translationKey != "action_member_left_card") &&
(this.notification.action.display.translationKey != "action_removed_member_from_card")
)
throw new InvalidActionException("No member removed from card");
var ret = new Member(this.notification.action.member);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Member was removed, but was not named: "+name);
return ret;
}
/**
* If this notification was the result of
* a member being added to a board, return
* an object of type Member, otherwise
* throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optional name (string or regex) to match against username
* @throws InvalidActionException
* @example
* var member = new Notification(posted).addedMemberToBoard();
*/
this.addedMemberToBoard = function(name)
{
if(["action_added_member_to_board","action_added_member_to_board_as_admin"].indexOf(this.notification.action.display.translationKey) == -1)
{
throw new InvalidActionException("No member added to a board");
}
var ret = new Member(this.notification.action.member);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("The added member was not named "+name);
return ret;
}
/**
* If this notification was the result of
* a member being removed from a board, return
* an object of type Member, otherwise
* throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optional name (string or regex) to match against username
* @throws InvalidActionException
* @example
* var member = new Notification(posted).removedMemberFromBoard();
*/
this.removedMemberFromBoard = function(name)
{
if(this.notification.action.display.translationKey != "action_removed_from_board")
throw new InvalidActionException("No member removed from a board");
var ret = new Member(this.notification.action.member);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("The removed member was not named "+name);
return ret;
}
/**
* A board was created, returns an object of type Board
* This is true if the board was created or copied from another board
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optional name (string or regex) to match against username
* @throws InvalidActionException
* @example
* var board = new Notification(posted).createdBoard();
*/
this.createdBoard = function()
{
if(["action_create_board","action_copy_board"].indexOf(this.notification.action.display.translationKey) == -1)
throw new InvalidActionException("No member added to a board");
return new Board(this.notification.action.data.board);
}
/**
* A board was copied, returns an object of type Board
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optional name (string or regex) to match against username
* @throws InvalidActionException
* @example
* var board = new Notification(posted).copiedBoard();
*/
this.copiedBoard = function()
{
if(["action_copy_board"].indexOf(this.notification.action.display.translationKey) == -1)
throw new InvalidActionException("No member added to a board");
return new Board(this.notification.action.data.board);
}
/**
* If a checklist item was converted to a card
* return a Card object, otherwise throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a string or RegExp object
* to match against the name of the converted checklist item
* @throws InvalidActionException
* @example
* new Notification(posted)
* .convertedChecklistItemToCard(new RegExp(".*Carmen San Diego.*"))
* .checklist().card().postComment("Great work gumshoe!");
*/
this.convertedChecklistItemToCard = function(name)
{
if(this.notification.action.display.translationKey != "action_convert_to_card_from_checkitem")
throw new InvalidActionException("No checklist item was converted to a card");
var ret = new Card(this.notification.action.data.card);
ret.source = new Checklist(this.notification.action.data.checklist)
.setContainingCard(new Card(this.notification.action.data.cardSource));
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("A checklist item was converted to a card but it was not named: "+name);
return ret;
}
/**
* If a checklist was completed as part of this notification
* return a Checklist object, otherwise throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a string or RegExp object
* to match against the name of the completed checklist
* @throws InvalidActionException
* @example
* new Notification(posted)
* .completedChecklist(new RegExp(".*Carmen San Diego.*"))
* .card().postComment("Great work gumshoe!");
*/
this.completedChecklist = function(name)
{
if(this.notification.action.display.translationKey != "action_completed_checkitem")
throw new InvalidActionException("No checklist item was completed, therefore no checklist was completed as part of this action");
var ret = this.checklist();
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("The completed checklist was not named "+name);
else
{
if(!ret.isComplete())
throw new InvalidActionException("The checklist in which the item was checked is not complete");
var completed_actions = new Array();
new IterableCollection(TrelloApi.get("cards/"+this.card().data.id+"/actions?filter=updateCheckItemStateOnCard")).each(function(elem)
{
if((elem.data.checkItem.state == "complete") && elem.data.checklist.id == ret.id())
completed_actions.push(elem);
});
if(this.notification.action.id != completed_actions[0].id)
throw new InvalidActionException("This was not the most recent completed notification for the checklist in question so couldn't be the one that caused the checklist to be completed");
}
ret.setContainingCard(this.card());
return ret;
}
/**
* Return a Custom Field object
* that was changed, optionally pass
* in a string or regex to match against
* the name of the field
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionaly pass in a string
* or RegExp to match against the name of the CARD
* on which the field was changed
* @throws InvalidActionException
* @example
* var card = new Notification(posted).changedCustomField("Priority").card();
*/
this.changedCustomField = function(name)
{
if(this.notification.action.display.translationKey != "action_update_custom_field_item")
throw new InvalidActionException("No custom field was changed");
var ret = new CustomField(this.notification.action.data.customField)
.setContainingCard(this.card())
.setItemForCurrentCard(this.notification.action.data.customFieldItem)
.setOldValue(this.notification.action.data.old);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("The updated custom field was not named "+name);
return ret;
}
/**
* Return a Card object if a card was moved
* from one list to another (either on the same
* board or to a different board) as
* part of this notification, otherwise throw
* an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionaly pass in a string
* or RegExp to match against the name of the LIST
* into which the card was moved
* @throws InvalidActionException
* @example
* var notif = new Notification(posted);
* var card = notif.movedCard(new RegExp("Done.*"));
* var from = notif.listBefore().name();
* var to = notif.listAfter().name();
* card.postComment(from+" to "+to);
*/
this.movedCard = function(name)
{
this.listCardWasMovedTo(name);
return this.card();
}
/**
* Return a Card object if a card was added
* to a list as part of this notification.
* The "added" event can be any way that a card
* can arrive in a list, either by being
* created, copied, moved (within the same board
* or from a different board) or emailed to
* the board. If none of these things happened
* throws an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionaly pass in a string
* or RegExp to match against the name of the LIST
* into which the card was added
* @throws InvalidActionException
* @example
* new Notification(posted)
* .addedCard(new RegExp("ToDo.*"))
* .postComment("Hi there!");
*/
this.addedCard = function(name)
{
this.listCardWasAddedTo(name);
return this.card();
}
/**
* Return a Card object if a card was created
* on this board as part of this notification.
* The "created" event can be any way that a card
* can be added to a board for the first time,
* ie. created by a user, copied from another card
* including from a different board or emailed to
* the board. If none of these things happened
* throws an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionaly pass in a string
* or RegExp to match against the name of the LIST
* in which the card was created
* @throws InvalidActionException
* @example
* new Notification(posted)
* .createdCard(new RegExp("Incoming.*"))
* .postComment("Hi there!");
*/
this.createdCard = function(name)
{
this.listCardWasCreatedIn(name);
return this.card();
}
/**
* Return a Checklist object if a
* checklist was added to a card as part
* of this notification or throw an
* InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param name {string|RegExp} optionally pass in a string or RegExp
* to match against the name of the checklist that was added to the
* card
* @throws InvalidActionException
* @example
* new Notification(posted)
* .addedChecklist(new RegExp(".*Ghosts.*"))
* .addItem("Who you gonna call?");
*/
this.addedChecklist = function(name)
{
if(!this.notification.action.display.translationKey == "action_add_checklist_to_card")
throw new InvalidActionException("No checklist was added to a card");
var ret = new Checklist(this.notification.action.display.entities.checklist);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidDataException("A checklist was added but it was not named: "+name+" it was named: "+ret.name());
ret.setContainingCard(this.card());
return ret;
}
/**
* Return a Board object that a card was moved
* from or throw InvalidActionException.
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .boardBefore().name();
*/
this.boardBefore = function()
{
if(this.notification.action.display.translationKey != "action_move_card_to_board")
{
throw new InvalidActionException("Card not moved from a board");
}
try
{
var ret = this.listBefore().board();
}
catch(e)
{
Notification.expectException(InvalidActionException,e);
if(!this.notification.action.data.boardSource)
throw new InvalidActionException("No boardSource, no listBefore");
var ret = new Board(this.notification.action.data.boardSource);
}
return ret;
}
/**
* Return a List object that a card
* was moved out of if a card was
* moved as part of this notification,
* or return an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .listBefore().cards()
* .first()
* .postComment("We'll miss you!");
*/
this.listBefore = function()
{
if(this.notification.action.data.listBefore)
ret = new List(this.notification.action.data.listBefore);
else
throw new InvalidActionException("No list before");
return ret;
}
/**
* Return a List object that a card
* was moved into if a card was
* moved as part of this notification,
* or return an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .listAfter().cards()
* .first()
* .postComment("Welcome friend!");
*/
this.listAfter = function()
{
if(this.notification.action.data.listAfter)
ret = new List(this.notification.action.data.listAfter);
else
throw new InvalidActionException("No list after");
return ret;
}
/**
* Return a List object that was created
* or moved from another board
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* Card.create(new Notification(posted)
* .addedList(),"Welcome to the list!");
*/
this.addedList = function()
{
if(["action_added_list_to_board","action_move_list_to_board"].indexOf(this.notification.action.display.translationKey) > -1)
ret = new List(this.notification.action.data.list);
else
throw new InvalidActionException("No list added");
return ret;
}
/**
* Return a List object that was created
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* Card.create(new Notification(posted)
* .createdList(),"Welcome to the list!");
*/
this.createdList = function()
{
if(["action_added_list_to_board"].indexOf(this.notification.action.display.translationKey) > -1)
ret = new List(this.notification.action.data.list);
else
throw new InvalidActionException("No list created");
return ret;
}
/**
* Return a List object that was archived
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .archivedList().unArchive();
*/
this.archivedList = function()
{
if(["action_archived_list"].indexOf(this.notification.action.display.translationKey) > -1)
ret = new List(this.notification.action.data.list);
else
throw new InvalidActionException("No list archived");
return ret;
}
/**
* Return the previous value after a change
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .changedCardName().setName(new Notification(posted).oldValue());
*
*/
this.oldValue = function()
{
if(this.notification.action.data.old)
{
for(var key in this.notification.action.data.old)
return this.notification.action.data.old[key];
}
else
{
throw new InvalidActionException("No old value present");
}
}
/**
* Return the name of the attribute that changed
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* var notif = new Notification(posted);
* notif
* .changedCardName().setName(notif.oldValue());
* notif.replyToMember("You changed: "+notif.whatChanged());
*/
this.whatChanged = function()
{
if(this.notification.action.data.old)
{
for(var key in this.notification.action.data.old)
return key;
}
else
{
throw new InvalidActionException("No old value present");
}
}
/**
* Return a List object that had
* it's name changed or throws
* an InvalidActionException if no
* list name was changed
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .changedListName()
* .cards().first()
* .postComment("My name changed");
*/
this.changedListName = function()
{
return this.updatedList();
}
/**
* Return a Card object if the
* name/title of the card was
* changed or throw an InvalidActionException
* if no card name was changed
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .changedCardName()
* .postComment("how dare you!");
*/
this.changedCardName = function()
{
return this.cardWithNameChanged();
}
/**
* Return a Card object if the
* description of the card was
* changed or throw an InvalidActionException
* if no card description was changed
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a name of card to match
* @throws InvalidActionException
* @example
* new Notification(posted)
* .changedCardDescription()
* .postComment("how dare you!");
*/
this.changedCardDescription = function(name)
{
if(this.notification.action.display.translationKey != "action_changed_description_of_card")
throw new InvalidActionException("Card description was not changed");
var ret = this.card();
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Card item named: "+ret.name()+" description updated which doesn't match: "+name);
return ret;
}
/**
* Return previous desc if it was changed
* as part of this notification
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a name of card to match
* @throws InvalidActionException
* @example
* new Notification(posted)
* .oldCardDescription();
*/
this.oldCardDescription = function(name)
{
var ret = null;
if(this.notification && this.notification.action && this.notification.action.data && this.notification.action.data.old && this.notification.action.data.old.desc)
ret = this.notification.action.data.old.desc;
else
throw new InvalidDataException("No old description, probably it wasn't just changed");
return ret;
}
/**
* Return a Card object if the
* due date was marked complete
* or throw an InvalidActionException
* if no due date was completed
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .completedDueDate()
* .postComment("Feels gooood :)");
*/
this.completedDueDate = function()
{
return this.cardDueDateWasCompletedOn();
}
/**
* Return a Member object if the
* member's username was mentioned
* in a comment or throw an InvalidActionException
* if no due date was completed.
* This will apply only to members who were mentioned
* who are part of this board, rather than arbitrary
* strings preceded by an @ sign
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a string
* or RegExp to match against the mentioned member's
* username
* @throws InvalidActionException
* @example
* new Notification(posted)
* .mentionedMember("johnnydepp")
* .postComment("Yarrrrr");
*/
this.mentionedMember = function(name)
{
return this.memberMentionedInComment(name);
}
/**
* Return an IterableCollection of Member objects
* that were mentioned in a comment, or throw
* an InvalidActionException if no members were
* mentioned. This applies only to members who
* are actually on the board, so won't consider
* any string preceded by an @ sign as a member
* name
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* var notif = new Notification(posted);
* notif.mentionedMembers()
* .each(function(member)
* {
* notif.card().addMember(member);
* });
*/
this.mentionedMembers = function()
{
return this.membersMentionedInComment();
}
/**
* Return a Comment object if a comment
* was added to a card as part of this
* Notification, or throw an InvalidActionException.
* BEWARE!! It's very easy to create "infinite loops"
* if you don't filter out comments added by
* Trellinator itself.
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* var notif = new Notification(posted);
* var comment = notif.addedComment();
*
* if(notif.member().notTrellinator())
* notif.card().postComment("Okay!");
*/
this.addedComment = function()
{
return this.commentAddedToCard();
}
/**
* Return a Card object if it was archived
* as part of this notification, or throw
* an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .archivedCard()
* .unArchive()
* .postComment("No you don't!");
*/
this.archivedCard = function()
{
if(this.notification.action.display.translationKey != "action_archived_card")
throw new InvalidActionException("No card was archived in this update");
return this.card();
}
/**
* Return a Card object if a due
* date was added as part of this notification,
* or throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .addedDueDate()
* .postComment("Roger that!");
*/
this.addedDueDate = function()
{
return this.cardDueDateWasAddedTo();
}
/**
* Return a Label object if a label
* was added to a card as part of this notification,
* or throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a
* string or RegExp to match against the name of
* the label that was added
* @throws InvalidActionException
* @example
* new Notification(posted)
* .addedLabel(new RegExp("Charles.*"))
* .card()
* .postComment("In Charge!");
*/
this.addedLabel = function(name)
{
return this.labelAddedToCard(name);
}
/**
* Return a Label object if a label
* was removed from a card as part of this notification,
* or throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally pass in a
* string or RegExp to match against the name of
* the label that was removed
* @throws InvalidActionException
* @example
* new Notification(posted)
* .removedLabel(new RegExp("Charles.*"))
* .card()
* .postComment("In Charge!");
*/
this.removedLabel = function(name)
{
if(this.notification.action.display.translationKey != "action_remove_label_from_card")
throw new InvalidActionException("No label was removed from a card");
var ret = new Label(this.notification.action.data.label);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Label was removed, but was not named: "+name);
return ret.setContainingCard(this.card());
}
/**
* Return a Card object if all checklists
* were completed as part of this notification,
* or throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .completedAllChecklists()
* .moveToNextList();
*/
this.completedAllChecklists = function()
{
return this.cardAllChecklistsAreCompleteOn();
}
/**
* Return a CheckItem object if it was
* marked as complete or throw an InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally include a string
* or RegExp object to match against the text of the completed item
* @throws InvalidActionException
* @example
* new Notification(posted)
* .completedChecklistItem(new RegExp("URGENT.*"))
* .card()
* .postComment("Crisis averted");
*/
this.completedChecklistItem = function(name)
{
if(this.notification.action.display.translationKey != "action_completed_checkitem")
throw new InvalidActionException("No checklist item was completed");
var ret = new CheckItem(this.notification.action.display.entities.checkitem);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("A checklist item was completed but it was not named: "+name);
ret.setContainingChecklist(this.checklist().setContainingCard(this.card()));
return ret;
}
/**
* Return a CheckItem object if it was added to a checklist
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionally only look for a checklist item matching a string or regex
* or RegExp object to match against the text of the completed item
* @throws InvalidActionException
* @example
* new Notification(posted)
* .addedChecklistItem(/.*Fluffy.*Bunny/)
* .card()
* .postComment("Cute ^_^");
*/
this.addedChecklistItem = function(name)
{
if(this.notification.action.type && (this.notification.action.type == "createCheckItem"))
{
var ret = new CheckItem(this.notification.action.data.checkItem);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Checklist item named: "+ret.name()+" added which doesn't match: "+name);
ret.setContainingChecklist(this.checklist().setContainingCard(this.card()));
}
else
{
throw new InvalidActionException("No checklist item was created");
}
return ret;
}
/**
* Return an Attachment object which will
* hold a reference to the Card object via
* the card() method, if an attachment was
* added to a card as part of this notification
* and that attachment was a Trello Board
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .attachedBoard()
* .card().postComment("I have a Trello Board");
*/
this.attachedBoard = function(name)
{
if(this.notification.action.display.translationKey != "action_add_attachment_to_card")
throw new InvalidActionException("No attachment was added to a card");
ret = new Attachment(this.notification.action.display.entities.attachment);
if(!ret.isBoard())
throw new InvalidActionException("An attachment was added but it wasn't a board");
return ret.setContainingCard(this.card());
}
/**
* Return an Attachment object which will
* hold a reference to the Card object via
* the card() method, if an attachment was
* added to a card as part of this notification
* and that attachment was a Trello card
* @memberof module:TrellinatorCore.Notification
* @throws InvalidActionException
* @example
* new Notification(posted)
* .attachedCard()
* .card().postComment("I have a Trello Card");
*/
this.attachedCard = function(name)
{
if(this.notification.action.display.translationKey != "action_add_attachment_to_card")
throw new InvalidActionException("No attachment was added to a card");
ret = new Attachment(this.notification.action.display.entities.attachment);
if(!ret.isCard())
throw new InvalidActionException("An attachment was added but it wasn't a card");
return ret.setContainingCard(this.card());
}
/**
* Return an Attachment object which will
* hold a reference to the Card object via
* the card() method, if an attachment was
* added to a card as part of this notification
* and that attachment was a file optionally with
* a file name matching a regex or string passed in
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionaly pass in a string
* or RegExp to match against the name of the file
* (not the name of the attachment)
* @throws InvalidActionException
* @example
* new Notification(posted)
* .attachedFile(new RegExp("*.pdf","i"))
* .card().postComment("I have a PDF file");
*/
this.attachedFile = function(name)
{
if(this.notification.action.display.translationKey != "action_add_attachment_to_card")
throw new InvalidActionException("No attachment was added to a card");
ret = new Attachment(this.notification.action.display.entities.attachment);
if(!ret.isFile())
throw new InvalidActionException("An attachment was added but it wasn't a file");
if(name && !TrelloApi.nameTest(name,ret.link()))
throw new InvalidActionException("Attachment with url: "+ret.link()+" did not match: "+name);
return ret.setContainingCard(this.card());
}
/**
* Return an Attachment object which will
* hold a reference to the Card object via
* the card() method, if an attachment was
* added to a card as part of this notification
* and that attachment was a link/URL, optionally
* matching a regex or string passed in
* @memberof module:TrellinatorCore.Notification
* @param {string|RegExp} optionaly pass in a string
* or RegExp to match against the URL of the attachment
* @throws InvalidActionException
* @example
* new Notification(posted)
* .attachedLink(new RegExp("*google.com*"))
* .card().postComment("I have a new Google link");
*/
this.attachedLink = function(name)
{
if(this.notification.action.display.translationKey != "action_add_attachment_to_card")
throw new InvalidActionException("No attachment was added to a card");
ret = new Attachment(this.notification.action.display.entities.attachment);
if(!ret.isLink())
throw new InvalidActionException("An attachment was added but it wasn't a link");
if(
(name && !TrelloApi.nameTest(name,ret.link())) &&
(name && !TrelloApi.nameTest(name,ret.text()))
)
throw new InvalidActionException("Attachment with url: "+ret.link()+" and name: "+ret.text()+" did not match "+name);
return ret.setContainingCard(this.card());
}
/**
* Return the Member object who initiated this
* notification. Since all notifications are at
* the board level, this will always return
* a valid Member object
* @memberof module:TrellinatorCore.Notification
* @example
* var notif = new Notification(posted);
* var comment = notif.member().name()+" dug a hole");
* notif.card()
*.postComment(comment);
*/
this.member = function()
{
if(!this.member_object)
this.member_object = new Member(this.notification.action.memberCreator);
return this.member_object;
}
/**
* Throw an invalid action exception if the
* notification came in on a member webhook
* rather than a board webhook
* @memberof module:TrellinatorCore.Notification
* @example
* new Notification(posted)
* .ignoreMemberWebhooks()
*/
this.ignoreMemberWebhooks = function()
{
if(this.notification.model.id != this.notification.action.data.board.id)
throw new InvalidActionException("We are only worried about notifications at the board level");
}
/**
* Return the Board object on which this action
* was performed. Since all notifications are at
* the board level, this will always return
* a valid Board object
* @memberof module:TrellinatorCore.Notification
* @example
* new Notification(posted)
* .board().card("Updates")
* .postComment("Something happened!");
*/
this.board = function()
{
if(!this.board_object)
{
if(this.notification.model.id == this.notification.action.data.board.id)
this.board_object = new Board(this.notification.model);
else
this.board_object = new Board(this.notification.action.data.board);
}
return this.board_object;
}
/**
* Return the Card object on which this
* action was performed. If this action
* was not performed on a card, throw an
* InvalidActionException
* @memberof module:TrellinatorCore.Notification
* @example
* new Notification(posted)
* .card().postComment("You rang?");
*/
this.card = function()
{
if(!this.notification.action.display.entities.card && !this.notification.action.data.card)
throw new InvalidActionException("No card was part of this notification");
if(!this.card_object)
{
if(this.notification.action.display.entities.card)
this.card_object = new Card(this.notification.action.display.entities.card);
else if(this.notification.action.data.card)
this.card_object = new Card(this.notification.action.data.card);
}
return this.card_object.setNotification(this);
}
/**
* Post a comment on the card associated with
* this notification mentioning the member who
* initiated the notification
* @memberof module:TrellinatorCore.Notification
* @param message {string} the message to post, without
* a username (this will be added automatically)
* @example
* new Notification(posted)
* .replyToMember("Aaaaaaas yoooooou wiiiiiiiish!");
*/
this.replyToMember = function(message)
{
this.card().postComment("@"+this.member().name()+" "+message);
return this.card();
}
/**
* Schedule a function to be executed by the
* Execution Queue at some point relative to
* the due date added to or edited on a card.
*
* By default, will execute on the due date, but
* you can optionally pass in a callback to modify
* the date relative to the due date of the card.
*
* @memberof module:TrellinatorCore.Notification
* @param function_name {string} the name of the function to execute
* @param signature {string} an arbitrary string but usually just the signature
* passed into the original function
* @param callback {Function} (optional) a function into which the due date
* as a Date object, and a second argument with the notification params that
* will be passed into the function, and the card to which the due date was added
* as a third parameter
* @example
* new Notification(posted).actionOnDueDateAdded("doSomething",signature,function(date,params,card)
* {
* if(card.currentList().name() != "Action")
* throw new InvalidActionException("Don't action if card not in Action list");
*
* date.addDays(3).at("9:00");
* params.arbitrary_string = "I ain't afraid of no ghost";
* });
*/
this.actionOnDueDateAdded = function(function_name,signature,callback)
{
var card = this.cardDueDateWasAddedTo();
this.pushDueDateActionForCard(function_name,signature,callback,card);
}
/**
* Schedule a function to be executed by the
* Execution Queue at some point relative to
* the due date on a card, even if the due date
* was not added as part of the notification.
*
* It is therefore important to use this in
* conjunction with another notification type
* because otherwise any notification for a card
* with a due date will schedule the function to
* execute.
*
* By default, will execute on the due date, but
* you can optionally pass in a callback to modify
* the date relative to the due date of the card.
*
* The original notification object is passed into
* the scheduled function, so you will need to
* call new Notification(params) in the scheduled function
*
* @memberof module:TrellinatorCore.Notification
* @param function_name {string} the name of the function to execute
* @param signature {string} an arbitrary string but usually just the signature
* passed into the original function
* @param callback {Function} (optional) a function into which the due date
* as a Date object, and a second argument with the notification params that
* will be passed into the function
* @example
* try
* {
* var notif = new Notification(posted);
* notif.createdCard();
* //Action 3 days after the due date on a card only
* //if the card was just created
* notif.actionOnDueDate("doSomething",signature,function(date,params)
* {
* date.addDays(3).at("9:00");
* params.arbitrary_string = "I ain't afraid of no ghost";
* });
* }
*/
this.actionOnDueDate = function(function_name,signature,callback)
{
try
{
var card = this.cardDueDateWasAddedTo();
}
catch(e)
{
var card = this.card();
if(!card.due())
throw new InvalidActionException("Unable to action on due date, there is no due date on the card");
}
this.pushDueDateActionForCard(function_name,signature,callback,card);
}
//This is for internal use only
this.pushDueDateActionForCard = function(function_name,signature,callback,card)
{
var params = {};
if(!callback)
callback = function(){}
var trigger_signature = signature+card.data.id;
clear(trigger_signature);
params = this.notification;
var date = new Date(card.due());
callback(date,params,card);
ExecutionQueue.push(function_name,params,trigger_signature,date);
}
//Deprecated
this.checklist = function()
{
return new Checklist(this.notification.action.data.checklist);
}
//Deprecated: use movedCard instead
this.listCardWasMovedTo = function(name)
{
if(["action_move_card_from_list_to_list"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = new List(this.notification.action.display.entities.listAfter);
else if(["action_move_card_to_board"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = this.card().currentList();
else
throw new InvalidActionException("Card was not moved to a list");
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Card was moved to : "+ret.name()+" rather than "+name);
return ret;
}
//Deprecated: use createdCard instead
this.listCardWasCreatedIn = function(name)
{
if(["action_create_card","action_copy_card","action_email_card"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = new List(this.notification.action.display.entities.list);
else if(["action_convert_to_card_from_checkitem"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = new List(this.notification.action.data.list);
else
throw new InvalidActionException("Card was not created in a list");
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Card was created in : "+ret.name()+" rather than "+name);
return ret;
}
//Deprecated: use addedCard instead
this.listCardWasAddedTo = function(name)
{
if(["action_create_card","action_copy_card","action_email_card"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = new List(this.notification.action.display.entities.list);
else if(["action_move_card_to_board","action_convert_to_card_from_checkitem"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = new List(this.notification.action.data.list);
else if(["action_move_card_from_list_to_list"].indexOf(this.notification.action.display.translationKey) > -1)
var ret = new List(this.notification.action.display.entities.listAfter);
else
throw new InvalidActionException("Card was not added to a list");
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Card was added in: \""+ret.name()+"\" rather than "+name);
return ret;
}
//Deprecated: use addedMember instead
this.memberAddedToCard = function(name)
{
if(
(this.notification.action.display.translationKey != "action_member_joined_card") &&
(this.notification.action.display.translationKey != "action_added_member_to_card")
)
throw new InvalidActionException("No member added to card");
var ret = new Member(this.notification.action.member);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Member was added, but was not named: "+name);
return ret.setContainingCard(this.card());
}
//Deprecated: use addedComment instead
this.commentAddedToCard = function()
{
if(this.notification.action.display.translationKey != "action_comment_on_card")
throw new InvalidActionException("No comment added as part of this notification");
var data = {data: {id: this.notification.action.id,
text: this.notification.action.data.text}};
return new Comment(data).setContainingCard(this.card());
}
//Deprecated: use completedDueDate instead
this.cardDueDateWasCompletedOn = function()
{
if(this.notification.action.display.translationKey != "action_marked_the_due_date_complete")
throw new InvalidActionException("Due date not marked complete");
return this.card();
}
//Deprecated: use mentionedMember instead
this.memberMentionedInComment = function(name)
{
return this.membersMentionedInComment().findByName(name).first();
}
//Deprecated: use mentionedMembers instead
this.membersMentionedInComment = function()
{
var comment = this.commentAddedToCard();
var ret = new Array();
this.board().members().each(function(member)
{
if(new RegExp("(^|\\b|\\s)@"+member.name()+"(\\b|\\s|$)","i").test(comment.text()))
ret.push(member);
});
if(!ret.length)
throw new InvalidActionException("No members were mentioned in this comment");
return new IterableCollection(ret);
}
//Deprecated: use dueDateAdded instead
this.cardDueDateWasAddedTo = function()
{
if(
(this.notification.action.display.translationKey != "action_added_a_due_date")&&
(this.notification.action.display.translationKey != "action_changed_a_due_date")
)
throw new InvalidActionException("No due date was added");
return this.card();
}
//Deprecated: use addedLabel instead
this.labelAddedToCard = function(name)
{
if(this.notification.action.display.translationKey != "action_add_label_to_card")
throw new InvalidActionException("No label as added to a card");
var ret = new Label(this.notification.action.data.label);
if(name && !TrelloApi.nameTest(name,ret.name()))
throw new InvalidActionException("Label was added, but was not named: "+name);
return ret.setContainingCard(this.card());
}
//Deprecated: use completedAllChecklists instead
this.cardAllChecklistsAreCompleteOn = function()
{
var item = this.completedChecklist();
this.card().checklists().each(function(list)
{
if(!list.isComplete())
throw new InvalidActionException("There is an incomplete checklist on "+this.card().name()+" name "+list.name());
});
return this.card();
}
//DEPRECATED: use changedCardName()
this.cardWithNameChanged = function()
{
if(this.notification.action.display.translationKey != "action_renamed_card")
throw new InvalidActionException("Card name was not changed");
return this.card();
}
//DEPRECATED: use changedListName
this.updatedList = function()
{
if(this.notification.action.data.list)
ret = new List(this.notification.action.data.list);
else
throw new InvalidActionException("List not updated");
return ret;
}
}
/**
* When you catch an exception, throw it again
* if it is not the type you expected. This is
* useful when catching exceptions thrown by
* entity classes such as Card and Board. These
* will throw InvalidDataExceptions but will
* never intentionally throw a ReferenceError
* for example.
* @memberof module:TrellinatorCore.Notification
* @param type {Function} the constructor function of an
* exception, typically one from Exceptions.js
* @param e {Object} the exception you caught
* @example
* var notif = new Notification(posted);
* //Remove any labels that were added that
* //aren't "Urgent"
* try
* {
* var label = notif.addedLabel("Urgent");
* }
*
* catch(e)
* {
* Notification.expectException(InvalidDataException,e);
* notif.card().removeLabel(notif.addedLabel());
* }
*/
Notification.expectException = function(type,e)
{
if(!Array.isArray(type))
type = [type];
if(type.indexOf(e.constructor) == -1)
throw e;
}
/**
* Log a message in the Info Log tab if this is a
* non fatal exception, or what is considered
* an "Expected Exception" from the types in
* Exceptions.js. These are exceptions thrown
* by the Notification class and entity classes
* like Card and Board. This static method will
* write any message from an "expected" exception
* to Info Log and rethrow the exception if it
* isn't one of these types. This method is used
* by Trellinator when executing functions anyway
* so you don't need to wrap all your functions
* using this try/catch, however it can be useful
* to have a shorthand way of creating a log entry
* for expected exceptions and then taking some
* other action, but halting execution if the
* exception was not expected
* @memberof module:TrellinatorCore.Notification
* @param message {string} prepend the exception message
* @param e {Object} the exception that was caught
* @example
* var notif = new Notification(posted);
*
* try
* {
* notif.card().postComment("Hi there!");
* }
*
* catch(e)
* {
* Notification.logException("We didn't see a card",e);
* }
*/
Notification.logException = function(message,e)
{
if(e.constructor == InvalidDataException)
Trellinator.log(message+": "+e);
else if(e.constructor == InvalidActionException)
Trellinator.log(message+": "+e);
else
throw e;
}
//Deprecated
Notification.fromDueDateAction = function(params)
{
var ret = null;
if(params.notification)
ret = new Notification(params.notification.notification);
else
ret = new Notification(params);
return ret;
}