diff --git a/.gitignore b/.gitignore index 6601431c89..e225f81b84 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,5 @@ augur/plugins/ *.config.* *.cfg.* !sample.config.json +!vagrant.config.json frontend/public/*.map diff --git a/README.md b/README.md index a6d6d891df..0d3056c152 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,14 @@ Functionally, Augur is a prototyped implementation of the Linux Foundation's [CH ### Vagrant **The quickest way to get started working on Augur is by using [Vagrant](https://www.vagrantup.com/)** to spin up a virtual machine (VM) that comes with Augur already installed. We'll do all the work of setting up and installing dependencies, leaving you free to jump right into contributing something awesome. -*Caveat: if you’re a super nerd who likes to have total control over your development environment, there’s a local installation link at the bottom of this page. For the rest of you, Vagrant is the way to go, especially if you've had trouble getting all the dependcies installed locally, are not comfortable installing them yourself, or are using an OS for which we don't currently support local installation. We currently only support local installation for macOS and most flavors of Linux.* +*Caveat: if you’re a super nerd who likes to have total control over your development environment, there’s a local installation link at the bottom of this page. For the rest of you, Vagrant is the way to go, especially if you've had trouble getting all the dependcies installed locally, are not comfortable installing them yourself, or are using an OS for which we don't currently support local installation. **We currently only support local installation for macOS and most flavors of Linux**.* + +Windows installation instructions using Vagrant can be found [here](docs/python/source/windows-install.md). #### Dependencies - [Vagrant](https://www.vagrantup.com/) - [Virtualbox](https://www.virtualbox.org/) -- Local installation of Augur - [GitHub Access Token](https://github.com/settings/tokens) (no write access required) To get started, you'll need a VM provider- we currently only support [Virtualbox](https://www.virtualbox.org/). You'll also need to install [Vagrant](https://www.vagrantup.com/downloads.html). To begin, clone the repository, enter the root directory, and run `make vagrant`. @@ -43,13 +44,17 @@ After this process has completed, the VM should be up and running. You'll then b # inside the vagrant VM sudo su - cd /vagrant/augur + +# due to vagrant weirdness, we have to manually install the python packages +sudo $AUGUR_PIP install --upgrade . ``` -Once you've reached this point, you're ready to start developing! To start the backend, run `augur`. After you run the this command for the first time, a default configuration file called `augur.config.json` will automatically be generated. Reference the sample configuration file (`sample.config.json`) on how to set up the server, development, and cache configurations, as well as the plugin connections. +Augur will automatically create a config file called ``augur.config.json``. Add your GitHub API key to this file under the section ``GitHub``. At this point, you're ready to start developing! +Run the backend with ``augur``, or the frontend and backend together with ``make dev``. ```bash -augur # to create an augur.config.json -# send SIGINT to the VM to stop augur so you can edit the config (usually this is CTRL+C) +# to start both the backend and the frontend +make dev ``` If you're interested in adding a new plugin, data source, or metric, check out the [backend development guide](http://augur.augurlabs.io/static/docs/dev-guide/3-backend.html). If new visualizations are more your speed, you'll want the [frontend development guide](http://augur.augurlabs.io/static/docs/dev-guide/4-frontend.html). @@ -65,12 +70,12 @@ make vagrant sudo su - cd /vagrant/augur -# you might to install the Python dependencies again- vagrant can be weird -pip3 install -e . - -augur # to create an augur.config.json +# due to vagrant weirdness, we have to manually install the python packages +sudo $AUGUR_PIP install --upgrade . # add your GitHub personal access token to augur.config.json + +make dev # full steam ahead! ``` diff --git a/augur/datasources/downloads/routes.py b/augur/datasources/downloads/routes.py index 8961d11028..050566a419 100644 --- a/augur/datasources/downloads/routes.py +++ b/augur/datasources/downloads/routes.py @@ -35,7 +35,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/downloads Downloads @apiName downloads @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GitHub API @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository diff --git a/augur/datasources/facade/facade.py b/augur/datasources/facade/facade.py index 9ea6710e29..1396bebd1d 100644 --- a/augur/datasources/facade/facade.py +++ b/augur/datasources/facade/facade.py @@ -144,3 +144,578 @@ def commits_by_week(self, repo_url): """) results = pd.read_sql(commitsByMonthSQL, self.db, params={"repourl": '%{}%'.format(repo_url)}) return results + + + # cd - code + # rg - repo group + # tp - time period (fixed time period) + # interval + # ranked - ordered top to bottom + # commits + # loc - lines of code + # rep - repo + # ua - unaffiliated + + @annotate(tag='cd-rg-newrep-ranked-commits') + def cd_rg_newrep_ranked_commits(self, repo_url, calendar_year=None, repo_group=None): + """ + For each repository in a collection of repositories being managed, each REPO that first appears in the parameterized + calendar year (a new repo in that year), + show all commits for that year (total for year by repo). + Result ranked from highest number of commits to lowest by default. + :param repo_url: the repository's URL + :param calendar_year: the calendar year a repo is created in to be considered "new" + :param repo_group: the group of repositories to analyze + """ + if calendar_year == None: + calendar_year = 2018 + + if repo_group == None: + repo_group = 'facade_project' + + cdRgNewrepRankedCommitsSQL = None + + if repo_group == 'facade_project': + cdRgNewrepRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches, repos.name + FROM repo_annual_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + and YEAR(repos.added) = :calendar_year + LIMIT 1) + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + else: + cdRgNewrepRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches, projects.name + FROM repo_annual_cache, projects, repos + where projects.name = :repo_group + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + and YEAR(repos.added) = :calendar_year + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + results = pd.read_sql(cdRgNewrepRankedCommitsSQL, self.db, params={"repourl": '%{}%'.format(repo_url), "repo_group": repo_group, "calendar_year": calendar_year}) + return results + + @annotate(tag='cd-rg-newrep-ranked-loc') + def cd_rg_newrep_ranked_loc(self, repo_url, calendar_year=None, repo_group=None): + """ + For each repository in a collection of repositories being managed, each REPO that first appears in the parameterized + calendar year (a new repo in that year), + show all lines of code for that year (total for year by repo). Result ranked from highest number of commits to lowest by default. + :param repo_url: the repository's URL + :param calendar_year: the calendar year a repo is created in to be considered "new" + :param repo_group: the group of repositories to analyze + """ + + if calendar_year == None: + calendar_year = 2018 + + if repo_group == None: + repo_group = 'facade_project' + + cdRgNewrepRankedLocSQL = None + if repo_group == 'facade_project': + cdRgNewrepRankedLocSQL = s.sql.text(""" + SELECT repos_id, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches, repos.name + FROM repo_annual_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and YEAR(repos.added) = :calendar_year + and repos.projects_id = projects.id + LIMIT 1) + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + else: + cdRgNewrepRankedLocSQL = s.sql.text(""" + SELECT repos_id, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches, projects.name + FROM repo_annual_cache, projects, repos + where projects.name = :repo_group + and repos.projects_id = projects.id + and YEAR(repos.added) = :calendar_year + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + + results = pd.read_sql(cdRgNewrepRankedLocSQL, self.db, params={"repourl": '%{}%'.format(repo_url), "repo_group": repo_group, "calendar_year": calendar_year}) + return results + + @annotate(tag='cd-rg-tp-ranked-commits') + def cd_rg_tp_ranked_commits(self, repo_url, timeframe=None, repo_group=None): + """ + For each repository in a collection of repositories being managed, each REPO's total commits during the current Month, + Year or Week. Result ranked from highest number of commits to lowest by default. + :param repo_url: the repository's URL + :param timeframe: All, year, month, or week. Contribution data from the timeframe that the current date is within will be considered + :param repo_group: the group of repositories to analyze + """ + if repo_group == None: + repo_group = 'facade_project' + + if timeframe == None: + timeframe = 'all' + + cdRgTpRankedCommitsSQL = None + + if repo_group == 'facade_project': + if timeframe == "all": + cdRgTpRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + LIMIT 1) + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == "year": + cdRgTpRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + and YEAR(repos.added) = YEAR(CURDATE()) + LIMIT 1) + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == 'month': + cdRgTpRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_monthly_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_monthly_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + and YEAR(repos.added) = YEAR(CURDATE()) + and MONTH(repos.added) = MONTH(CURDATE()) + LIMIT 1) + and repo_monthly_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + else: + if timeframe == "all": + cdRgTpRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = :repo_group + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == "year": + cdRgTpRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = :repo_group + and repo_annual_cache.repos_id = repos.id + and YEAR(repos.added) = YEAR(CURDATE()) + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == 'month': + cdRgTpRankedCommitsSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_monthly_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_monthly_cache, projects, repos + where projects.name = :repo_group + and repo_monthly_cache.repos_id = repos.id + and YEAR(repos.added) = YEAR(CURDATE()) + and MONTH(repos.added) = MONTH(CURDATE()) + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + results = pd.read_sql(cdRgTpRankedCommitsSQL, self.db, params={"repourl": '%{}%'.format(repo_url), "repo_group": repo_group}) + return results + + @annotate(tag='cd-rg-tp-ranked-loc') + def cd_rg_tp_ranked_loc(self, repo_url, timeframe=None, repo_group=None): + """ + For each repository in a collection of repositories being managed, each REPO's total commits during the current Month, + Year or Week. Result ranked from highest number of LOC to lowest by default. + :param repo_url: the repository's URL + :param timeframe: All, year, month, or week. Contribution data from the timeframe that the current date is within will be considered + :param repo_group: the group of repositories to analyze + """ + + if repo_group == None: + repo_group = 'facade_project' + + if timeframe == None: + timeframe = 'all' + + cdRgTpRankedLocSQL = None + + if repo_group == 'facade_project': + if timeframe == "all": + cdRgTpRankedLocSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + LIMIT 1) + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == "year": + cdRgTpRankedLocSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + and YEAR(repos.added) = YEAR(CURDATE()) + LIMIT 1) + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == 'month': + cdRgTpRankedLocSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_monthly_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_monthly_cache, projects, repos + where projects.name = (SELECT projects.name FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + and YEAR(repos.added) = YEAR(CURDATE()) + and MONTH(repos.added) = MONTH(CURDATE()) + LIMIT 1) + and repo_monthly_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + else: + if timeframe == "all": + cdRgTpRankedLocSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = :repo_group + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == "year": + cdRgTpRankedLocSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_annual_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_annual_cache, projects, repos + where projects.name = :repo_group + and repo_annual_cache.repos_id = repos.id + and repos.projects_id = projects.id + and YEAR(repos.added) = YEAR(CURDATE()) + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + elif timeframe == 'month': + cdRgTpRankedLocSQL = s.sql.text(""" + SELECT repos_id, repos.name as name, sum(cast(repo_monthly_cache.added as signed) - cast(removed as signed) - cast(whitespace as signed)) as net, patches + FROM repo_monthly_cache, projects, repos + where projects.name = :repo_group + and repo_monthly_cache.repos_id = repos.id + and repos.projects_id = projects.id + and YEAR(repos.added) = YEAR(CURDATE()) + and MONTH(repos.added) = MONTH(CURDATE()) + group by repos_id + ORDER BY net desc + LIMIT 10 + """) + results = pd.read_sql(cdRgTpRankedLocSQL, self.db, params={"repourl": '%{}%'.format(repo_url), "repo_group": repo_group}) + return results + + @annotate(tag='cd-rep-tp-interval-loc-commits') + def cd_rep_tp_interval_loc_commits(self, repo_url, calendar_year=None, interval=None): + """ + For a single repository, all the commits and lines of code occuring for the specified year, grouped by the specified interval (week or month) + + :param repo_url: the repository's URL + :param calendar_year: the calendar year a repo is created in to be considered "new" + :param interval: Month or week. The periodocity of which to examine data within the given calendar_year + """ + + if calendar_year == None: + calendar_year = 2018 + + if interval == None: + interval = 'month' + + cdRepTpIntervalLocCommitsSQL = None + + if interval == "month": + cdRepTpIntervalLocCommitsSQL = s.sql.text(""" + SELECT sum(cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, + sum(IFNULL(added, 0)) as added, sum(IFNULL(removed, 0)) as removed, sum(IFNULL(whitespace, 0)) as whitespace, + IFNULL(patches, 0) as commits, a.month, IFNULL(year, :calendar_year) as year + FROM (select month from repo_monthly_cache group by month) a + LEFT JOIN (SELECT name, repo_monthly_cache.added, removed, whitespace, patches, month, IFNULL(year, :calendar_year) as year + FROM repo_monthly_cache, repos + WHERE repos_id = (SELECT id FROM repos WHERE git LIKE :repourl LIMIT 1) + AND year = :calendar_year + AND repos.id = repos_id + GROUP BY month) b + ON a.month = b.month + GROUP BY month + """) + elif interval == "week": + cdRepTpIntervalLocCommitsSQL = s.sql.text(""" + SELECT sum(cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, + sum(IFNULL(added, 0)) as added, sum(IFNULL(removed, 0)) as removed, sum(IFNULL(whitespace, 0)) as whitespace, + IFNULL(patches, 0) as commits, a.week, IFNULL(year, :calendar_year) as year + FROM (select week from repo_weekly_cache group by week) a + LEFT JOIN (SELECT name, repo_weekly_cache.added, removed, whitespace, patches, week, IFNULL(year, :calendar_year) as year + FROM repo_weekly_cache, repos + WHERE repos_id = (SELECT id FROM repos WHERE git LIKE :repourl LIMIT 1) + AND year = :calendar_year + AND repos.id = repos_id + GROUP BY week) b + ON a.week = b.week + GROUP BY week + """) + + results = pd.read_sql(cdRepTpIntervalLocCommitsSQL, self.db, params={"repourl": '%{}%'.format(repo_url), 'calendar_year': calendar_year}) + return results + + @annotate(tag='cd-rep-tp-interval-loc-commits-ua') + def cd_rep_tp_interval_loc_commits_ua(self, repo_url, calendar_year=None, interval=None, repo_group=None): + """ + For a single repository, all the commits and lines of code occuring for the specified year, grouped by the specified interval + (week or month) and by the affiliation of individuals and domains that are not mapped as "inside" within the repositories gitdm file. + "Unknown" is, in this case, interpreted as "outside" + + :param repo_url: the repository's URL + :param calendar_year: the calendar year a repo is created in to be considered "new" + :param interval: Month or week. The periodocity of which to examine data within the given calendar_year + :param repo_group: the group of repositories to analyze + """ + + if calendar_year == None: + calendar_year = 2018 + + if interval == None: + interval = 'month' + + if repo_group == None: + repo_group = 'facade_project' + + cdRepTpIntervalLocCommitsUaSQL = None + + if repo_group == 'facade_project': + if interval == "month": + cdRepTpIntervalLocCommitsUaSQL = s.sql.text(""" + + SELECT added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.month, affiliation + FROM (SELECT month FROM repo_monthly_cache GROUP BY month) a + LEFT JOIN + ( + SELECT SUM(repo_monthly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, month, SUM(patches) as patches, repo_monthly_cache.`affiliation` as affiliation + FROM repo_monthly_cache, repos, projects + WHERE repo_monthly_cache.repos_id = repos.id + AND repos.projects_id = (SELECT projects.id FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + LIMIT 1) + AND projects.id = repos.projects_id + AND repo_monthly_cache.`affiliation` <> projects.name + AND year = :calendar_year + GROUP BY month, affiliation + ) b ON a.month = b.month + ORDER BY month + """) + elif interval == "week": + cdRepTpIntervalLocCommitsUaSQL = s.sql.text(""" + SELECT added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.week, affiliation + FROM (SELECT week FROM repo_weekly_cache GROUP BY week) a + LEFT JOIN + ( + SELECT SUM(repo_weekly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, week, SUM(patches) as patches, repo_weekly_cache.`affiliation` as affiliation + FROM repo_weekly_cache, repos, projects + WHERE repo_weekly_cache.repos_id = repos.id + AND repos.projects_id = (SELECT projects.id FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + LIMIT 1) + AND projects.id = repos.projects_id + AND repo_weekly_cache.`affiliation` <> projects.name + AND year = :calendar_year + GROUP BY week, affiliation + ) b ON a.week = b.week + ORDER BY week + """) + else: + if interval == "month": + cdRepTpIntervalLocCommitsUaSQL = s.sql.text(""" + SELECT added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.month, affiliation + FROM (SELECT month FROM repo_monthly_cache GROUP BY month) a + LEFT JOIN + ( + SELECT SUM(repo_monthly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, month, SUM(patches) as patches, repo_monthly_cache.`affiliation` as affiliation + FROM repo_monthly_cache, repos, projects + WHERE repo_monthly_cache.repos_id = repos.id + AND repos.projects_id = :repo_group + AND projects.id = repos.projects_id + AND repo_monthly_cache.`affiliation` <> projects.name + AND year = :calendar_year + GROUP BY month, affiliation + ) b ON a.month = b.month + ORDER BY month + """) + elif interval == "week": + cdRepTpIntervalLocCommitsUaSQL = s.sql.text(""" + SELECT added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.week, affiliation + FROM (SELECT week FROM repo_weekly_cache GROUP BY week) a + LEFT JOIN + ( + SELECT SUM(repo_weekly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, week, SUM(patches) as patches, repo_weekly_cache.`affiliation` as affiliation + FROM repo_weekly_cache, repos, projects + WHERE repo_weekly_cache.repos_id = repos.id + AND repos.projects_id = :repo_group + AND projects.id = repos.projects_id + AND repo_weekly_cache.`affiliation` <> projects.name + AND year = :calendar_year + GROUP BY month, affiliation + ) b ON a.week = b.week + ORDER BY week + """) + results = pd.read_sql(cdRepTpIntervalLocCommitsUaSQL, self.db, params={"repourl": '%{}%'.format(repo_url), "repo_group": repo_group, 'calendar_year': calendar_year}) + return results + + @annotate(tag='cd-rg-tp-interval-loc-commits') + def cd_rg_tp_interval_loc_commits(self, repo_url, calendar_year=None, interval=None, repo_group=None): + """ + For each repository in a collection of repositories, all the commits and lines of code occuring for the specified year, + grouped by repository and the specified interval (week or month). Results ordered by repo. + + :param repo_url: the repository's URL + :param calendar_year: the calendar year a repo is created in to be considered "new" + :param interval: Month or week. The periodocity of which to examine data within the given calendar_year + :param repo_group: the group of repositories to analyze + """ + + if calendar_year == None: + calendar_year = 2019 + + if interval == None: + interval = 'month' + + if repo_group == None: + repo_group = 'facade_project' + + cdRgTpIntervalLocCommitsSQL = None + + if repo_group == 'facade_project': + if interval == "month": + cdRgTpIntervalLocCommitsSQL = s.sql.text(""" + SELECT name, added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.month + FROM (SELECT month FROM repo_monthly_cache GROUP BY month) a + LEFT JOIN + ( + SELECT repos.name, SUM(repo_monthly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, month, SUM(patches) as patches + FROM repo_monthly_cache, repos, projects + WHERE repo_monthly_cache.repos_id = repos.id + AND repos.projects_id = (SELECT projects.id FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + LIMIT 1) + AND projects.id = repos.projects_id + AND repos_id = repos.id + AND year = :calendar_year + GROUP BY month, repos.name + ) b ON a.month = b.month + ORDER BY month, name + """) + elif interval == "week": + cdRgTpIntervalLocCommitsSQL = s.sql.text(""" + SELECT name, added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.week + FROM (SELECT week FROM repo_weekly_cache GROUP BY week) a + LEFT JOIN + ( + SELECT repos.name, SUM(repo_weekly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, week, SUM(patches) as patches + FROM repo_weekly_cache, repos, projects + WHERE repo_weekly_cache.repos_id = repos.id + AND repos.projects_id = (SELECT projects.id FROM repos, projects + WHERE git LIKE :repourl + and repos.projects_id = projects.id + LIMIT 1) + AND projects.id = repos.projects_id + AND repos_id = repos.id + AND year = :calendar_year + GROUP BY week, repos.name + ) b ON a.week = b.week + ORDER BY week, name + """) + else: + if interval == "month": + cdRgTpIntervalLocCommitsSQL = s.sql.text(""" + SELECT name, added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.month + FROM (SELECT month FROM repo_monthly_cache GROUP BY month) a + LEFT JOIN + ( + SELECT repos.name, SUM(repo_monthly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, month, SUM(patches) as patches + FROM repo_monthly_cache, repos, projects + WHERE repo_monthly_cache.repos_id = repos.id + AND repos.projects_id = :repo_group + AND projects.id = repos.projects_id + AND repos_id = repos.id + AND year = :calendar_year + GROUP BY month, repos.name + ) b ON a.month = b.month + ORDER BY month, name + """) + elif interval == "week": + cdRgTpIntervalLocCommitsSQL = s.sql.text(""" + SELECT name, added, whitespace, removed, (cast(IFNULL(added, 0) as signed) - cast(IFNULL(removed, 0) as signed) - cast(IFNULL(whitespace, 0) as signed)) as net_lines_minus_whitespace, patches, a.week + FROM (SELECT week FROM repo_weekly_cache GROUP BY week) a + LEFT JOIN + ( + SELECT repos.name, SUM(repo_weekly_cache.added) AS added, SUM(whitespace) as whitespace, SUM(removed) as removed, week, SUM(patches) as patches + FROM repo_weekly_cache, repos, projects + WHERE repo_weekly_cache.repos_id = repos.id + AND repos.projects_id = :repo_group + AND projects.id = repos.projects_id + AND repos_id = repos.id + AND year = :calendar_year + GROUP BY week, repos.name + ) b ON a.week = b.week + ORDER BY week, name + """) + results = pd.read_sql(cdRgTpIntervalLocCommitsSQL, self.db, params={"repourl": '%{}%'.format(repo_url), "calendar_year": calendar_year, "repo_group": repo_group}) + return results \ No newline at end of file diff --git a/augur/datasources/facade/routes.py b/augur/datasources/facade/routes.py index 540d6dbecb..6b7d4f54d0 100644 --- a/augur/datasources/facade/routes.py +++ b/augur/datasources/facade/routes.py @@ -3,7 +3,7 @@ Creates routes for the facade data source plugin """ -from flask import Response +from flask import Response, request def create_routes(server): @@ -45,7 +45,7 @@ def facade_downloaded_repos(): #TODO: make this name automatic - wrapper? @api {get} /git/lines_changed/:facade_repo_url Lines Changed by Author @apiName lines-changed-by-author @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: Git Repository @apiParam {String} facade_repo_url URL of the GitHub repository as it appears in the Facade @@ -71,7 +71,7 @@ def facade_downloaded_repos(): #TODO: make this name automatic - wrapper? @api {get} /git/lines_changed_by_week/:facade_repo_url Lines Changed by Week @apiName lines-changed-by-week @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: Git Repository @apiParam {String} facade_repo_url URL of the GitHub repository as it appears in the Facade @@ -91,7 +91,7 @@ def facade_downloaded_repos(): #TODO: make this name automatic - wrapper? @api {get} /git/lines_changed_by_month/:facade_repo_url Lines Changed by Month @apiName lines-changed-by-month @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: Git Repository @apiParam {String} facade_repo_url URL of the GitHub repository as it appears in the Facade @@ -123,7 +123,7 @@ def facade_downloaded_repos(): #TODO: make this name automatic - wrapper? @api {get} /git/commits_by_week/:facade_repo_url Commits By Week @apiName commits-by-week @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: Git Repository @apiParam {String} facade_repo_url URL of the GitHub repository as it appears in the Facade @@ -146,3 +146,106 @@ def facade_downloaded_repos(): #TODO: make this name automatic - wrapper? ] """ server.addGitMetric(facade.commits_by_week, 'commits_by_week') + + + @server.app.route('/{}/git/cd_rg_tp_ranked_loc'.format(server.api_version)) + def cd_rg_tp_ranked_loc(): + + repo_url_base = request.args.get('repo_url_base') + + timeframe = request.args.get('timeframe') + repo_group = request.args.get('repo_group') + + data = server.transform(facade.cd_rg_tp_ranked_loc, args=([]), repo_url_base=repo_url_base, kwargs=({'timeframe': timeframe, 'repo_group': repo_group})) + + return Response(response=data, + status=200, + mimetype="application/json") + + # server.addGitMetric(facade.top_repos_commits, 'top_repos_commits') + @server.app.route('/{}/git/cd_rg_tp_ranked_commits'.format(server.api_version)) + def cd_rg_tp_ranked_commits(): + + repo_url_base = request.args.get('repo_url_base') + + timeframe = request.args.get('timeframe') + repo_group = request.args.get('repo_group') + + data = server.transform(facade.cd_rg_tp_ranked_commits, args=([]), repo_url_base=repo_url_base, kwargs=({'timeframe': timeframe, 'repo_group': repo_group})) + + return Response(response=data, + status=200, + mimetype="application/json") + + @server.app.route('/{}/git/cd_rg_newrep_ranked_loc'.format(server.api_version)) + def cd_rg_newrep_ranked_loc(): + + repo_url_base = request.args.get('repo_url_base') + + calendar_year = request.args.get('calendar_year') + repo_group = request.args.get('repo_group') + + data = server.transform(facade.cd_rg_newrep_ranked_loc, args=([]), repo_url_base=repo_url_base, kwargs=({'calendar_year': calendar_year, 'repo_group': repo_group})) + + return Response(response=data, + status=200, + mimetype="application/json") + + # server.addGitMetric(facade.cd_rg_newrep_ranked_commits, 'top_new_repos_commits') + @server.app.route('/{}/git/cd_rg_newrep_ranked_commits'.format(server.api_version)) + def cd_rg_newrep_ranked_commits(): + + repo_url_base = request.args.get('repo_url_base') + + calendar_year = request.args.get('calendar_year') + repo_group = request.args.get('repo_group') + + data = server.transform(facade.cd_rg_newrep_ranked_commits, args=([]), repo_url_base=repo_url_base, kwargs=({'calendar_year': calendar_year, 'repo_group': repo_group})) + + return Response(response=data, + status=200, + mimetype="application/json") + + @server.app.route('/{}/git/cd_rep_tp_interval_loc_commits'.format(server.api_version)) + def cd_rep_tp_interval_loc_commits(): + + repo_url_base = request.args.get('repo_url_base') + + calendar_year = request.args.get('calendar_year') + interval = request.args.get('interval') + + data = server.transform(facade.cd_rep_tp_interval_loc_commits, args=([]), repo_url_base=repo_url_base, kwargs=({'calendar_year': calendar_year, 'interval': interval})) + + return Response(response=data, + status=200, + mimetype="application/json") + + @server.app.route('/{}/git/cd_rep_tp_interval_loc_commits_ua'.format(server.api_version)) + def cd_rep_tp_interval_loc_commits_ua(): + + repo_url_base = request.args.get('repo_url_base') + + calendar_year = request.args.get('calendar_year') + interval = request.args.get('interval') + + data = server.transform(facade.cd_rep_tp_interval_loc_commits_ua, args=([]), repo_url_base=repo_url_base, kwargs=({'calendar_year': calendar_year, 'interval': interval})) + + return Response(response=data, + status=200, + mimetype="application/json") + + @server.app.route('/{}/git/cd_rg_tp_interval_loc_commits'.format(server.api_version)) + # @server.app.route('/{}/git////loc_commits'.format(server.api_version)) + def cd_rg_tp_interval_loc_commits(): + + repo_url_base = request.args.get('repo_url_base') + + calendar_year = request.args.get('calendar_year') + interval = request.args.get('interval') + repo_group = request.args.get('repo_group') + + data = server.transform(facade.cd_rg_tp_interval_loc_commits, args=([]), repo_url_base=repo_url_base, kwargs=({'calendar_year': calendar_year, 'interval': interval, 'repo_group': repo_group})) + + return Response(response=data, + status=200, + mimetype="application/json") \ No newline at end of file diff --git a/augur/datasources/ghtorrent/routes.py b/augur/datasources/ghtorrent/routes.py index 2d764a81ae..14e74db3b3 100644 --- a/augur/datasources/ghtorrent/routes.py +++ b/augur/datasources/ghtorrent/routes.py @@ -21,7 +21,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/issues/closed Closed Issues @apiName closed-issues @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -45,7 +45,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/commits?group_by=:group_by Code Commits @apiName code-commits @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -69,7 +69,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/code_review_iteration Code Review Iteration @apiName code-review-iteration @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -92,7 +92,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/contribution_acceptance Contribution Acceptance @apiName contribution-acceptance @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -115,7 +115,7 @@ def create_routes(server): @api {get} /:owner/:repo/contributing_github_organizations Contributing Github Organizations @apiName contributing-github-organizations @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -152,7 +152,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/issues/response_time First Response To Issue Duration @apiName first-response-to-issue-duration @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -182,7 +182,7 @@ def create_routes(server): @apiName forks @apiGroup Growth-Maturity-Decline @apiParam {String} group_by (Default to week) Allows for results to be grouped by day, week, month, or year - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -205,7 +205,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/pulls/maintainer_response_time Maintainer Response to Merge Request Duration @apiName maintainer-response-to-merge-request-duration @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -228,7 +228,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/pulls/new_contributing_github_organizations New Contributing Github Organizations @apiName new-github-contributing-organizations @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -251,7 +251,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/issues Open Issues @apiName open-issues @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {string} group_by (default to week) allows for results to be grouped by day, week, month, or year @apiParam {string} owner username of the owner of the github repository @@ -275,7 +275,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/pulls/comments?group_by=:group_by Pull Request Comments @apiName pull-request-comments @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -299,7 +299,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/pulls Pull Requests Open @apiName pull-requests-open @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -334,7 +334,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/issue_comments Issue Comments @apiName issue-comments @apiGroup Activity - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -357,7 +357,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/pulls/made_closed Pull Requests Made/Closed @apiName pull-requests-made-closed @apiGroup Activity - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -380,7 +380,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/watchers Watchers @apiName watchers @apiGroup Activity - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -407,7 +407,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/commits100 Commits100 @apiName commits100 @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -430,7 +430,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/commits/comments Commit Comments @apiName commit-comments @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -454,7 +454,7 @@ def create_routes(server): @api {get} /:owner/:repo/committer_locations Committer Locations @apiName committer-locations @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -479,7 +479,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/total_committers Total Committers @apiName total-committers @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -502,7 +502,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/issues/activity Issue Activity @apiName issue-activity @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -558,7 +558,7 @@ def create_routes(server): @apiDeprecated This endpoint was removed. Please use (#Experimental:community-engagement) @apiName pull-request-acceptance-rate @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -581,7 +581,7 @@ def create_routes(server): @api {get} /:owner/:repo/community_age Community Age @apiName community-age @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -606,7 +606,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/community_engagement Community Engagement @apiName community-engagement @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -661,7 +661,7 @@ def create_routes(server): @api {get} /:owner/:repo/contributors Total Contributions by User @apiName contributors @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -696,7 +696,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/contributions Contributions @apiName contributions @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -742,7 +742,7 @@ def contributions(owner, repo): @api {get} /:owner/:repo/project_age Project Age @apiName project-age @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -762,7 +762,7 @@ def contributions(owner, repo): @api {get} /:owner/:repo/timeseries/fakes Fakes @apiName fakes @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -782,10 +782,10 @@ def contributions(owner, repo): server.addTimeseries(ghtorrent.fakes, 'fakes') """ - @api {get} /:owner/:repo/timeseries/new_watchers Fakes + @api {get} /:owner/:repo/timeseries/new_watchers New Watchers @apiName new_watchers @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GHTorrent @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository diff --git a/augur/datasources/githubapi/routes.py b/augur/datasources/githubapi/routes.py index e1cd693bf4..1c15f9feb9 100644 --- a/augur/datasources/githubapi/routes.py +++ b/augur/datasources/githubapi/routes.py @@ -19,7 +19,7 @@ def create_routes(server): @api {get} /:owner/:repo/lines_changed Lines of Code Changed @apiName lines-of-code-changed @apiGroup Growth-Maturity-Decline - @apiDescription CHAOSS Metric Definition + @apiDescription CHAOSS Metric Definition. Source: GitHub API @apiGroup Growth-Maturity-Decline @apiParam {String} owner Username of the owner of the GitHub repository @@ -59,7 +59,7 @@ def create_routes(server): @api {get} /:owner/:repo/bus_factor Bus Factor @apiName bus-factor @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GitHub API @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -79,7 +79,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/tags/major Major Tags @apiName major-tags @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GitHub API @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -102,7 +102,7 @@ def create_routes(server): @api {get} /:owner/:repo/timeseries/tags/major Tages @apiName tags @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: GitHub API @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository diff --git a/augur/datasources/librariesio/routes.py b/augur/datasources/librariesio/routes.py index 7e1ed98181..7264f418da 100644 --- a/augur/datasources/librariesio/routes.py +++ b/augur/datasources/librariesio/routes.py @@ -35,7 +35,7 @@ def create_routes(server): @api {get} /:owner/:repo/dependencies Dependencies @apiName dependencies @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: LibrariesIO @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -126,7 +126,7 @@ def create_routes(server): @api {get} /:owner/:repo/dependency_stats Dependency Stats @apiName dependency-stats @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: LibrariesIO @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository @@ -146,7 +146,7 @@ def create_routes(server): @api {get} /:owner/:repo/dependents Dependents @apiName dependents @apiGroup Experimental - @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. + @apiDescription This is an Augur-specific metric. We are currently working to define these more formally. Source: LibrariesIO @apiParam {String} owner Username of the owner of the GitHub repository @apiParam {String} repo Name of the GitHub repository diff --git a/augur/metadata.py b/augur/metadata.py index e4e49b3bb8..8969d49666 100644 --- a/augur/metadata.py +++ b/augur/metadata.py @@ -1 +1 @@ -__version__ = '0.9.0' +__version__ = '0.9.1' diff --git a/docs/python/build/doctrees/dev-guide/2-install.doctree b/docs/python/build/doctrees/dev-guide/2-install.doctree index 8564bea46c..41724db63b 100644 Binary files a/docs/python/build/doctrees/dev-guide/2-install.doctree and b/docs/python/build/doctrees/dev-guide/2-install.doctree differ diff --git a/docs/python/build/doctrees/dev-guide/3-backend.doctree b/docs/python/build/doctrees/dev-guide/3-backend.doctree index 93ac3b2d6c..25ec82f2be 100644 Binary files a/docs/python/build/doctrees/dev-guide/3-backend.doctree and b/docs/python/build/doctrees/dev-guide/3-backend.doctree differ diff --git a/docs/python/build/doctrees/environment.pickle b/docs/python/build/doctrees/environment.pickle index 6d9e29bee8..93e3ad32ed 100755 Binary files a/docs/python/build/doctrees/environment.pickle and b/docs/python/build/doctrees/environment.pickle differ diff --git a/docs/python/build/doctrees/index.doctree b/docs/python/build/doctrees/index.doctree index 8ed968afd7..0f0f3454c1 100755 Binary files a/docs/python/build/doctrees/index.doctree and b/docs/python/build/doctrees/index.doctree differ diff --git a/docs/python/build/doctrees/windows-install.doctree b/docs/python/build/doctrees/windows-install.doctree new file mode 100644 index 0000000000..fbc7adcd52 Binary files /dev/null and b/docs/python/build/doctrees/windows-install.doctree differ diff --git a/docs/python/source/index.rst b/docs/python/source/index.rst index a2b546ebf9..d29216d883 100755 --- a/docs/python/source/index.rst +++ b/docs/python/source/index.rst @@ -15,9 +15,11 @@ Augur is a software suite that provides open source health and sustainability me augurcontext architecture python - dev-guide-toc + dev-guide-toc docker-install + windows-install + deployment ghtorrent-restore use-cases-toc diff --git a/docs/python/source/windows-install.md b/docs/python/source/windows-install.md new file mode 100644 index 0000000000..5325bee752 --- /dev/null +++ b/docs/python/source/windows-install.md @@ -0,0 +1,100 @@ +Windows Installation +==================== + +We currently don\'t support local installation of Augur for Windows. +However, we do provide a Vagrant box which can be used to spin up an +Ubuntu VM with Augur pre-installed. + +Dependencies +------------ + +- [Git + client](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [Vagrant](https://www.vagrantup.com/) +- [Virtualbox](https://www.virtualbox.org/) +- [GitHub Access Token](https://github.com/settings/tokens) (no write + access required) + +To get started, you'll need a VM provider- we currently only support +[Virtualbox](https://www.virtualbox.org/). You'll also need to install +[Vagrant](https://www.vagrantup.com/downloads.html) and a Git client. To +begin, clone the repository using your Git client, enter the root +directory of the repo using Command Prompt, and run `vagrant up`. + +```bash +# on your local machine + +# using your Git client: +git clone https://github.com/chaoss/augur.git + +# using Command Prompt +cd augur +vagrant up +vagrant ssh +``` + +The first time you run this command, Vagrant will need to download the +base box configuration. After that, it will provision the VM and then +install Augur and its dependencies. Note: you'll probably see a fair bit +of errors during this provisioning process as Augur is getting +installed. Don't worry about them, most of them are harmless. +*Probably.* + +After this process has completed, the VM should be up and running. Log +in to the VM `vagrant ssh`. Log in as `root` with `sudo su -` and then +navigate to `/vagrant/augur`. This folder is where you'll be working, as +it's synced with your local version of Augur, meaning you won't have to +worry about losing your changes after you shutdown the VM. You'll also +be able to use your preferred editor. During the provisioning process, +Augur will create a lightweight version of both the +[Facade](facade-oss.org) and [GHTorrent](http://ghtorrent.org/) +datasets, both of which we rely on for a lot of our metrics. You'll need +to provide Augur with a [GitHub Access Token](https://github.com/settings/tokens\) (no write access +required). + +```bash +# inside the vagrant VM +sudo su - +cd /vagrant/augur + +# due to vagrant weirdness, we have to manually install the python packages +sudo $AUGUR_PIP install --upgrade . +``` + +Augur will automatically create a config file called +`augur.config.json`. Add your GitHub API key to this file under the +section `GitHub`. At this point, you\'re ready to start developing! Run +the backend with `augur`, or the frontend and backend together with +`make dev`. + +```bash +make dev +``` + +If you're interested in adding a new plugin, data source, or metric, check out the [backend development guide](http://augur.augurlabs.io/static/docs/dev-guide/3-backend.html). If new visualizations are more your speed, you'll want the [frontend development guide](http://augur.augurlabs.io/static/docs/dev-guide/4-frontend.html\). + +### TL;DR + +```bash +# on your local machine + +# using your Git client: +git clone https://github.com/chaoss/augur.git + +# using Command Prompt +cd augur +vagrant up +vagrant ssh + +# inside the vagrant VM +sudo su - +cd /vagrant/augur + +# due to vagrant weirdness, we have to manually install the python packages +sudo $AUGUR_PIP install --upgrade . + +# add your GitHub personal access token to augur.config.json + +make dev +# full steam ahead! +``` diff --git a/docs/python/source/windows-install.rst b/docs/python/source/windows-install.rst new file mode 100644 index 0000000000..7356401d9d --- /dev/null +++ b/docs/python/source/windows-install.rst @@ -0,0 +1,101 @@ +Windows Installation +========================== + +We currently don't support local installation of Augur for Windows. However, we do provide a Vagrant box which can be used to spin up an Ubuntu VM with Augur pre-installed. + +Dependencies +^^^^^^^^^^^^ + +- `Git client `__ +- `Vagrant `__ +- `Virtualbox `__ +- `GitHub Access Token `__ (no + write access required) + +To get started, you’ll need a VM provider- we currently only support +`Virtualbox `__. You’ll also need to +install `Vagrant `__ and a Git client. To +begin, clone the repository using your Git client, enter the root directory of the repo using Command Prompt, and run +``vagrant up``. + +.. code:: bash + + # on your local machine + + # using your Git client: + git clone https://github.com/chaoss/augur.git + + # using Command Prompt + cd augur + vagrant up + vagrant ssh + +The first time you run this command, Vagrant will need to download the +base box configuration. After that, it will provision the VM and then +install Augur and its dependencies. Note: you’ll probably see a fair bit +of errors during this provisioning process as Augur is getting +installed. Don’t worry about them, most of them are harmless. +*Probably.* + +After this process has completed, the VM should be up and running. Log in to the VM ``vagrant ssh``. Log +in as ``root`` with ``sudo su -`` and then navigate to +``/vagrant/augur``. This folder is where you’ll be working, as it’s +synced with your local version of Augur, meaning you won’t have to worry +about losing your changes after you shutdown the VM. You’ll also be able +to use your preferred editor. During the provisioning process, Augur +will create a lightweight version of both the +`Facade `__ and `GHTorrent `__ +datasets, both of which we rely on for a lot of our metrics. You’ll need +to provide Augur with a `GitHub Access +Token `__ (no write access +required). + +.. code:: bash + + # inside the vagrant VM + sudo su - + cd /vagrant/augur + + # due to vagrant weirdness, we have to manually install the python packages + sudo $AUGUR_PIP install --upgrade . + + +Augur will automatically create a config file called ``augur.config.json``. Add your GitHub API key to this file under the section ``GitHub``. At this point, you're ready to start developing! +Run the backend with ``augur``, or the frontend and backend together with ``make dev``. + +.. code:: bash + make dev + +If you’re interested in adding a new plugin, data source, or metric, +check out the `backend development +guide `__. +If new visualizations are more your speed, you’ll want the `frontend +development +guide `__. + +TL;DR +''''' + +.. code:: bash + + # on your local machine + + # using your Git client: + git clone https://github.com/chaoss/augur.git + + # using Command Prompt + cd augur + vagrant up + vagrant ssh + + # inside the vagrant VM + sudo su - + cd /vagrant/augur + + # due to vagrant weirdness, we have to manually install the python packages + sudo $AUGUR_PIP install --upgrade . + + # add your GitHub personal access token to augur.config.json + + make dev + # full steam ahead! diff --git a/frontend/app/Augur.js b/frontend/app/Augur.js index 31e684f4e0..e1367b1825 100644 --- a/frontend/app/Augur.js +++ b/frontend/app/Augur.js @@ -44,9 +44,9 @@ export default function Augur () { gitRepo: null, comparedRepos: [], trailingAverage: 180, - startDate: new Date('1 January 2011'), + startDate: new Date('1 February 2011'), endDate: new Date(), - compare: 'zscore', + compare: 'rolling', showBelowAverage: false, rawWeekly: false, showArea: true, @@ -67,9 +67,7 @@ export default function Augur () { } }, setRepo (state, payload) { - console.log("js",payload) let repo = window.AugurAPI.Repo(payload) - console.log(repo) if (!window.AugurRepos[repo.toString()]) { window.AugurRepos[repo.toString()] = repo } else { @@ -97,24 +95,44 @@ export default function Augur () { state.compare = 'zscore' state.hasState = true let repo = window.AugurAPI.Repo(payload) - if (!window.AugurRepos[repo.toString()]) { - window.AugurRepos[repo.toString()] = repo - } else { - repo = window.AugurRepos[repo.toString()] - } - state.hasState = true - if (repo.owner && repo.name) { - state.comparedRepos.push(repo.toString()) - let title = repo.owner + '/' + repo.name + '- Augur' - state.tab = 'gmd' - let queryString = window.location.search + '&comparedTo[]=' + repo.owner + '+' + repo.name - window.history.pushState(null, title, queryString) - } - if (payload.gitURL) { - let queryString = '&git=' + window.btoa(repo.gitURL) - window.history.pushState(null, 'Git Analysis - Augur', window.location.search + queryString) - state.tab = 'git' - state.gitRepo = repo.gitURL + if(!state.comparedRepos.includes(repo.toString()) && state.baseRepo != repo.toString()){ + if (!window.AugurRepos[repo.toString()]) { + window.AugurRepos[repo.toString()] = repo + } else { + repo = window.AugurRepos[repo.toString()] + } + state.hasState = true + if (repo.owner && repo.name) { + state.comparedRepos.push(repo.toString()) + let title = repo.owner + '/' + repo.name + '- Augur' + } + if (payload.gitURL) { + state.gitRepo = repo.gitURL + } + if (state.comparedRepos.length == 1) { + if (!router.currentRoute.params.comparedrepo) { + + let owner = state.gitRepo ? null : state.baseRepo.substring(0, state.baseRepo.indexOf('/')) + let repo = state.gitRepo ? state.gitRepo : state.baseRepo.slice(state.baseRepo.indexOf('/') + 1) + let name = state.tab + "compare" + router.push({ + name, + params: {owner, repo, comparedowner: payload.owner, comparedrepo: payload.name} + }) + } + } else { + let groupid = (state.gitRepo ? String(state.gitRepo) + '+' : String(state.baseRepo) + "+") + state.comparedRepos.forEach((repo) => { + groupid += (String(repo) + '+') + }) + let name = state.tab + "group" + router.push({ + name, + params: { + groupid + } + }) + } } }, setDates (state, payload) { @@ -155,9 +173,8 @@ export default function Augur () { resetComparedRepos (state) { state.comparedRepos = [] router.push({ - name: 'single', - params: {tab: state.tab, domain: state.domain, owner: state.baseRepo.substring(0, state.baseRepo.indexOf('/')), repo: state.baseRepo.slice(state.baseRepo.indexOf('/') + 1)} - }) + name: state.tab, + params: {owner: state.baseRepo.substring(0, state.baseRepo.indexOf('/')), repo: state.baseRepo.slice(state.baseRepo.indexOf('/') + 1)} }) }, resetBaseRepo (state) { state.baseRepo = null @@ -181,14 +198,67 @@ export default function Augur () { }) AugurApp.store = window.augur - - - - // AugurApp.router = router - // AugurApp.render = h => h(AugurApp) - - // window.AugurApp = new window.Vue(AugurApp).$mount('#app') - + + router.beforeEach((to, from, next) => { + if (to.params.repo || to.params.groupid){ + if (!to.params.groupid && !to.params.comparedrepo){ + AugurApp.store.commit("resetTab") + AugurApp.store.commit('setTab', { + tab: to.name + }) + if (to.params.repo.includes('github') || to.params.repo.split(".").length > 2) { + AugurApp.store.commit('setRepo', { + gitURL: to.params.repo + }) + } else { + AugurApp.store.commit('setRepo', { + githubURL: to.params.owner + '/' + to.params.repo + }) + } + } else if (to.params.comparedrepo && augur.state.comparedRepos.length == 0) { + let tab = to.name + tab = tab.substring(0, tab.length-7) + AugurApp.store.commit("resetTab") + AugurApp.store.commit('setTab', { + tab + }) + AugurApp.store.commit('setRepo', { + githubURL: to.params.owner + '/' + to.params.repo + }) + AugurApp.store.commit('addComparedRepo', { + githubURL: to.params.comparedowner + '/' + to.params.comparedrepo + }) + } else if (to.params.groupid && augur.state.comparedRepos.length == 0){ + AugurApp.store.commit("resetTab") + let tab = to.name + tab = tab.substring(0, tab.length-5) + AugurApp.store.commit('setTab', { + tab + }) + let repos = to.params.groupid.split('+') + if (repos[0].includes('github')) { + AugurApp.store.commit('setRepo', { + gitURL: repos[0] + }) + } else { + AugurApp.store.commit('setRepo', { + githubURL: repos[0] + }) + } + repos.shift() + // repos.pop() + repos.forEach((cmprepo) => { + AugurApp.store.commit('addComparedRepo', { + githubURL: cmprepo + }) + }) + } + } + + next() + }) + + window.AugurApp = new window.Vue({ // components: { AugurApp }, // store: window.augur, diff --git a/frontend/app/AugurAPI.js b/frontend/app/AugurAPI.js index 12fe76e0cf..8bb506bef5 100644 --- a/frontend/app/AugurAPI.js +++ b/frontend/app/AugurAPI.js @@ -104,6 +104,7 @@ export default class AugurAPI { processedData[repo.toString()] = {} }) return this.batch(endpoints).then((data) => { + return new Promise((resolve, reject) => { if (Array.isArray(data)) { data.forEach(response => { @@ -288,7 +289,14 @@ export default class AugurAPI { if (repo.gitURL) { // Other - GitEndpoint(repo, 'changesByAuthor', 'changes_by_author') + GitEndpoint(repo, 'changesByAuthor', 'changes_by_author'), + GitEndpoint(repo, 'cdRepTpIntervalLocCommits', 'cd_rep_tp_interval_loc_commits'), + GitEndpoint(repo, 'cdRgTpRankedLoc', 'cd_rg_tp_ranked_loc'), + GitEndpoint(repo, 'cdRgTpRankedCommits', 'cd_rg_tp_ranked_commits'), + GitEndpoint(repo, 'cdRgNewrepRankedLoc', 'cd_rg_newrep_ranked_loc'), + GitEndpoint(repo, 'cdRgNewrepRankedCommits', 'cd_rg_newrep_ranked_commits') + + } return repo diff --git a/frontend/app/components/AugurCards.vue b/frontend/app/components/AugurCards.vue index ef71bfd032..0a29db8894 100644 --- a/frontend/app/components/AugurCards.vue +++ b/frontend/app/components/AugurCards.vue @@ -2,85 +2,40 @@
- -
- -
- -
-
-

Downloaded Git Repos by Project

- -
- -
+ +
- -
- +
+ + + + + + -
- -
- -
- -
-
-
- -
-
- -
-
- -
-
- - -
-
- -
-
- -
-
diff --git a/frontend/app/components/AugurHeader.vue b/frontend/app/components/AugurHeader.vue index 540c8aa144..298238daab 100644 --- a/frontend/app/components/AugurHeader.vue +++ b/frontend/app/components/AugurHeader.vue @@ -31,7 +31,6 @@ module.exports = { let repo = window.AugurAPI.Repo({ githubURL: e.target.value }) - console.log("check", repo.batch(['codeCommits'], true)) if(!repo.batch(['codeCommits'], true)[0]){ alert("The repo " + repo.githubURL + " could not be found. Please try again.") } else { @@ -40,8 +39,8 @@ module.exports = { githubURL: e.target.value }) this.$router.push({ - name: 'single', - params: {tab: 'gmd', owner: repo.owner, repo: repo.name} + name: 'gmd', + params: {owner: repo.owner, repo: repo.name} }) } diff --git a/frontend/app/components/DownloadedReposCard.vue b/frontend/app/components/DownloadedReposCard.vue index 711d70207e..e8e706113c 100644 --- a/frontend/app/components/DownloadedReposCard.vue +++ b/frontend/app/components/DownloadedReposCard.vue @@ -1,32 +1,33 @@ \ No newline at end of file diff --git a/frontend/app/components/TableView.vue b/frontend/app/components/TableView.vue new file mode 100644 index 0000000000..17d90ed6eb --- /dev/null +++ b/frontend/app/components/TableView.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/frontend/app/components/Tabs.vue b/frontend/app/components/Tabs.vue new file mode 100644 index 0000000000..52ff8b6a46 --- /dev/null +++ b/frontend/app/components/Tabs.vue @@ -0,0 +1,72 @@ + + + \ No newline at end of file diff --git a/frontend/app/components/charts/DirectionalTimeChart.vue b/frontend/app/components/charts/DirectionalTimeChart.vue new file mode 100644 index 0000000000..aadd971c4d --- /dev/null +++ b/frontend/app/components/charts/DirectionalTimeChart.vue @@ -0,0 +1,218 @@ + + + + \ No newline at end of file diff --git a/frontend/app/components/charts/DualAxisContributions.vue b/frontend/app/components/charts/DualAxisContributions.vue new file mode 100644 index 0000000000..a9e2e0ab1a --- /dev/null +++ b/frontend/app/components/charts/DualAxisContributions.vue @@ -0,0 +1,299 @@ + + + + diff --git a/frontend/app/components/charts/DynamicLineChart.vue b/frontend/app/components/charts/DynamicLineChart.vue index e2859211f1..911fd5404c 100644 --- a/frontend/app/components/charts/DynamicLineChart.vue +++ b/frontend/app/components/charts/DynamicLineChart.vue @@ -45,7 +45,8 @@ export default { detail: this.$store.state.showDetail, compRepos: this.$store.state.comparedRepos, metricSource: null, - timeperiod: 'all' + timeperiod: 'all', + forceRecomputeCounter: 0 } }, watch: { @@ -63,7 +64,17 @@ export default { $(this.$el).find('.spacing').removeClass('hidden') } }, - mounted() { + beforeUpdate() { + this.$store.watch( + // When the returned result changes... + function (state) { + console.log("WORKED") + this.thisShouldTriggerRecompute() + return + }, + // // Run this callback + // callback + ) }, computed: { repo () { @@ -100,6 +111,7 @@ export default { return this.$store.state.showDetail }, spec() { + this.forceRecomputeCounter; // Get the repos we need let repos = [] if (this.repo) { @@ -866,6 +878,9 @@ export default { }, // end computed methods: { + thisShouldTriggerRecompute() { + this.forceRecomputeCounter++; + }, downloadSVG (e) { var svgsaver = new window.SvgSaver() var svg = window.$(this.$refs.holder).find('svg')[0] diff --git a/frontend/app/components/charts/GroupedBarChart.vue b/frontend/app/components/charts/GroupedBarChart.vue new file mode 100644 index 0000000000..d9e5d4c7d9 --- /dev/null +++ b/frontend/app/components/charts/GroupedBarChart.vue @@ -0,0 +1,404 @@ + + + + \ No newline at end of file diff --git a/frontend/app/components/charts/HorizontalBarChart.vue b/frontend/app/components/charts/HorizontalBarChart.vue index 062ad70aac..293164f14e 100644 --- a/frontend/app/components/charts/HorizontalBarChart.vue +++ b/frontend/app/components/charts/HorizontalBarChart.vue @@ -124,7 +124,8 @@ export default { }, "title": { "text": this.title, - "offset": 15 + "offset": 15, + }, "layer": [ { diff --git a/frontend/app/components/charts/NormalizedStackedBarChart.vue b/frontend/app/components/charts/NormalizedStackedBarChart.vue index e46870b442..bbe6b1d6c2 100644 --- a/frontend/app/components/charts/NormalizedStackedBarChart.vue +++ b/frontend/app/components/charts/NormalizedStackedBarChart.vue @@ -1,6 +1,6 @@