An easy-to-use burndown chart generator for GitHub Project Boards.
0

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #19 from jhale1805:dev

Add support for organization projects + multiple saved configs

authored by

Joseph Hale and committed by
GitHub
(Oct 16, 2021, 5:14 PM -0700) bcbf2212 fd5e5725

+416 -60
+143 -4
.gitignore
··· 1 + # Local config files 1 2 secrets.json 2 3 config.json 3 4 4 - **/venv** 5 - 5 + # VS Code Project Files 6 6 .vscode 7 7 8 - __pycache__ 9 8 10 - .coverage 9 + ############################################################################ 10 + # From https://github.com/github/gitignore/blob/master/Python.gitignore 11 + ############################################################################ 12 + # Byte-compiled / optimized / DLL files 13 + __pycache__/ 14 + *.py[cod] 15 + *$py.class 16 + 17 + # C extensions 18 + *.so 19 + 20 + # Distribution / packaging 21 + .Python 22 + build/ 23 + develop-eggs/ 24 + dist/ 25 + downloads/ 26 + eggs/ 27 + .eggs/ 28 + lib/ 29 + lib64/ 30 + parts/ 31 + sdist/ 32 + var/ 33 + wheels/ 34 + share/python-wheels/ 35 + *.egg-info/ 36 + .installed.cfg 37 + *.egg 38 + MANIFEST 39 + 40 + # PyInstaller 41 + # Usually these files are written by a python script from a template 42 + # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 + *.manifest 44 + *.spec 45 + 46 + # Installer logs 47 + pip-log.txt 48 + pip-delete-this-directory.txt 49 + 50 + # Unit test / coverage reports 51 + htmlcov/ 52 + .tox/ 53 + .nox/ 54 + .coverage 55 + .coverage.* 56 + .cache 57 + nosetests.xml 58 + coverage.xml 59 + *.cover 60 + *.py,cover 61 + .hypothesis/ 62 + .pytest_cache/ 63 + cover/ 64 + 65 + # Translations 66 + *.mo 67 + *.pot 68 + 69 + # Django stuff: 70 + *.log 71 + local_settings.py 72 + db.sqlite3 73 + db.sqlite3-journal 74 + 75 + # Flask stuff: 76 + instance/ 77 + .webassets-cache 78 + 79 + # Scrapy stuff: 80 + .scrapy 81 + 82 + # Sphinx documentation 83 + docs/_build/ 84 + 85 + # PyBuilder 86 + .pybuilder/ 87 + target/ 88 + 89 + # Jupyter Notebook 90 + .ipynb_checkpoints 91 + 92 + # IPython 93 + profile_default/ 94 + ipython_config.py 95 + 96 + # pyenv 97 + # For a library or package, you might want to ignore these files since the code is 98 + # intended to run in multiple environments; otherwise, check them in: 99 + # .python-version 100 + 101 + # pipenv 102 + # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 103 + # However, in case of collaboration, if having platform-specific dependencies or dependencies 104 + # having no cross-platform support, pipenv may install dependencies that don't work, or not 105 + # install all needed dependencies. 106 + #Pipfile.lock 107 + 108 + # PEP 582; used by e.g. github.com/David-OConnor/pyflow 109 + __pypackages__/ 110 + 111 + # Celery stuff 112 + celerybeat-schedule 113 + celerybeat.pid 114 + 115 + # SageMath parsed files 116 + *.sage.py 117 + 118 + # Environments 119 + .env 120 + .venv 121 + env/ 122 + venv/ 123 + ENV/ 124 + env.bak/ 125 + venv.bak/ 126 + 127 + # Spyder project settings 128 + .spyderproject 129 + .spyproject 130 + 131 + # Rope project settings 132 + .ropeproject 133 + 134 + # mkdocs documentation 135 + /site 136 + 137 + # mypy 138 + .mypy_cache/ 139 + .dmypy.json 140 + dmypy.json 141 + 142 + # Pyre type checker 143 + .pyre/ 144 + 145 + # pytype static type analyzer 146 + .pytype/ 147 + 148 + # Cython debug symbols 149 + cython_debug/
+1 -1
Makefile
··· 7 7 8 8 run: instructions 9 9 cd ./src/github_projects_burndown_chart \ 10 - && PYTHONPATH=. python main.py 10 + && PYTHONPATH=. python main.py $(project_type) $(project_name) 11 11 12 12 test: instructions 13 13 coverage run \
+64 -31
README.md
··· 1 1 # Burndown Chart for GitHub Projects 2 2 An easy to use [burndown chart](https://www.scrum.org/resources/scrum-glossary#:~:text=B-,burn-down%20chart,-%3A%C2%A0a%20chart%20which) generator for [GitHub Project Boards](https://docs.github.com/en/issues/organizing-your-work-with-project-boards/managing-project-boards/about-project-boards). 3 3 4 + ![Example Burndown Chart](./docs/images/example_burndown_chart.png) 5 + 4 6 ## Table of Contents 5 7 * [Features](#features) 6 8 * [Installation](#installation) 7 9 * [Assumptions](#assumptions) 10 + * [Configuration](#configuration) 8 11 * [Usage](#usage) 9 12 * [Contributing](#contributing) 10 13 * [About](#about) 11 14 12 15 ## Features 13 16 * Create a **burndown chart for a GitHub Project Board**. 14 - * Works for **private repositories**. 17 + * Works for projects in **public/private repositories** and **organizations**. 15 18 * Includes a **trend line** for the current sprint. 16 19 * Supports custom labels for **tracking points for issues** 17 20 ··· 20 23 * You use one and only one [GitHub Project Board](https://docs.github.com/en/issues/organizing-your-work-with-project-boards/managing-project-boards/about-project-boards) for each of your [Sprints](https://scrumguides.org/scrum-guide.html#the-sprint) 21 24 * You use one and only one [GitHub Milestone](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/about-milestones) for each of your [User Stories](https://www.scrum.org/resources/blog/user-story-or-stakeholder-story) 22 25 * You use one and only one [GitHub Issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues) for each of your [Sprint Backlog Items/Tasks](https://scrumguides.org/scrum-guide.html#sprint-backlog) 23 - * Each of your GitHub Issues has a [label](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) indicating how many [points](https://www.scrum.org/resources/scrum-glossary#:~:text=several%20ways%20such%20as-,user%20story%20points,-or%20task%20hours.%20Work) its corresponding task is worth. 26 + * If you want to track points, each of your GitHub Issues has a [label](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) indicating how many [points](https://www.scrum.org/resources/scrum-glossary#:~:text=several%20ways%20such%20as-,user%20story%20points,-or%20task%20hours.%20Work) its corresponding task is worth. 24 27 - Furthermore, all labels that indicate point values have the format `<prefix><int>`. 25 - - However, multiple labels indicating points on the same Issue are supported. 28 + - Multiple labels indicating points on the same Issue are supported. 26 29 * A Sprint Backlog Task is considered [Done](https://www.scrum.org/resources/professional-scrum-developer-glossary#:~:text=D-,definition%20of%20done%3A,-a%20shared%20understanding) if its corresponding GitHub Issue is Closed. 27 30 28 31 ## Installation ··· 38 41 39 42 ### 2. Activate the virtual environment 40 43 41 - *Linux/Mac OS* 44 + *Linux/macOS* 42 45 ``` 43 46 source venv/bin/activate 44 47 ``` ··· 56 59 pip install -r requirements.txt 57 60 ``` 58 61 62 + ## Configuration 63 + ### 1. Create a [Personal Access Token](https://github.com/settings/tokens) with the `repo` scope. 64 + Do not share this token with anyone! It gives the bearer full control over all private repositories you have access to! 65 + 66 + This is required to pull the Project Board data from GitHub's GraphQL API. 67 + ### 2. Make a copy of `src/github_projects_burndown_chart/config/secrets.json.dist` without the `.dist` ending. 68 + This allows the `.gitignore` to exclude your `secrets.json` from being accidentally committed. 69 + ### 3. Fill out the `github_token` with your newly created Personal Access Token. 70 + ### 4. Make a copy of `src/github_projects_burndown_chart/config/config.json.dist` without the `.dist` ending. 71 + This allows the `.gitignore` to exclude your `config.json` from being accidentally committed. 72 + ### 5. Fill out all the configuration settings 73 + #### Repository Projects 74 + `project_name`: A memorable name for your project for use with this tool. 75 + 76 + `project_name.query_variables` 77 + | Variable | Meaning | 78 + |----------|---------| 79 + | `repo_owner` | The username of the owner of the repo. <br/><br/> Example: `jhale1805` | 80 + | `repo_name` | The name of the repo. <br/><br/> Example: `github-projects-burndown-chart`| 81 + | `project_number` | The ID of the project for which you want to generate a burndown chart. This is found in the URL when looking at the project board on GitHub. <br/><br/> Example: `1` (from [`https://github.com/jhale1805/github-projects-burndown-chart/projects/1`](https://github.com/jhale1805/github-projects-burndown-chart/projects/1)) | 82 + | `column_count` | A number >= the number of columns on the project board. (DEFAULT: 5)<br/><br/> A closer fit improves performance and reduces the chance of rate limiting from GitHub's GraphQL API. | 83 + | `max_cards_per_column_count` | A number >= the maximum number of cards in any column on the project board. (DEFAULT: 50)<br/><br/> A closer fit improves performance and reduces the chance of rate limiting from GitHub's GraphQL API. | 84 + | `labels_per_issue_count` | A number >= the number of labels on any issue on project board. (DEFAULT: 5)<br/><br/> A closer fit improves performance and reduces the chance of rate limiting from GitHub's GraphQL API. | 85 + 86 + `project_name.settings` 87 + | Variable | Meaning | 88 + |----------|---------| 89 + | `sprint_start_date` | The first day of the sprint formatted as `YYYY-MM-DD`. <br/><br/> Must be entered here since GitHub Project Boards don't have an assigned start/end date. <br/><br/> Example: `2021-10-08` | 90 + | `sprint_end_date` | The last day of the sprint formatted as `YYYY-MM-DD`. <br/><br/> Must be entered here since GitHub Project Boards don't have an assigned start/end date. <br/><br/> Example: `2021-10-21` | 91 + | `points_label` | (OPTIONAL) The prefix for issue labels containing the point value of the issue. Removing this prefix must leave just an integer. If set to `null`, the burndown chart will count open issues instead of points.<br/><br/> Example: `Points: ` (with the space) | 92 + 93 + #### Organization Projects 94 + All settings are the same as for the [Repository Projects](#repository-projects), except `repo_owner` and `repo_name` are replaced with `organization_name` as shown below. 95 + 96 + `project_name.query_variables` 97 + | Variable | Meaning | 98 + |----------|---------| 99 + | `organization_name` | The name of the organization on GitHub as it appears in the URL of their organization page. <br/><br/> Example: `golang` (from https://github.com/golang) | 59 100 ## Usage 60 - ### Configuration 61 - 1. Create a [Personal Access Token](https://github.com/settings/tokens) with the `repo` scope. 62 - - Do not share this token with anyone! It gives the bearer full control over all private repositories you have access to! 63 - - This is required to pull the Project Board data from GitHub's GraphQL API. 64 - 2. Make a copy of `src/github_projects_burndown_chart/config/secrets.json.dist` without the `.dist` ending. 65 - - This allows the `.gitignore` to exclude your `secrets.json` from being accidentally committed. 66 - 3. Fill out the `github_token` with your newly created Personal Access Token. 67 - 4. Make a copy of `src/github_projects_burndown_chart/config/config.json.dist` without the `.dist` ending. 68 - - This allows the `.gitignore` to exclude your `config.json` from being accidentally committed. 69 - 5. Fill out all the configuration settings 70 - - `repository_project_query.repo_owner`: The username of the owner of the repo. 71 - - For example, `jhale1805` 72 - - `repository_project_query.repo_name`: The name of the repo. 73 - - For example, `github-projects-burndown-chart` 74 - - `repository_project_query.project_number`: The id of the project for which you want to generate a burndown chart. This is found in the URL when looking at the project board on GitHub. 75 - - For example, `1` from [`https://github.com/jhale1805/github-projects-burndown-chart/projects/1`](https://github.com/jhale1805/github-projects-burndown-chart/projects/1) 76 - - `settings.sprint_start_date`: The first day of the sprint. Formatted as `YYYY-MM-DD`. 77 - - Must be entered here since GitHub Project Boards don't have an assigned start/end date. 78 - - For example, `2021-10-08` 79 - - `settings.sprint_end_date`: The last day of the sprint. Formatted as `YYYY-MM-DD`. 80 - - Must be entered here since GitHub Project Boards don't have an assigned start/end date. 81 - - For example, `2021-10-22` 82 - - `settings.points_label`: The prefix for issue labels containing the point value of the issue. Removing this prefix must leave just an integer. 83 - - For example: `Points: ` (with the space) 84 - ### Generating the Chart 85 - 1. Run `make run` to generate the burndown chart. 86 - - This will pop up an interactive window containing the burndown chart, including a button for saving it as a picture. 101 + Given that `PROJECT_TYPE` is one of `[repository, organization]` and `PROJECT_NAME` matches a key in the `config.json` under the chosen `PROJECT_TYPE`, run the following command: 102 + ``` 103 + make run project_type=PROJECT_TYPE project_name=PROJECT_NAME 104 + ``` 105 + 106 + This will pop up an interactive window containing the burndown chart, including a button for saving it as a picture. 107 + 108 + ### Example 109 + Make a copy of `example.config.json` without the leading `example.` 110 + 111 + To see this repository's example project board: 112 + ``` 113 + make run project_type=repository project_name=burndown_chart_kickoff 114 + ``` 115 + 116 + To see Golang's progress on their current roadmap: 117 + ``` 118 + make run project_type=organization project_name=golang_on_deck 119 + ``` 87 120 88 121 ## Contributing 89 122 Contributions are welcome via a [Pull Request](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
docs/images/example_burndown_chart.png

