class Command(BaseCommand):
    def __init__(self):

        super().__init__()
        self.trello_client = TrelloClient(settings.TRELLO_API_KEY,
                                          settings.TRELLO_API_TOKEN)

    def handle(self, *args, **options):
        """ Run command """

        boards = TASK_CONFIG['boards']
        cards = self.get_relevant_cards(boards)

        roles = TASK_CONFIG['roles']
        users = User.objects.filter(role__in=roles)

        # send cards to its member based on role
        for user in users:
            member_id = self.trello_client.get_member(user.email).id
            member_cards = [
                card for card in cards if member_id in card.member_ids
            ]
            if member_cards:
                self.send_data(member_cards, [user.email])

        # send cards to the additional emails
        additional_emails = TASK_CONFIG.get('additional_emails')
        if additional_emails:
            self.send_data(cards, additional_emails)

    def send_data(self, cards, recipients):
        """ send relevant cards links to recipients """

        email_template = Template(EMAIL_TEMPLATE)
        html_content = email_template.render(Context({'cards': cards}))

        send_mail(subject="Trello Manager - Feature Freeze",
                  message="Cards left before feature freeze",
                  from_email=settings.EMAIL_HOST_USER,
                  recipient_list=recipients,
                  html_message=html_content)

    def get_relevant_cards(self, boards):

        cards = []
        for board_dict in boards:
            board_obj = self.trello_client.get_board(board_dict['id'])
            for list_dict in board_dict['lists']:
                list_obj = board_obj.get_list(list_dict['id'])
                cards += list_obj.list_cards()

        return cards
예제 #2
0
print "Finding Board"
for b in client.list_boards():
  if b.name == strboardname:
    print "Found Board - " + strboardname
    for l in b.all_lists():
      if l.name == 'Information':
        continue
      f.write("<H2><U>" + l.name + "</U></H2>")
      #print l.name
      for c in l.list_cards():
        print "Fetching Card Comments -", c.name
        c.fetch()
        members = ''
        for m in c.member_id:
          member = client.get_member(m)
          if members == '':
            members += member.full_name
          else:
            members += ", "
            members += member.full_name
        #print "\t", c.name, "(", members, ")"
        f.write("<H3>" + c.name + "(" + members + ")</H3>")
        f.write("<UL>")
 
        for comment in c.comments:
           #print "\t\t", c.comments
           commentdatetime = datetime.datetime.strptime(comment['date'], '%Y-%m-%dT%H:%M:%S.%fZ')
           commentdate = commentdatetime.strftime('%m/%d/%Y %H:%M')
           if lastdate <= commentdatetime.date() <= today:
              #print commentdate, comment['data']['text']
예제 #3
0
load_dotenv(dotenv_path)

API_KEY = AP= os.environ.get("API_KEY")
API_TOKEN = AP= os.environ.get("API_TOKEN")
BOARD_ID = AP= os.environ.get("BOARD_ID")

today = datetime.now()

es = Elasticsearch("localhost:9200")

client = TrelloClient(API_KEY, token=API_TOKEN)
board = client.get_board(BOARD_ID)
members = board.all_members()

for m in members:
    member = client.get_member(m.id)
    d = datetime(2017, 4, 1, 0, 0)
    while True:
        since = d
        before = since + relativedelta(months=1)
        actions = client.fetch_json(
                    '/members/' + member.id + '/actions',
                    query_params={'limit': 1000, "since": since.strftime("%Y-%m-%d"), "before": before.strftime("%Y-%m-%d")})
        for a in actions:
            utc_date = parse(a["date"])
            jst_date = utc_date.astimezone(timezone('Asia/Tokyo'))
            a["date"] = jst_date.strftime("%Y-%m-%dT%H:%M:%S%z")
            a["hour"] = jst_date.hour
            a["weekday"] = jst_date.weekday()
            if "text" in a["data"]:
                a["text_length"] = len(a["data"]["text"]) 
예제 #4
0
csvfile = StringIO()
writer = csv.DictWriter(csvfile, fieldnames=reader.fieldnames)
writer.writeheader()

hacknight_date = last_hacknight(datetime.datetime.now(pytz.utc))

for row in reader:
    # If entries for this date already exist, ignore them and rewrite.
    if row['date'] == hacknight_date.strftime('%Y-%m-%d'):
        continue
    writer.writerow(row)


for card in cards:
    assigned_members = [client.get_member(member_id) for member_id in card.member_ids]
    data = {
            'date':    hacknight_date.strftime('%Y-%m-%d'),
            'project': card.name,
            'person':  '',
            'trello_card_id': card.id,
            }

    if assigned_members:
        data.update({'person': assigned_members[0].username})

    writer.writerow(data)

if DEBUG:
    with open('projects.csv', 'w') as f:
        f.write(csvfile.getvalue())
