async def closed(event: github.EventType): ref = event.payload[event.key]['html_url'] logger.debug( f'Triggered {event.key}/{event.action} event handler from ref {ref}') token = event.app['token'] card_data = await github.graphql_query(queries.GET_ALL_CARDS, token=token) cards_df = projects.build_cards_frame(card_data) matching_cards = cards_df[cards_df['ref'] == ref] if len(matching_cards) == 0: logger.debug(f'No cards found matching ref {ref}') return web.Response() df_column = 'in_progress_column_id' if event.action == 'reopened' else 'done_column_id' async with github.ProjectClientSession(token=token) as session: for _, card in matching_cards.iterrows(): card_id = int(card['card_id']) column_id = int(card[df_column]) logger.info(f'Moving card {card_id} to column {column_id}') response = await session.move_project_card(card_id=card_id, column_id=column_id) if response.status != 201: logger.warning(f'Failed to move card [{response.status}]') await asyncio.sleep(0.1) await utils.log_rate_limits(token=token) return web.Response()
async def moved(event: github.EventType): mover = event.payload['sender']['login'] if mover == 'xdev-bot': return web.Response() moved_card_id = event.payload[event.key]['id'] note = event.payload[event.key]['note'] refs = utils.refs_from_note(note) if len(refs) != 1: logger.debug(f'No cards found matching note: {note}') return web.Response() ref = refs[0] logger.debug( f'Triggered {event.key}/{event.action} event handler from note "{note}"' ) token = event.app['token'] all_card_data = await github.graphql_query(queries.GET_ALL_CARDS, token=token) cards_df = projects.build_cards_frame(all_card_data) moved_card = cards_df[cards_df['card_id'] == moved_card_id] if len(moved_card) != 1: logger.debug(f'Could not find moved card {moved_card_id}') return web.Response() moved_column_name = moved_card['column_name'].to_list()[0] df_column = f'{moved_column_name.lower().replace(" ", "_")}_column_id' matching_cards = cards_df[cards_df['ref'] == ref] other_cards = matching_cards[matching_cards['card_id'] != moved_card_id] if len(other_cards) == 0: logger.debug(f'No other cards found matching ref {ref}') return web.Response() async with github.ProjectClientSession(token=token) as session: for _, card in other_cards.iterrows(): card_id = int(card['card_id']) column_id = int(card[df_column]) logger.info(f'Moving card {card_id} to column {column_id}') response = await session.move_project_card(card_id=card_id, column_id=column_id) if response.status != 201: logger.warning(f'Failed to move card [{response.status}]') await asyncio.sleep(0.1) await utils.log_rate_limits(token=token) return web.Response()
async def test_project_card_lifetime(): note = 'https://github.com/NCAR/xdevbot-testing/issues/5' # content_id = 664727902 # content_type = 'Issue' async with github.ProjectClientSession(token=TOKEN) as session: response = await session.create_project_card(note=note, column_id=NEW_COLUMN_ID) assert response.status == 201 new_card = await response.json() response = await session.list_project_cards(column_id=NEW_COLUMN_ID) assert response.status == 200 new_cards = await response.json() new_card_found = False for card in new_cards: if card['id'] == new_card['id']: new_card_found = True assert new_card_found response = await session.update_project_card(card_id=new_card['id'], archived=True) assert response.status == 200 card = await response.json() assert card['archived'] response = await session.update_project_card(card_id=new_card['id'], archived=False) assert response.status == 200 card = await response.json() assert not card['archived'] response = await session.get_project_card(card_id=new_card['id']) assert response.status == 200 card = await response.json() assert card['id'] == new_card['id'] assert card['note'] == new_card['note'] response = await session.move_project_card(card_id=new_card['id'], column_id=OLD_COLUMN_ID) assert response.status == 201 response = await session.delete_project_card(card_id=new_card['id']) assert response.status == 204
async def deleted(event: github.EventType): deleter = event.payload['sender']['login'] if deleter == 'xdev-bot': return web.Response() deleted_card_id = event.payload[event.key]['id'] note = event.payload[event.key]['note'] refs = utils.refs_from_note(note) if len(refs) != 1: logger.debug(f'No cards found matching note: {note}') return web.Response() ref = refs[0] logger.debug( f'Triggered {event.key}/{event.action} event handler from note "{note}"' ) token = event.app['token'] all_card_data = await github.graphql_query(queries.GET_ALL_CARDS, token=token) cards_df = projects.build_cards_frame(all_card_data) matching_cards = cards_df[cards_df['ref'] == ref] other_cards = matching_cards[matching_cards['card_id'] != deleted_card_id] if len(other_cards) == 0: logger.debug(f'No other cards found matching ref {ref}') return web.Response() async with github.ProjectClientSession(token=token) as session: for _, card in other_cards.iterrows(): card_id = int(card['card_id']) logger.info(f'Deleting card {card_id}') response = await session.delete_project_card(card_id=card_id) if response.status != 204: logger.warning(f'Failed to delete card [{response.status}]') await asyncio.sleep(0.1) await utils.log_rate_limits(token=token) return web.Response()
async def opened(event: github.EventType): ref = event.payload[event.key]['html_url'] logger.debug( f'Triggered {event.key}/{event.action} event handler from ref {ref}') repo = event.payload['repository']['full_name'] cfg_data = await utils.read_remote_yaml(CONFIG_URL) df = projects.build_config_frame(cfg_data) project_urls = df[df['repo'] == repo]['project_url'].to_list() if len(project_urls) == 0: logger.debug(f'No projects associated with repo {repo}') return web.Response() token = event.app['token'] column_data = await github.graphql_query(queries.GET_COLUMNS, token=token) columns_df = projects.build_columns_frame(column_data) column_name = 'In Progress' if event.key == 'pull_request' else 'New' async with github.ProjectClientSession(token=token) as session: for project_url in project_urls: logger.info(f'Creating new card on project {project_url}') df = columns_df[columns_df['project_url'] == project_url] column_id = int(df[df['column_name'] == column_name]['column_id']) response = await session.create_project_card(note=ref, column_id=column_id) if response.status != 201: logger.warning( f'Failed to create new card! [{response.status}]') body = json.dumps(await response.json(), indent=4) logger.debug(f'HTTP Response Body:\n{body}') await asyncio.sleep(0.1) await utils.log_rate_limits(token=token) return web.Response()
async def main(): token = os.environ.get('XDEVBOT_TOKEN', None) cfg_data = await utils.read_remote_yaml(routes.CONFIG_URL) cfg_df = projects.build_config_frame(cfg_data) all_card_data = await github.graphql_query(GET_ALL_CARDS, token=token) all_cards_df = projects.build_cards_frame(all_card_data) columns_df = projects.build_columns_frame(all_card_data) new_cards_df = all_cards_df[all_cards_df['project_url'].isin(cfg_df['project_url'])] old_cards_df = all_cards_df[all_cards_df['project_url'] == BACKLOG_URL] unwatched = set() existing = set() missing = set() nonbot = set() for _, old_card in old_cards_df.iterrows(): ref = old_card['ref'] new_cards = new_cards_df[new_cards_df['ref'] == ref] if len(new_cards) > 0: existing.add(ref) continue creator = old_card['creator'] if creator != 'xdev-bot': nonbot.add(ref) continue org, user, _ = utils.split_issue_ref(ref) repo = f'{org}/{user}' project_urls = cfg_df[cfg_df['repo'] == repo]['project_url'].to_list() if len(project_urls) == 0: unwatched.add(repo) continue missing.add(ref) print() print(f'--- Need to copy card: {ref}') old_column = old_card['column_name'] new_column = BACKLOG_COLUMN_MAP[old_column] print(f' From column "{old_column}"') async with github.ProjectClientSession(token=token) as session: for project_url in project_urls: df = columns_df[columns_df['project_url'] == project_url] column_id = int(df[df['column_name'] == new_column]['column_id']) print(f' To project {project_url} column "{new_column}" [id: {column_id}]') response = await session.create_project_card(note=ref, column_id=column_id) if response.status != 201: print(f' *** Failed to transfer card! [{response.status}]') if unwatched: print() print('=== Unwatched Repositories:') for repo in sorted(unwatched): print(f' - {repo}') if nonbot: print() print('=== Non-bot cards that were skipped:') for ref in sorted(nonbot): print(f' - {ref}') if existing: print() print(f'Found {len(existing)} existing cards that were skipped.') if missing: print() print(f'Will copy {len(missing)} cards to new boards.')
async def update_nonbot_cards(token=None): card_data = await github.graphql_query(queries.GET_ALL_CARDS, token=token) columns = projects.build_columns_frame(card_data) cards = projects.build_cards_frame(card_data) nonbot_cards = cards[cards['creator'] != 'xdev-bot'] logger.debug(f'Found {len(nonbot_cards)} non-bot cards on project boards') num_cards_moved = 0 num_failures = 0 for _, card in nonbot_cards.iterrows(): card_id = card['card_id'] project_url = card['project_url'] column_id = card['column_id'] df = columns[columns['project_url'] == project_url] column_name = df[df['column_id'] == column_id]['column_name'].item() ref = card['ref'] content_id = card['content_id'] if not pd.isna(content_id): state = card['content_state'].lower() elif not pd.isna(ref): owner, repo, number = utils.split_issue_ref(ref) async with github.IssueClientSession(token=token) as session: response = await session.get_issue(owner=owner, repo=repo, number=number) if response.status != 200: logger.warning( f'Failed to retrieve state of reference: {owner}/{repo}#{number}' ) continue issue = await response.json() state = issue['state'] else: logger.warning( f'Non-bot card {card_id} has no content_id or reference. Skipping.' ) continue if state == 'open' and column_name == 'Done': logger.debug( f'Non-bot card {card_id} is open but was found in the "Done" column' ) column_id = card['in_progress_column_id'] elif state != 'open' and column_name != 'Done': logger.debug( f'Non-bot card {card_id} is closed but wasn\'t found in the "Done" column' ) column_id = card['done_column_id'] else: logger.debug( f'Non-bot card {card_id} appears to be in a consistent column') column_id = None if column_id: async with github.ProjectClientSession(token=token) as session: logger.debug( f'Moving non-bot card {card_id} to column {column_id}') response = await session.move_project_card(card_id=card_id, column_id=column_id) if response.status == 201: num_cards_moved += 1 else: num_failures += 1 logger.warning( f'Failed to move non-bot card [{response.status}]') if num_cards_moved > 0: logger.info( f'Updated {num_cards_moved} non-bot cards on project boards ({num_failures} failures)' )