This is a binary file and will not be displayed.

+24 -3
src/github_projects_burndown_chart/main.py
··· 1 + import argparse 2 + 1 3 from chart.burndown import BurndownChart 2 - from gh.api_wrapper import get_repository_project 4 + from config import config 5 + from gh.api_wrapper import get_organization_project, get_repository_project 3 6 from gh.project import Project 4 7 5 8 if __name__ == '__main__': 6 - project: Project = get_repository_project() 9 + # Parse the command line arguments 10 + parser = argparse.ArgumentParser( 11 + description='Generate a burndown chart for a GitHub project.') 12 + parser.add_argument("project_type", 13 + choices=['repository', 'organization'], 14 + help="The type of project to generate a burndown chart for. Can be either 'organization' or 'repository'.") 15 + parser.add_argument("project_name", help="The name of the project as it appears in the config.json") 16 + args = parser.parse_args() 17 + 18 + # Point the config to the correct project 19 + config.set_project(args.project_type, args.project_name) 20 + 21 + # Pull the corresponding project from GitHub 22 + if args.project_type == 'repository': 23 + project: Project = get_repository_project() 24 + elif args.project_type == 'organization': 25 + project: Project = get_organization_project() 26 + 27 + # Generate the burndown chart 7 28 burndown_chart = BurndownChart(project) 8 29 burndown_chart.render() 9 - print('Done') 30 + print('Done')
+2 -1
src/github_projects_burndown_chart/chart/burndown.py
··· 41 41 42 42 # Set titles and labels 43 43 plt.title(f"{self.project.name}: Burndown Chart") 44 - plt.ylabel("Outstanding Points") 44 + points_label = config['settings']['points_label'] 45 + plt.ylabel(f"Outstanding {'Points' if points_label else 'Issues'}") 45 46 plt.xlabel("Date") 46 47 47 48 # Generate Plot
+33 -6
src/github_projects_burndown_chart/config/__init__.py
··· 15 15 os.getcwd(), 16 16 os.path.dirname(__file__))) 17 17 18 + ############################################################################### 19 + # Load config.json 20 + ############################################################################### 18 21 try: 19 22 with open(os.path.join(__location__, 'config.json')) as config_json: 20 - config = json.load(config_json) 23 + __config = json.load(config_json) 21 24 except FileNotFoundError as err: 22 25 __logger.critical(err) 23 26 __logger.critical('Please create a config.json file in the config ' 24 - 'directory; this tool cannot generate a burndown chart without it.') 27 + 'directory; this tool cannot generate a burndown chart without it.') 25 28 __logger.critical('See the project README.md and config/config.json.dist ' 26 - 'for details.') 29 + 'for details.') 27 30 exit(1) 28 31 32 + 33 + class Config: 34 + 35 + def __init__(self, raw_config: dict): 36 + self.raw_config = raw_config 37 + 38 + def set_project(self, project_type: str, project_name: str): 39 + self.project_type = project_type 40 + self.project_name = project_name 41 + 42 + def __getitem__(self, key: str): 43 + if not hasattr(self, 'project_type'): 44 + raise AttributeError('No project has been set.') 45 + if not hasattr(self, 'project_name'): 46 + raise AttributeError('No project has been set.') 47 + return self.raw_config[self.project_type][self.project_name][key] 48 + 49 + 50 + config = Config(__config) 51 + 52 + 53 + ############################################################################### 54 + # Load secrets.json 55 + ############################################################################### 29 56 try: 30 57 with open(os.path.join(__location__, 'secrets.json')) as secrets_json: 31 58 secrets = json.load(secrets_json) 32 59 except FileNotFoundError as err: 33 60 __logger.critical(err) 34 61 __logger.critical('Please create a secrets.json file in the config ' 35 - 'directory; this tool cannot generate a burndown chart without it.') 62 + 'directory; this tool cannot generate a burndown chart without it.') 36 63 __logger.critical('See the project README.md and config/secrets.json.dist ' 37 - 'for details.') 38 - exit(1) 64 + 'for details.') 65 + exit(1)
+54 -5
src/github_projects_burndown_chart/config/config.json.dist
··· 1 1 { 2 - "repository_project_query": { 2 + "repository": { 3 + "PROJECT_1_NAME": { 4 + "query_variables": { 3 5 "repo_owner": "REQUIRED", 4 6 "repo_name": "REQUIRED", 5 7 "project_number": -1, //REQUIRED 6 8 "column_count": 5, 7 9 "max_cards_per_column_count": 50, 8 10 "labels_per_issue_count": 5 9 - }, 10 - "settings": { 11 + }, 12 + "settings": { 11 13 "sprint_start_date": "REQUIRED", 12 14 "sprint_end_date": "REQUIRED", 13 - "points_label": "REQUIRED" 15 + "points_label": null 16 + } 14 17 } 15 - } 18 + "PROJECT_2_NAME": { 19 + "query_variables": { 20 + "repo_owner": "REQUIRED", 21 + "repo_name": "REQUIRED", 22 + "project_number": -1, //REQUIRED 23 + "column_count": 5, 24 + "max_cards_per_column_count": 50, 25 + "labels_per_issue_count": 5 26 + }, 27 + "settings": { 28 + "sprint_start_date": "REQUIRED", 29 + "sprint_end_date": "REQUIRED", 30 + "points_label": null 31 + } 32 + } 33 + }, 34 + "organization": { 35 + "PROJECT_1_NAME": { 36 + "query_variables": { 37 + "organization_name": "REQUIRED", 38 + "project_number": -1, //REQUIRED 39 + "column_count": 5, 40 + "max_cards_per_column_count": 50, 41 + "labels_per_issue_count": 5 42 + }, 43 + "settings": { 44 + "sprint_start_date": "REQUIRED", 45 + "sprint_end_date": "REQUIRED", 46 + "points_label": null 47 + } 48 + } 49 + "PROJECT_2_NAME": { 50 + "query_variables": { 51 + "organization_name": "REQUIRED", 52 + "project_number": -1, //REQUIRED 53 + "column_count": 5, 54 + "max_cards_per_column_count": 50, 55 + "labels_per_issue_count": 5 56 + }, 57 + "settings": { 58 + "sprint_start_date": "REQUIRED", 59 + "sprint_end_date": "REQUIRED", 60 + "points_label": null 61 + } 62 + } 63 + } 64 + }
+35
src/github_projects_burndown_chart/config/example.config.json
··· 1 + { 2 + "repository": { 3 + "burndown_chart_kickoff": { 4 + "query_variables": { 5 + "repo_owner": "jhale1805", 6 + "repo_name": "github-projects-burndown-chart", 7 + "project_number": 1, 8 + "column_count": 5, 9 + "max_cards_per_column_count": 50, 10 + "labels_per_issue_count": 5 11 + }, 12 + "settings": { 13 + "sprint_start_date": "2021-10-07", 14 + "sprint_end_date": "2021-10-21", 15 + "points_label": "Points: " 16 + } 17 + } 18 + }, 19 + "organization": { 20 + "golang_on_deck": { 21 + "query_variables": { 22 + "organization_name": "golang", 23 + "project_number": 5, 24 + "column_count": 5, 25 + "max_cards_per_column_count": 50, 26 + "labels_per_issue_count": 5 27 + }, 28 + "settings": { 29 + "sprint_start_date": "2021-02-28", 30 + "sprint_end_date": "2021-10-21", 31 + "points_label": null 32 + } 33 + } 34 + } 35 + }
+18 -5
src/github_projects_burndown_chart/gh/api_wrapper.py
··· 4 4 5 5 from config import config, secrets 6 6 from .project import Project 7 - from .queries import RepositoryProject 7 + from .queries import OrganizationProject, RepositoryProject 8 8 9 9 # Set up logging 10 10 __logger = logging.getLogger(__name__) ··· 15 15 16 16 17 17 def get_repository_project() -> dict: 18 - query_response = gh_api_query( 19 - RepositoryProject, config.get('repository_project_query')) 18 + query_variables = config['query_variables'] 19 + query_response = gh_api_query(RepositoryProject, query_variables) 20 20 project_data = query_response['data']['repository']['project'] 21 + return Project(project_data) 22 + 23 + 24 + def get_organization_project() -> dict: 25 + query_variables = config['query_variables'] 26 + query_response = gh_api_query(OrganizationProject, query_variables) 27 + project_data = query_response['data']['organization']['project'] 21 28 return Project(project_data) 22 29 23 30 24 31 def gh_api_query(query: str, variables: dict) -> dict: 25 32 headers = {'Authorization': 'bearer %s' % secrets['github_token']} \ 26 33 if 'github_token' in secrets else {} 27 - 34 + 28 35 response = requests.post( 29 36 'https://api.github.com/graphql', 30 37 headers=headers, 31 38 json={'query': query, 'variables': variables}).json() 32 - 39 + 33 40 # Gracefully report failures due to bad credentials 34 41 if response.get('message') and response['message'] == 'Bad credentials': 35 42 __logger.critical(response['message']) ··· 38 45 __logger.critical('Please set the `github_token` key in the ' 39 46 '`src/secrets.json` file to a valid access token with access ' 40 47 'to the repo specified in the `src/config.json` file.') 48 + exit(1) 49 + # Gracefully report failures due to errors 50 + elif response.get('errors'): 51 + __logger.critical('Failed to extract project data from GitHub due to ' 52 + 'an error.') 53 + __logger.critical(response['errors']) 41 54 exit(1) 42 55 return response
+8 -4
src/github_projects_burndown_chart/gh/project.py
··· 84 84 85 85 def __parse_points(self, card_data): 86 86 card_points = 0 87 - card_labels = card_data.get('labels', {"nodes": []})['nodes'] 88 - for label in card_labels: 89 - if config['settings']['points_label'] in label['name']: 90 - card_points += int(label['name'][len(config['settings']['points_label']):]) 87 + points_label = config['settings']['points_label'] 88 + if not points_label: 89 + card_points = 1 90 + else: 91 + card_labels = card_data.get('labels', {"nodes": []})['nodes'] 92 + for label in card_labels: 93 + if points_label in label['name']: 94 + card_points += int(label['name'][len(points_label):]) 91 95 return card_points
+31
src/github_projects_burndown_chart/gh/queries/OrganizationProject.graphql
··· 1 + query OrganizationProject($organization_name: String!, $project_number: Int!, $column_count: Int!, $max_cards_per_column_count: Int!, $labels_per_issue_count: Int!) { 2 + organization(login: $organization_name) { 3 + project(number: $project_number) { 4 + name 5 + columns(first: $column_count) { 6 + nodes { 7 + name 8 + cards(first: $max_cards_per_column_count) { 9 + nodes { 10 + id 11 + note 12 + state 13 + content { 14 + ... on Issue { 15 + title 16 + createdAt 17 + closedAt 18 + labels(first: $labels_per_issue_count) { 19 + nodes { 20 + name 21 + } 22 + } 23 + } 24 + } 25 + } 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }
+3
src/github_projects_burndown_chart/gh/queries/__init__.py
··· 8 8 9 9 with open(os.path.join(__location__, 'RepositoryProject.graphql')) as query: 10 10 RepositoryProject = query.read() 11 + 12 + with open(os.path.join(__location__, 'OrganizationProject.graphql')) as query: 13 + OrganizationProject = query.read()