예제 #5
0
파일: gitr.py 프로젝트: gitplof/git-trello
def main():
    usage = '%prog (load|rebuild|commit|push|test|config|update)'\
            ' [options] args... are you ready?'
    
    parser = OptionParser(usage)

    parser.add_option('-i', '--id',
                      type='int',
                      dest='id')

    parser.add_option('-u', '--user',
                      dest='user')

    parser.add_option('-c', '--comment',
                      dest='comment')

    parser.add_option('-g', '--gitpath',
                      default=realpath(__file__), 
                      dest='gitpath')

    parser.add_option('-p', '--params',
                      dest='params')

    parser.add_option('-t', '--title',
                      dest='title')

    parser.add_option('-d', '--description',
                      dest='description',
                      default="")

    (options, args) = parser.parse_args()

    repo = Repo(options.gitpath)
    conf_path = join(expanduser("~"), '.gitr.json')
    
    if not len(args):
        handle_error(msg='Some action are required', handle=parser)

    if not isfile(conf_path):
        conf_src = join(dirname(realpath(__file__)), 'gitr.json')
        shutil.copy(conf_src, conf_path)

    with open(conf_path, 'r') as infile:
        json_data = json.load(infile)

    BOARD_ID = json_data['BOARD_ID']
    CREATE_LIST_ID = json_data['CREATE_LIST_ID']
    DONE_LIST_ID = json_data['DONE_LIST_ID']
    MEMBERS = json_data['MEMBERS']
    USER = MEMBERS[int(json_data['USER'])]

    client = TrelloClient(api_key=json_data['API_KEY'], 
                          token=json_data['TOKEN'], 
                          api_secret=json_data['API_SECRET'])
    
    if ('update' in args):
        create_list = client.get_list(CREATE_LIST_ID)
        card = get_current_card(repo, create_list)

        if options.description:
            card._set_remote_attribute('description', options.description)

        if options.title or options.params:

            def sub_match(match):
                match_args = [arg for arg in match.groups()]

                if options.title:
                    match_args[4] = options.title

                if options.params:
                    params = check_params(options.params)

                    if (len(params) > 2):
                        match_args[2] = params[2]

                    elif (len(params) == 2):
                        match_args[1] = params[1]
                    
                    match_args[3] = params[0]

                return '#%s. %sº (%s,%s) %s' % tuple(match_args)

            name = re.sub('#(\d+).(\d+)º \((\d+),(\d+)\) (.*)', sub_match, card.name)
            card._set_remote_attribute('name', name)

    elif ('members' in args):
        for idx, member_id in enumerate(MEMBERS):
            member = client.get_member(member_id)

            logging.info("*%s %d. %s (%s)" % ((USER == member_id) and '*' or '', 
                idx, member.username, member.full_name))

    elif ('config' in args):
        if options.params:
            params = options.params.split(',')

            for param in params:
                try:
                    key, value = param.split(':')
                except ValueError:
                    handle_error('(-p <key:value, key2:value2, ...>)'\
                                 ' format is wrong.')
                json_data[key] = value

        if options.user:
            json_data['USER'] = options.user

        if not (options.params and options.user):
            members = client.get_board_members(BOARD_ID)
            json_data['MEMBERS'] = [member['id'] for member in members]
            json_data['USER'] = json_data['MEMBERS'].index(client.me()['id'])

        with open(conf_path, 'w') as outfile:
          json.dump(json_data, outfile)
        
        for key, value in json_data.items():
            logging.info("* %s: %s" %(key, value))

    elif ('load' in args):
        if not options.params:
            handle_error('(-p <t_estimated>)You must include '\
                         'almost time estimated.')

        params = check_params(options.params)
        (t_estimated, priority, t_real) = (params + [1, 0])[:3]

        if options.id:
            short_id = options.id
            create_list = client.get_list(CREATE_LIST_ID)
            card = get_card_by_id(create_list, short_id)

            if not card:
                handle_error('Card not found')

            full_title = '%dº (%d,%d) %s' % (priority, t_estimated, 
                                             t_real, card.name)
            
            card._set_remote_attribute('name', "#%d.%s" % (short_id, full_title))
            checkout_by_id(repo, short_id)

        else:
            if not options.title:
                handle_error('(-t <title>) to load is required')

            full_title = '%dº (%d,%d) %s' % (priority, t_estimated, 
                                             t_real, options.title)
            check_untracked(repo)
            create_list = client.get_list(CREATE_LIST_ID)
            card = create_list.add_card(full_title, options.description)

            if options.user:
                users = check_params(options.user)

                for member_pos in users:
                    member_id = MEMBERS[member_pos]
                    if (member_id <> USER):
                        card.assign(member_id)

            card.assign(USER)
            card.fetch()
            short_id = int(card.short_id)
            card._set_remote_attribute('name', "#%d.%s" % (short_id, card.name))
            checkout_by_id(repo, short_id)

    elif ('commit' in args):
        create_list = client.get_list(CREATE_LIST_ID)
        card = get_current_card(repo, create_list)

        if not options.comment:
            handle_error('(-c <comment>) to commit is required')

        card.comment(options.comment)
        git_add_commit(repo, options.comment)

    elif ('push' in args):
        create_list = client.get_list(CREATE_LIST_ID)
        card = get_current_card(repo, create_list)
        message = "CLOSED AT %s.\n%s" % (time.strftime('%Y-%m-%d %H:%M'), 
                                         options.comment or "")

        git_add_commit(repo, message)
        card.comment(message)
        card.change_list(DONE_LIST_ID)

        try:
            repo.remotes.origin.pull()
        except CalledProcessError:
            pass
        
        active_branch = repo.active_branch.name
        
        repo.remotes.origin.push()
        repo.heads.devel.checkout()
        check_call(('git', 'merge', active_branch))

    elif ('rebuild' in args):
        done_list = client.get_list(DONE_LIST_ID)
        card = get_current_card(repo, done_list)

    else:
        create_list = client.get_list(CREATE_LIST_ID)
        card = get_current_card(repo, create_list)
        message = "TESTED AT %s.\n%s" % (time.strftime('%Y-%m-%d %H:%M'), 
                                         options.comment or "")
        git_add_commit(repo, message)

        repo.heads.devel.checkout()
        repo.remotes.origin.pull()
        checkout_by_id(repo, card.short_id, 'I')

        try:
            check_call(('git', 'merge', 'F-#%d' % card.short_id))
        except CalledProcessError as error:
            handle_error('Conflict merge.')
        else:
            try:
                git_add_commit(repo, 'merge devel - I-#%d' % card.short_id)
            except CalledProcessError as error:
                handle_error(error)
