Skip to content

Commit

Permalink
Merge pull request #15 from Ctri-The-Third/trello-insert-to-index
Browse files Browse the repository at this point in the history
trello caching and sorting
  • Loading branch information
Ctri-The-Third authored Oct 12, 2022
2 parents be44dfc + bab7d06 commit d942943
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 17 deletions.
126 changes: 110 additions & 16 deletions serviceHelpers/trello.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,23 @@ def __init__(self, board_id, key, token) -> None:
self.board_id = board_id
self.key = key
self.token = token
self._cached_cards = {} #listID into list of cards, sorted by their IDs
self.dirty_cache = True
pass

def find_trello_card(self, regex):
cards = self.fetch_trello_cards()
"uses regexes to search name and description of cached / fetched cards. Returns the first it finds."
cards = self.fetch_trello_cards() if self.dirty_cache else self._cached_cards

for card in cards:
if re.search(regex,card["name"]) or re.search(regex,card["desc"]):
return card
return

def find_trello_cards(self,regex):
cards = self.fetch_trello_cards()
"uses regexes to search name and description of cached / fetched cards. Returns all cards found."

cards = self.fetch_trello_cards() if self.dirty_cache else self._cached_cards
foundCards = []
for card in cards:
if re.search(regex,card["name"]) or re.search(regex,card["desc"]):
Expand Down Expand Up @@ -55,8 +61,8 @@ def purge_trello_cards(self,titlePattern = "", descPattern = "", targetLists = [
* customFieldIDs and targetLists are an array of IDs. Cards without those fields populated, or outside of those fields will be deleted.
* targetLists can have the value `[*]` which will result in all lists being considered valid
"""
cards = self.fetch_trello_cards()

cards = self.fetch_trello_cards() if self.dirty_cache else self._cached_cards
for card in cards:
#search through all cards, attempt to DQ from list. If all checks pass and not DQd, delete
continueSearch = True
Expand All @@ -79,32 +85,84 @@ def purge_trello_cards(self,titlePattern = "", descPattern = "", targetLists = [
if continueSearch == True:
#we found no disqualifiers.
self.deleteTrelloCard(card["id"])
self.dirty_cache = True
return
def get_all_cards_on_list(self, list_id):
"""Returns all the visible cards on a given list"""
if self.dirty_cache:
self.fetch_trello_cards()
cards = self._cached_cards
filtered_cards = {k:v for k,v in cards.items() if v["idList"] == list_id}
return filtered_cards
#creates Key:value FOR - (for each Key, value in cards) BUT ONLY IF the list_id matches

def fetch_trello_cards(self):
"""returns all visible cards from the board"""
url = "https://api.trello.com/1/boards/%s/cards" % (self.board_id)
params = self._get_trello_params()
params["filter"] = "visible"
params["customFieldItems"] = "true"

#params["customFieldItems"] = "true"

r = requests.get(url,params=params)
if r.status_code != 200:
print("Unexpected response code [%s], whilst getting ticket list" % r.status_code)
print(r.content)
return
cards = json.loads(r.content)

cards = json.loads(r.content)
self._populate_cache(cards)
self.dirty_cache = False
return cards
def get_trello_card(self,id):

def fetch_trello_card(self,id):
"returns a single trello card"
url = "https://api.trello.com/1/cards/%s" % (id)
params = self._get_trello_params()
r = requests.get(url, params = params)
card = json.loads(r.content)

self._populate_cache([card])
return card

def create_card(self,title, list_id, description = None, labelID = None, dueTimestamp = None) :


def get_trello_card(self,id):
"gets a card from the cache, or fetches it"
if id in self._cached_cards:
return self._cached_cards[id]
else:
return self.fetch_trello_card(id)

def convert_index_to_pos(self, list_id,position) -> float:
"takes the index of a card, and finds a suitable position float value for it"
cards = self.get_all_cards_on_list(list_id)
cards = self._sort_trello_cards_dict(cards)


cards = list(cards.items())
if len(cards) == 1:
return cards[0][1].get("pos",0)+1
if len(cards) == 0:
return 0
if position == 0:
return (cards[0][1].get("pos",0))/2
if position == -1:
return (cards[len(cards)-1][1].get("pos",0)+0.0000001)

try:
card = cards[position]
prev_card = cards[position-1]
pos = (prev_card[1]["pos"] + card[1]["pos"])/2
return pos
except IndexError:
lo.error("got a horrible value when trying to convert index to pos value - index=[%s], len=[%s]")
return 0



def create_card(self,title, list_id, description = None, labelID = None, dueTimestamp = None, position:int = None):
"`position` is the numerical index of the card on the list it's appearing on. 0 = top, 1 = second, -1 = last"

params = self._get_trello_params()
params["name"] =title

Expand All @@ -114,6 +172,9 @@ def create_card(self,title, list_id, description = None, labelID = None, dueTim
params["idLabels"] = (labelID)
if dueTimestamp is not None:
params["due"] = dueTimestamp
if position is not None and position >= 0:
#if position is -1 (bottom) then use default behaviour
params["pos"] = self.convert_index_to_pos(list_id,position)
params["idList"] = list_id

url = "https://api.trello.com/1/cards/"
Expand All @@ -123,6 +184,7 @@ def create_card(self,title, list_id, description = None, labelID = None, dueTim
print(r.content)
return
card = json.loads(r.content)
self.dirty_cache = True
return card


Expand All @@ -139,6 +201,7 @@ def update_card( self, card_id, title, description = None, pos = None):
r = requests.put(url,params=params)
if r.status_code != 200:
print("ERROR: %s couldn't update the Gmail Trello card's name" % (r.status_code))
self.dirty_cache = True
return


Expand All @@ -155,6 +218,7 @@ def create_checklist_on_card(self, cardID, checklistName = "Todo items") -> str:
print("ERROR: %s when attempting create a trello checklist (HabiticaMapper) \n%s" % (r.status_code,r.content))
return
checklist = json.loads(r.content)
self.dirty_cache = True
return checklist["id"]


Expand All @@ -166,7 +230,8 @@ def add_item_to_checklist(self, checklistID, text):
r = requests.post(url,params=params)
if r.status_code != 200:
print("ERROR: %s when attempting add to a trello checklist (HabiticaMapper) \n%s" % (r.status_code,r.content))
return
return
self.dirty_cache = True
return

def delete_checklist_item(self,checklist_id, checklist_item_id):
Expand All @@ -175,7 +240,7 @@ def delete_checklist_item(self,checklist_id, checklist_item_id):
r = requests.delete(url,params=params)
if r.status_code != 200:
lo.error("ERROR: %s, Couldn't delete trello checklist item %s " % (r.status_code,checklist_item_id))

self.dirty_cache = True

def _setCustomFieldValue(self, cardID,value, fieldID):
url = "https://api.trello.com/1/card/%s/customField/%s/item" % (cardID,fieldID)
Expand All @@ -196,7 +261,8 @@ def archiveTrelloCard(self,trelloCardID):
if r.status_code != 200:
print(r)
print(r.reason)

self.dirty_cache = True


def create_custom_field(self,field_name) -> str:
board_id = self.board_id
Expand All @@ -216,10 +282,11 @@ def create_custom_field(self,field_name) -> str:
try:
new_field = json.loads(result.content)["id"]
return new_field

except Exception as e:
logger.warn("Couldn't parse JSON of new field? this shouldn't happen")
return ""

return ""

def _get_trello_params(self):
params = {
Expand All @@ -236,6 +303,8 @@ def deleteTrelloCard(self,trelloCardID):
if r.status_code != 200:
print(r)
print(r.reason)
return
self.dirty_cache = True



Expand All @@ -244,9 +313,34 @@ def fetch_checklist_content(self, checklist_id) ->list:
params = self._get_trello_params()
r = requests.get(url=url,params=params)
if r.status_code != 200:
lo.error("Couldn't fetch checklist content %s" % (r.status_code, r.content))
lo.error("Couldn't fetch checklist content %s - %s", r.status_code, r.content)
try:
content = json.loads(r.content)["checkItems"]
except Exception as e:
lo.error(f"Couldn't parse the json from the fetch_checklist_content method for {checklist_id}")
return content
return content


def _sort_trello_cards_list(self, array_of_cards:list) -> list:
"Takes an arbitrary list of cards and sorts them by their position value if present"
array_of_cards = sorted(array_of_cards,key=lambda card:card.get("pos",0))

return array_of_cards

def _sort_trello_cards_dict(self,dict_of_cards:dict) -> dict:
sorted_dict = {k:v for k,v in sorted(dict_of_cards.items(), key=lambda x:x[1].get("pos"))}
#note for future C'tri reading this - the part that does the sorting is this:
#key=lambda x:x[1].get("pos")
#in this case, x is a tuple comprised of K and V
#x[1] is the card - a dict, and x[0] is the id, a string.
return sorted_dict

def _populate_cache(self,array_of_cards:list) -> None:
"takes a list of cards and puts them into the _cached_cards dict"
for card in array_of_cards:
card:dict
if "id" not in card:
lo.warn("skipped adding a card to the cache, no id present")
continue

self._cached_cards[card.get("id")] = card
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

setup(
name="hex-helpers",
version="2.6.0",
version="2.7.0",
description="A series of light helpers for `freshdesk`,`gmail`,`habitica`,`hue lights`,`jira`,`slack`,`trello`",
long_description=README,
long_description_content_type="text/markdown",
Expand Down
105 changes: 105 additions & 0 deletions tests/test_trello_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import sys
sys.path.append("")
import os

from serviceHelpers.trello import trello

TEST_BOARD_ID = "5f0dee6c5026590ce300472c"
TEST_LIST_ID = "5f0dee6c5026590ce3004732"

def test_trello_sort():
helper = trello("none","none","none")

list_to_sort = [
{"pos":300},
{"pos":0.304},
{"pos":20.4},
{"pos":44.4},
{"pos":209.30955}
]
list_to_sort = helper._sort_trello_cards_list(list_to_sort)
last_pos = 0
for card in list_to_sort:
assert card["pos"] > last_pos
last_pos = card["pos"]

def test_trello_sort_array():
helper = trello("none","none","none")
cards = {
"id_1":{"id":"id_1","pos":300},
"id_2":{"id":"id_2","pos":0.304},
"id_5":{"id":"id_5","pos":20.4},
"id_3":{"id":"id_3","pos":44.4},
"id_7":{"id":"id_7","pos":209.30955},
"id_12":{"id":"id_12","pos":4444},
"id_33":{"id":"id_44","pos":3.5},
"id_32":{"id":"id_32","pos":2.1},

}
sorted_cards = helper._sort_trello_cards_dict(cards)
last_pos = 0
for _,card in sorted_cards.items():
assert card["pos"] >= last_pos
last_pos = card["pos"]


def test_convert_index_to_pos():
helper = trello("none","none","none")
helper._cached_cards = {
"id_1":{"id":"id_1","idList":"main","pos":300},
"id_2":{"id":"id_2","idList":"main","pos":0.304},
"id_5":{"id":"id_5","idList":"main","pos":20.4},
"id_3":{"id":"id_3","idList":"main","pos":44.4},
"id_7":{"id":"id_7","idList":"main","pos":209.30955},
"id_12":{"id":"id_12","idList":"main","pos":4444},
"id_33":{"id":"id_44","idList":"main","pos":3.5},
"id_32":{"id":"id_32","idList":"main","pos":2.1},

}
helper.dirty_cache = False
pos = helper.convert_index_to_pos("main",5)
assert pos > 44.4
assert pos < 209.30955


def test_keys_present():
assert "TRELLO_KEY" in os.environ
assert "TRELLO_TOKEN" in os.environ

def test_trello_get_cards():
helper = trello(TEST_BOARD_ID,os.environ["TRELLO_KEY"],os.environ["TRELLO_TOKEN"])
cards = helper.fetch_trello_cards()
for card in cards:
assert "id" in card

assert len(cards) == len(helper._cached_cards)

def test_get_list():
helper = trello("none","none","none")
helper._cached_cards = {
"id_1":{"id":"id_1","idList":"not_on_thelist"},
"id_2":{"id":"id_2","idList":"yes"},
"id_5":{"id":"id_5","idList":"DEFINITELY_not"},
"id_3":{"id":"id_3","idList":"not_on_thelist"},
"id_7":{"id":"id_7","idList":"yes"},
"id_12":{"id":"id_12","idList":"yes_but_not_really"},
"id_33":{"id":"id_44","idList":"yes"},
"id_32":{"id":"id_32","idList":"not_on_thelist"},

}
helper.dirty_cache = False
cards = helper.get_all_cards_on_list("yes")
assert(len(cards) == 3 )


def test_create_card_at_correct_position():
helper = trello(TEST_BOARD_ID,os.environ["TRELLO_KEY"],os.environ["TRELLO_TOKEN"])
card = helper.create_card("TEST CARD, PLEASE IGNORE",TEST_LIST_ID,"A temporary card that should get deleted",position=0)
helper.deleteTrelloCard(card["id"])

card = helper.create_card("TEST CARD, PLEASE IGNORE",TEST_LIST_ID,"A temporary card that should get deleted",position=3)
helper.deleteTrelloCard(card["id"])


card = helper.create_card("TEST CARD, PLEASE IGNORE",TEST_LIST_ID,"A temporary card that should get deleted",position=-1)
helper.deleteTrelloCard(card["id"])

0 comments on commit d942943

Please sign in to comment.