예제 #6
0
class TrelloBoardTestCase(unittest.TestCase):
    """
    Tests for TrelloClient API. Note these test are in order to
    preserve dependencies, as an API integration cannot be tested
    independently.
    """
    def setUp(self):
        self._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
                                    token=os.environ['TRELLO_TOKEN'])
        for b in self._trello.list_boards():
            if b.name == os.environ['TRELLO_TEST_BOARD_NAME']:
                self._board = b
                break
        try:
            self._list = self._board.open_lists()[0]
        except IndexError:
            self._list = self._board.add_list('List')

    def _add_card(self, name, description=None):
        try:
            card = self._list.add_card(name, description)
            self.assertIsNotNone(card, msg="card is None")
            self.assertIsNotNone(card.id, msg="id not provided")
            self.assertEquals(card.name, name)
            return card
        except Exception as e:
            print(str(e))
            self.fail("Caught Exception adding card")

    def test40_add_card(self):
        name = "Testing from Python - no desc"
        card = self._add_card(name)

        self.assertIsNotNone(card.closed, msg="closed not provided")
        self.assertIsNotNone(card.url, msg="url not provided")

        card2 = self._trello.get_card(card.id)
        self.assertEqual(card.name, card2.name)

    def test41_add_card(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._add_card(name, description)

        self.assertEquals(card.description, description)
        self.assertIsNotNone(card.closed, msg="closed not provided")
        self.assertIsNotNone(card.url, msg="url not provided")
        card.fetch()
        self.assertIsNotNone(card.member_id)
        self.assertIsNotNone(card.short_id)
        self.assertIsNotNone(card.list_id)
        self.assertIsNotNone(card.comments)
        self.assertIsNotNone(card.checklists)
        self.assertIsInstance(card.create_date, datetime)

    def test42_add_card_with_comments(self):
        name = "Card with comments"
        comment = "Hello World!"
        card = self._add_card(name)
        card.comment(comment)
        card.fetch(True)

        self.assertEquals(card.description, '')
        self.assertIsNotNone(card.closed, msg="closed not provided")
        self.assertIsNotNone(card.url, msg="url not provided")
        self.assertEquals(len(card.comments), 1)
        self.assertEquals(card.comments[0]['data']['text'], comment)

    def test43_delete_checklist(self):
        name = "Card with comments"
        card = self._list.add_card(name)
        card.fetch(True)

        name = 'Checklists'
        checklist = card.add_checklist(name, ['item1', 'item2'])
        self.assertIsNotNone(checklist, msg="checklist is None")
        self.assertIsNotNone(checklist.id, msg="id not provided")
        self.assertEquals(checklist.name, name)
        checklist.delete()
        card.delete()

    def test44_attach_url_to_card(self):
        name = "Testing from Python - url"
        card = self._add_card(name)

        card.attach(name='lwn', url='http://lwn.net/')
        card.fetch()
        self.assertEquals(card.badges['attachments'], 1)
        card.delete()

    def test52_get_cards(self):
        cards = self._board.get_cards()
        self.assertEquals(len(cards), 4)

        for card in cards:
            if card.name == 'Testing from Python':
                self.assertEqual(card.description, 'Description goes here')
            elif card.name == 'Testing from Python - no desc':
                self.assertEqual(card.description, '')
            elif card.name == 'Card with comments':
                self.assertEqual(card.description, '')
            else:
                self.fail(msg='Unexpected card found')

        self.assertIsInstance(self._board.all_cards(), list)
        self.assertIsInstance(self._board.open_cards(), list)
        self.assertIsInstance(self._board.closed_cards(), list)

    def test52_add_card_set_due(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._list.add_card(name, description)

        # Set the due date to be 3 days from now
        today = datetime.today()
        day_detla = timedelta(3)
        due_date = today + day_detla
        card.set_due(due_date)
        expected_due_date = card.due
        # Refresh the due date from cloud
        card.fetch()
        actual_due_date = card.due[:10]
        self.assertEquals(expected_due_date, actual_due_date)

    def test53_checklist(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._list.add_card(name, description)

        name = 'Checklists'
        checklist = card.add_checklist(name, ['item1', 'item2'])
        self.assertIsNotNone(checklist, msg="checklist is None")
        self.assertIsNotNone(checklist.id, msg="id not provided")
        self.assertEquals(checklist.name, name)
        checklist.rename('Renamed')
        self.assertEquals(checklist.name, 'Renamed')

    def test54_set(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._list.add_card('noname')
        card.set_name(name)
        card.set_description(description)
        self.assertEquals(card.name, name)
        self.assertEquals(card.description, description)

    def test60_delete_cards(self):
        cards = self._board.get_cards()
        for card in cards:
            card.delete()

    def test70_all_members(self):
        self.assertTrue(len(self._board.all_members()) > 0)

    def test71_normal_members(self):
        self.assertTrue(len(self._board.normal_members()) >= 0)

    def test72_admin_members(self):
        self.assertTrue(len(self._board.admin_members()) > 0)

    def test73_owner_members(self):
        members = self._board.owner_members()
        self.assertTrue(len(members) > 0)
        member = members[0].fetch()
        self.assertNotEqual(member.status, None)
        self.assertNotEqual(member.id, None)
        self.assertNotEqual(member.bio, None)
        self.assertNotEqual(member.url, None)
        self.assertNotEqual(member.username, None)
        self.assertNotEqual(member.full_name, None)
        self.assertNotEqual(member.initials, None)
        member2 = self._trello.get_member(member.id)
        self.assertEqual(member.username, member2.username)

    def test80_unauthorized(self):
        client = TrelloClient('a')
        self.assertRaises(Unauthorized, client.list_boards)

    def test81_resource_unavailable(self):
        self.assertRaises(ResourceUnavailable, self._trello.get_card, '0')

    def test90_get_board(self):
        board = self._trello.get_board(self._board.id)
        self.assertEqual(self._board.name, board.name)
class Trellist(object):
    """The main object - initialize and use run()

    :param apikey: Your api-key.
    :param apisecret: Your api secret token. Get it from a protected file that you don't commit. Keep it secret!
    :param boardname: Name of the board to work on.
    :param done_list_name: The name of the done list on your board. Only one.
    :param releases_list_name: The name of the list to put release cards on.
    :param create_comments: True by default. Create a comment on the release card for each done card
    :param create_release_if_zero_done: If nothing is done, should you make a sad empty release card?
    """
    def __init__(
        self,
        apikey,
        apisecret,
        boardname,
        done_list_name="done",
        releases_list_name="releases",
        create_release_if_zero_done=False,
        create_comments=True,
    ):
        self.client = TrelloClient(api_key=apikey, api_secret=apisecret)
        self.board = self.get_board(boardname)
        self.done = self.get_list_by_name(done_list_name)
        self.releases = self.get_list_by_name(releases_list_name)
        self.release_template = "{date} release: {count} done"
        self.card_summary_template = "- {card.name} {card.members_initials}"
        self.create_release_if_zero_done = create_release_if_zero_done
        self.create_comment_per_item = create_comments

    def run(self):
        """Runs through all the methods to perform the work"""
        logger.info(f"get all cards in the done board: {self.done.name}")
        cards = self.get_done_cards()
        logger.info(f"got {len(cards)} cards")
        if cards or self.create_release_if_zero_done:
            release_card = self.create_release_card(cards,
                                                    self.release_template,
                                                    self.card_summary_template)
            for card in cards:
                if self.create_comment_per_item:
                    self.add_comment_to_release(release_card, card)
                card.set_closed(True)
        logger.info("finished run")

    def get_board(self, board_name):
        """Gets the open board object by a name, otherwise raises an Error to
        let you know we don't have that board

        :param board_name: actual name of a board you have access to
        """
        board = self.first(self.client.list_boards(),
                           lambda b: b.name == board_name and not b.closed)
        if board:
            return board
        raise ValueError(
            ("Couldn't find an open board named '{}'. Check the name in your"
             " trello - are there extra quote marks in this message?"
             ).format(board_name))

    def get_list_by_name(self, name):
        """Iterate lists and get the first one matching the name passed in

        :param name: Name of a list on the board you've passed in
        """
        trello_list = self.first(self.board.list_lists(),
                                 lambda l: l.name == name)
        if trello_list:
            return trello_list
        raise ValueError(
            ("Couldn't find a list named '{}'. Check the name in your trello"
             " - are there extra quote marks in this message?").format(name))

    def get_card_members(self, card):
        """Return an array of Trello.Member objects of this card

        :param card:
        """
        return [self.get_member(member_id) for member_id in card.member_id]

    @lru_cache()
    def get_member(self, member_id):
        """Get a member. We only use our own function so that we can cache calls

        :param member_id:
        """
        return self.client.get_member(member_id)

    def first(self, iterable, condition):
        """Iterates an iterable and returns the first item that meets a condition or None

        :param iterable: An iterable to fetch the first itemm that meets condition
        :param condition: The condition to evaluate per item
        """
        return next((i for i in iterable if condition(i)), None)

    def get_done_cards(self):
        """Get every card from the done list"""
        return self.done.list_cards()

    def prep_card(self, card):
        """Take the card and add arrays of all member initials, full_names and usernames for use in templates

        :param card: a card to modify with extra information
        """

        card.members = self.get_card_members(card)
        card.members_initials = ""
        card.members_full_names = ""
        card.members_usernames = ""
        if len(card.members):
            card.members_initials = [
                member.initials for member in card.members
            ]
            card.members_full_names = [
                member.full_name for member in card.members
            ]
            card.members_usernames = [
                member.username for member in card.members
            ]
        return card

    def summarize_these(self, cards, template, prep_function):
        """Run a list of cards through a template and return those joined by newlines
        The template can reference any part of a Trello.card as well as 3 handy arrays
        - members_initials
        - members_full_names
        - members_usernames
        These will either have an empty string or an array of strings that show up nicely
        in string.format

        :param cards: Card objects to summarize
        :param template: A template for each card. We pass the full card to format
        :param prep_function: A function that will add make the card more friendly to format
        """
        summary = "\n".join([
            self.summarize_this(card, template, prep_function)
            for card in cards
        ])
        return summary

    def summarize_this(self, card, template, prep_function):
        """Summarize a card by passing it to a template after prepping it with values from a prep_function

        :param card: a Card to summarize
        :param template: a string template. We'll call format and pass in a prepped Card
        :param prep_function: use this to add extra information to the card 
        """
        card = prep_function(self, card)
        summary = template.format(card=card)
        return summary

    def create_release_card(self, cards, release_template,
                            card_summary_template):
        """Returns a new release card, with a title from template and description based on a summary of the cards

        :param cards: Cards in this release
        :param release_template: A format string for release card name that we pass in date and length of cards
        :param card_summary_template: A string we format with per each card
        """
        release_card_name = release_template.format(date=date.today(),
                                                    count=len(cards))
        # turn list of names of cards into a summary
        summary = self.summarize_these(cards,
                                       template=card_summary_template,
                                       prep_function=self.prep_card)
        logger.info(f"{summary}")
        release_card = self.releases.add_card(release_card_name, summary)
        return release_card

    def add_comment_to_release(
        self,
        release_card,
        card,
        comment_format="{card.name}\n{card.url}\n{card.description}",
    ):
        """add_comment_to_release

        :param release_card: A card to comment on
        :param card: A card to summarize in a comment
        :param comment_format: The template, passed in the card.
        """
        comment_text = comment_format.format(card=card)
        release_card.comment(comment_text)
예제 #8
0
class Trello:

    # cache shared across all instances of this class
    list_cache = {}

    def __init__(self, member, api_key, api_secret, token):
        self.trello = TrelloClient(api_key=api_key,
                                   api_secret=api_secret,
                                   token=token)

        self.member = member
        self.cards = []

        print(f"Fetching cards for {self.member}")
        self.get_member_cards()

    def get_member_cards(self):
        member = self.trello.get_member(self.member)
        self.cards = member.fetch_cards()
        self.preprocess_cards()

    def get_list_name(self, list_id):
        """ cache trello List objects """
        if list_id not in self.list_cache:
            list_data = self.trello.get_list(list_id)

            # ensure we stay under 100 hits per 10 sec period limit
            time.sleep(0.15)

            self.list_cache[list_id] = list_data
        return self.list_cache[list_id].name

    def date_str_to_datetime_obj(self, date_str):
        # trim off miscroseconds and Z char, and set to UTC
        trimmed = date_str[0:-5] + "+0000"
        return datetime.datetime.strptime(trimmed,
                                          "%Y-%m-%dT%H:%M:%S%z").astimezone()

    def preprocess_cards(self):
        for c in self.cards:
            # store actual list name
            c['listName'] = self.get_list_name(c['idList'])

            # localize dates
            c['dateLastActivity'] = self.date_str_to_datetime_obj(
                c['dateLastActivity'])
            if c['due']:
                c['due'] = self.date_str_to_datetime_obj(c['due'])

    def get_done(self):
        """
        return the cards that are considered 'Done'
        """
        done = [c for c in self.cards if c['dueComplete']]
        done = sorted(done, key=lambda c: c['dateLastActivity'])
        return done

    def get_to_complete(self):
        """
        return the cards that are considered 'To Be Completed'. that means if it has a due date
        and it's not marked completed yet.
        """
        to_complete = [
            c for c in self.cards
            if not c['dueComplete'] and c['due'] is not None
        ]
        to_complete = sorted(to_complete, key=lambda c: c['due'])
        return to_complete

    def get_missing_due_date(self):
        # cards should never be missing a due date
        missing_due_date = [
            c for c in self.cards if not c['dueComplete'] and c['due'] is None
        ]
        missing_due_date = sorted(missing_due_date,
                                  key=lambda c: c['dateLastActivity'])
        return missing_due_date

    def output_to_file(self):
        """ for debugging """
        output = """
        <html>
        <head>
            <style>
                h1 {
                }
                table th {
                    text-align: left;
                }
                table td {
                    text-align: left;
                }
            </style>
        </head>
        <body>
        """

        output += "<h1>Done</h1>\n"
        output += "<table><thead><th>Est. Completion Date</th><th>Name</th></thead>\n"
        for card in self.get_done():
            output += "<tr><td>%s</td><td><a href='%s'>%s</a></td></tr>\n" % (
                card['dateLastActivity'].strftime("%c"), card['shortUrl'],
                card['name'])
        output += "</table>"

        output += "<h1>To Be Completed</h1>\n"
        output += "<table><thead><th>Due Date</th><th>Name</th></thead>\n"
        for card in self.get_to_complete():
            output += "<tr><td>%s</td><td><a href='%s'>%s</a></td></tr>\n" % (
                card['due'].strftime("%c"), card['shortUrl'], card['name'])
        output += "</table>"

        output += "<h1>Missing Due Date</h1>"
        output += "<table><thead><th>Last Updated</th><th>Name</th></thead>\n"
        for card in self.get_missing_due_date():
            output += "<tr><td>%s</td><td><a href='%s'>%s</a></td></tr>\n" % (
                card['dateLastActivity'].strftime("%c"), card['shortUrl'],
                card['name'])

        output += """
        </body>
        </html>
        """

        f = open("allcards.html", "w")
        f.write(output)
        f.close()
예제 #9
0
class Command(BaseCommand):
    def __init__(self):

        super().__init__()
        self.trello_client = TrelloClient(settings.TRELLO_API_KEY,
                                          settings.TRELLO_API_TOKEN)
        self.github_client = Github(
            settings.GITHUB_TOKEN).get_organization('bluevine-dev')

        repos = self.github_client.get_repos()
        unmerged_pull_requests = []
        for repo in repos:
            unmerged_pull_requests += [
                pr for pr in repo.get_pulls() if not pr.merged_at
            ]

        self.unmerged_pull_requests = {
            pr.html_url: pr
            for pr in unmerged_pull_requests
        }

    def add_arguments(self, parser):  # pylint: disable=no-self-use
        """ Add extra arguments """

        super().add_arguments(parser)
        parser.add_argument('--csv',
                            action='store_true',
                            default=False,
                            help='attach a csv to the generated email')
        parser.add_argument('--user',
                            action='store',
                            help='run command for specified user')

    def handle(self, *args, **options):
        """ Run command """

        username = options.get('user')
        if username:
            users = User.objects.filter(username=username)
        else:
            users = User.objects.filter(role='TL')

        # generate unmerged pull request email to all desired users
        for user in users:
            member_id = self.trello_client.get_member(user.email).id
            data = self._get_unmerged_pull_requests(member_id)
            attachment = None
            if data and options['csv']:
                attachment = self.create_pull_requests_csv(data, member_id)
                logger.info(f'attachment {attachment} was created')

            self.send_unmerged_pull_requests_data(
                data=data,
                recipients=['*****@*****.**'],
                attachment=attachment)

    def _get_unmerged_pull_requests(self, member_id):
        """ Returns all unmerged pull requests related to cards """

        boards = self.trello_client.list_boards(board_filter='open')
        pull_requests_per_board = []
        for board in boards:
            cards = board.get_cards(card_filter='open')

            # get only member cards
            cards = [card for card in cards if member_id in card.member_id]
            pull_requests_per_card = []
            for card in cards:
                attachments = card.get_attachments()
                pr_attachments = [
                    attachment for attachment in attachments
                    if all(s in attachment.url for s in ['github.com', 'pull'])
                ]
                # check for unmerged pull request in card
                unmerged_pull_requests = []
                for pr_attachment in pr_attachments:
                    if pr_attachment.url in self.unmerged_pull_requests:
                        pull_request_data = {
                            'name': pr_attachment.name,
                            "url": pr_attachment.url,
                        }

                        unmerged_pull_requests.append(pull_request_data)
                if unmerged_pull_requests:
                    card_data = {
                        'name': card.name,
                        'url': card.url,
                        'pull_requests': unmerged_pull_requests
                    }
                    pull_requests_per_card.append(card_data)

            if pull_requests_per_card:
                board_data = {
                    'name': board.name,
                    'cards': pull_requests_per_card
                }
                pull_requests_per_board.append(board_data)

        return pull_requests_per_board

    @staticmethod
    def send_unmerged_pull_requests_data(data, recipients, attachment=None):
        """ Sends an email according to given data """

        html_content = Command.create_email_template(data)
        email_message = EmailMessage(
            subject='Trello Manager - Unmerged Pull Requests',
            body=html_content,
            from_email=settings.EMAIL_HOST_USER,
            to=recipients)
        email_message.content_subtype = 'html'
        if attachment:
            email_message.attach_file(attachment)
        email_message.send()

        logger.info(f'Email was sent to {recipients}')

    @staticmethod
    def create_pull_requests_csv(data, member_id):
        """ create a temporary csv file """

        import os, tempfile, csv
        from datetime import datetime

        now = datetime.now().strftime("%m_%d_%Y__%H%M%S")
        file_path = os.path.join(
            tempfile.gettempdir(),
            f'unmerged_pull_request_{member_id}_{now}.csv')
        with open(file_path, 'w') as csv_file:
            headers = [
                'board name', 'card name', 'card url', 'pull request name',
                'pull request url'
            ]
            writer = csv.writer(csv_file)
            writer.writerow(headers)
            for board in data:
                cards = board['cards']
                for card in cards:
                    pull_requests = card['pull_requests']
                    for pull_request in pull_requests:
                        writer.writerow([
                            board['name'], card['name'], card['url'],
                            pull_request['name'], pull_request['url']
                        ])

        return file_path

    @staticmethod
    def fetch_data_from_pull_request_url(pull_request_url):
        """ Parses and returns pull request data from a pull request url """

        split_url = pull_request_url.split("/")
        owner = split_url[3]
        repo = split_url[4]
        number = int(split_url[-1])

        return owner, repo, number

    def _fetch_cards_by_member(self, member_id):
        """ Fetches all the cards for this member """

        cards = self.trello_client.fetch_json('/members/' + member_id +
                                              '/cards',
                                              query_params={
                                                  'filter': 'visible',
                                                  'fields': 'name,idBoard,url',
                                                  'attachments': 'true'
                                              })
        return sorted(cards, key=lambda card: card['idBoard'])

    def _get_board_name_by_id(self, board_id):
        """ Returns the name of the board """

        if board_id not in self.boards_names:
            self.boards_names[board_id] = self.trello_client.get_board(
                board_id).name

        return self.boards_names[board_id]

    def _get_pull_request_by_url(self, pull_request_url):

        owner, repo, number = self.fetch_data_from_pull_request_url(
            pull_request_url)
        return self.github_client.get_repo(repo).get_pull(int(number))

    @staticmethod
    def create_email_template(data):

        email_template = Template(EMAIL_TEMPLATE)
        return email_template.render(Context({'boards': data}))
예제 #10
0
class TrelloClient:
    def __init__(self, api_key, api_secret, token, token_secret):
        self.trello_client = Client(api_key=api_key,
                                    api_secret=api_secret,
                                    token=token,
                                    token_secret=token_secret)
        self._uid = None
        self._project = None
        self._board = None
        self._lists = None
        self._board_labels = None
        self._lists_filter = None
        self._only_my_cards = False

    @property
    def whoami(self):
        """
        Get my Trello UID

        :return: my Trello UID
        :rtype: string
        """
        if self._uid is None:
            self._uid = self.trello_client.get_member('me').id
        return self._uid

    def project(self, project):
        """
        Set the class working project

        :param project: TelloWarrior project object
        """
        if self._project == None or self._project.name != project.name:
            self._board = self.get_board(project.trello_board_name)
            self._lists = self.get_lists()
            self._board_labels = self.get_board_labels()
            self._lists_filter = project.trello_lists_filter
            self._only_my_cards = project.only_my_cards

    def get_board(self, board_name):
        """
        Get a open Trello board from name, if it does not exist create it

        :param board_name: the board name
        :return: a Tello board
        :rtype: Trello board object
        """
        for trello_board in self.trello_client.list_boards(
                board_filter='open'):
            if trello_board.name == board_name and not trello_board.closed:
                logger.debug('Trello board {} found'.format(board_name))
                return trello_board
        logger.debug('Creating Trello board {}'.format(board_name))
        return self.trello_client.add_board(board_name)

    def get_lists(self):
        """
        Get the open lists of a Trello board

        :return: a list of Trello list objects
        :rtype: list
        """
        return self._board.open_lists()

    def get_list(self, list_name):
        """
        Get a Trello list from list name, if it does not exist create it

        :param list_name: the list name
        :return: a Tello list
        :rtype: Trello list object
        """
        if self._lists == None:
            raise ClientError('get_list')
        for trello_list in self._lists:
            if trello_list.name == list_name:
                logger.debug('Trello list {} found'.format(list_name))
                return trello_list
        logger.debug('Creating Trello list {}'.format(list_name))
        trello_list = self._board.add_list(list_name)
        self._lists.append(trello_list)  # Update _lists with new list
        return trello_list

    def get_board_labels(self):
        """
        Get the labels of a Trello board

        :param board_name: the board name
        :return: a list of Trello label objects
        :rtype: list
        """
        return self._board.get_labels()

    def get_board_label(self, label_name):
        """
        Get a Trello board label from label name, if it does not exist create it

        :param label_name: the label name
        :return: a Tello label
        :rtype: Trello label object
        """
        if self._board_labels == None:
            raise ClientError('get_board_label')
        for board_label in self._board_labels:
            if board_label.name == label_name:
                logger.debug('Trello board label {} found'.format(label_name))
                return board_label
        logger.debug('Creating Trello board label {}'.format(label_name))
        board_label = self._board.add_label(label_name, 'black')
        self._board_labels.append(
            board_label)  # Update _board_labels with new label
        return board_label

    def get_cards_dict(self):
        """
        Get all cards of a list of Trello lists in a dictionary

        :return: a dict with Cards
        :rtype: dict
        """
        trello_cards_dict = {}
        if self._lists_filter is not None:
            trello_lists = filter(
                lambda trello_list: trello_list.name not in self._lists_filter,
                self._lists)
        for trello_list in trello_lists:
            logger.debug('Getting Trello cards of list {}'.format(
                trello_list.name))
            trello_cards_dict[trello_list.name] = trello_list.list_cards()
            if self._only_my_cards:
                trello_cards_dict[trello_list.name] = filter(
                    lambda trello_card: self.whoami in trello_card.member_ids,
                    trello_cards_dict[trello_list.name])
        return trello_cards_dict

    def delete_trello_card(self, trello_card_id):
        """
        Delete (forever) a Trello card by ID

        :param trello_card_id: ID of Trello card
        """
        try:
            self.trello_client.get_card(trello_card_id).delete()
        except ResourceUnavailable:
            logger.warning(
                'Cannot find Trello card with ID {} deleted in Task Warrior. Maybe you also deleted it in Trello?'
                .format(trello_card_id))
예제 #11
0
class TrelloBoardTestCase(unittest.TestCase):
    """
    Tests for TrelloClient API. Note these test are in order to
    preserve dependencies, as an API integration cannot be tested
    independently.
    """

    def setUp(self):
        self._trello = TrelloClient(os.environ['TRELLO_API_KEY'],
                                    token=os.environ['TRELLO_TOKEN'])
        for b in self._trello.list_boards():
            if b.name == os.environ['TRELLO_TEST_BOARD_NAME']:
                self._board = b
                break
        try:
            self._list = self._board.open_lists()[0]
        except IndexError:
            self._list = self._board.add_list('List')

    def _add_card(self, name, description=None):
        try:
            card = self._list.add_card(name, description)
            self.assertIsNotNone(card, msg="card is None")
            self.assertIsNotNone(card.id, msg="id not provided")
            self.assertEquals(card.name, name)
            return card
        except Exception as e:
            print(str(e))
            self.fail("Caught Exception adding card")

    def test40_add_card(self):
        name = "Testing from Python - no desc"
        card = self._add_card(name)

        self.assertIsNotNone(card.closed, msg="closed not provided")
        self.assertIsNotNone(card.url, msg="url not provided")

        card2 = self._trello.get_card(card.id)
        self.assertEqual(card.name, card2.name)

    def test41_add_card(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._add_card(name, description)

        self.assertEquals(card.description, description)
        self.assertIsNotNone(card.closed, msg="closed not provided")
        self.assertIsNotNone(card.url, msg="url not provided")
        card.fetch()
        self.assertIsNotNone(card.member_id)
        self.assertIsNotNone(card.short_id)
        self.assertIsNotNone(card.list_id)
        self.assertIsNotNone(card.comments)
        self.assertIsNotNone(card.checklists)
        self.assertIsInstance(card.create_date, datetime)

    def test42_add_card_with_comments(self):
        name = "Card with comments"
        comment = "Hello World!"
        card = self._add_card(name)
        card.comment(comment)
        card.fetch(True)

        self.assertEquals(card.description, '')
        self.assertIsNotNone(card.closed, msg="closed not provided")
        self.assertIsNotNone(card.url, msg="url not provided")
        self.assertEquals(len(card.comments), 1)
        self.assertEquals(card.comments[0]['data']['text'], comment)

    def test43_delete_checklist(self):
        name = "Card with comments"
        card = self._list.add_card(name)
        card.fetch(True)

        name = 'Checklists'
        checklist = card.add_checklist(name,
                                       ['item1', 'item2'])
        self.assertIsNotNone(checklist, msg="checklist is None")
        self.assertIsNotNone(checklist.id, msg="id not provided")
        self.assertEquals(checklist.name, name)
        checklist.delete()
        card.delete()

    def test44_attach_url_to_card(self):
        name = "Testing from Python - url"
        card = self._add_card(name)

        card.attach(name='lwn', url='http://lwn.net/')
        card.fetch()
        self.assertEquals(card.badges['attachments'], 1)
        card.delete()

    def test52_get_cards(self):
        cards = self._board.get_cards()
        self.assertEquals(len(cards), 4)

        for card in cards:
            if card.name == 'Testing from Python':
                self.assertEqual(card.description, 'Description goes here')
            elif card.name == 'Testing from Python - no desc':
                self.assertEqual(card.description, '')
            elif card.name == 'Card with comments':
                self.assertEqual(card.description, '')
            else:
                self.fail(msg='Unexpected card found')

        self.assertIsInstance(self._board.all_cards(), list)
        self.assertIsInstance(self._board.open_cards(), list)
        self.assertIsInstance(self._board.closed_cards(), list)

    def test52_add_card_set_due(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._list.add_card(name, description)

        # Set the due date to be 3 days from now
        today = datetime.today()
        day_detla = timedelta(3)
        due_date = today + day_detla
        card.set_due(due_date)
        expected_due_date = card.due
        # Refresh the due date from cloud
        card.fetch()
        actual_due_date = card.due[:10]
        self.assertEquals(expected_due_date, actual_due_date)
        # Note that set_due passes only the date, stripping time
        self.assertEquals(card.due_date.date(), due_date.date())

    def test53_checklist(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._list.add_card(name, description)

        name = 'Checklists'
        checklist = card.add_checklist(name,
                                       ['item1', 'item2'])
        self.assertIsNotNone(checklist, msg="checklist is None")
        self.assertIsNotNone(checklist.id, msg="id not provided")
        self.assertEquals(checklist.name, name)
        checklist.rename('Renamed')
        self.assertEquals(checklist.name, 'Renamed')

    def test54_set(self):
        name = "Testing from Python"
        description = "Description goes here"
        card = self._list.add_card('noname')
        card.set_name(name)
        card.set_description(description)
        self.assertEquals(card.name, name)
        self.assertEquals(card.description, description)

    def test55_set_pos(self):
        card_names = lambda: [c.name for c in self._list.list_cards()]
        self._list.add_card('card1')
        card2 = self._list.add_card('card2')
        names = card_names()
        self.assertGreater(names.index('card2'), names.index('card1'))

        card2.set_pos('top')
        names = card_names()
        self.assertGreater(names.index('card1'), names.index('card2'))

        card2.set_pos('bottom')
        names = card_names()
        self.assertGreater(names.index('card2'), names.index('card1'))

    def test60_delete_cards(self):
        cards = self._board.get_cards()
        for card in cards:
            card.delete()

    def test70_all_members(self):
        self.assertTrue(len(self._board.all_members()) > 0)

    def test71_normal_members(self):
        self.assertTrue(len(self._board.normal_members()) >= 0)

    def test72_admin_members(self):
        self.assertTrue(len(self._board.admin_members()) > 0)

    def test73_owner_members(self):
        members = self._board.owner_members()
        self.assertTrue(len(members) > 0)
        member = members[0].fetch()
        self.assertNotEqual(member.status, None)
        self.assertNotEqual(member.id, None)
        self.assertNotEqual(member.bio, None)
        self.assertNotEqual(member.url, None)
        self.assertNotEqual(member.username, None)
        self.assertNotEqual(member.full_name, None)
        self.assertNotEqual(member.initials, None)
        member2 = self._trello.get_member(member.id)
        self.assertEqual(member.username, member2.username)

    def test80_unauthorized(self):
        client = TrelloClient('a')
        self.assertRaises(Unauthorized,
                          client.list_boards)

    def test81_resource_unavailable(self):
        self.assertRaises(ResourceUnavailable,
                          self._trello.get_card, '0')

    def test90_get_board(self):
        board = self._trello.get_board(self._board.id)
        self.assertEqual(self._board.name, board.name)
예제 #12
0
    fields = []
    for p in plugin_data:
        if p.get('idPlugin') == FREE_FIELD_PLUGIN_ID:
            value = json.loads(p['value'])
            fields = [STORIES[i] for i, v in value['fields'].items() if v]
    return fields

progress_cpt = {}
cards_by_story = {}
dci_board = client.get_board('0eMozAiR')
for l in dci_board.all_lists():
    if l.name in ACTIVE_lISTS.keys():
        trello_list = dci_board.get_list(l.id)
        for c in trello_list.list_cards():
            c.list = l
            c.members = [client.get_member(i) for i in c.member_ids]
            c.status = ACTIVE_lISTS[l.name]
            c.fetch(eager=True)
            c.checklist_items = []
            try:
                c.checklist_items = c._checklists[0].items
            except IndexError:
                pass
            stories = get_stories(client, c)
            if not stories:
                stories += ['other']
            stories += ['all']
            for story in stories:
                if story not in cards_by_story:
                    cards_by_story[story] = []
                    progress_cpt[story] = Cpt()