From 557df21a30d410804678f44f80b0924b5eef20c7 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 13:15:46 -0400 Subject: [PATCH 01/10] Add initial Reports object --- atst/domain/reports.py | 9 +++++++++ templates/workspace_reports.html | 11 +++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 atst/domain/reports.py diff --git a/atst/domain/reports.py b/atst/domain/reports.py new file mode 100644 index 00000000..1d14e006 --- /dev/null +++ b/atst/domain/reports.py @@ -0,0 +1,9 @@ +class Reports: + + @classmethod + def workspace_totals(cls, workspace): + return { + 'budget': 100_000_000, + 'spent': 40_000_000, + } + diff --git a/templates/workspace_reports.html b/templates/workspace_reports.html index af72b3cf..f5469514 100644 --- a/templates/workspace_reports.html +++ b/templates/workspace_reports.html @@ -19,24 +19,27 @@

Workspace Total Spend

+ {% set budget = workspace_totals['budget'] %} + {% set spent = workspace_totals['spent'] %} + {% set remaining = budget - spent %}
Budget
-
$100,000,000
+
{{ budget | dollars }}
Remaining
-
$60,000,000
+
{{ remaining | dollars }}
- +
Total spend to date
-
$40,000,00
+
{{ spent | dollars }}
From f33a7b4a9fba229c66e5b066174dd1687bd27920 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 13:16:09 -0400 Subject: [PATCH 02/10] Protect reports page with correct permission --- atst/routes/workspaces.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 5ded0500..60ab4a67 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -8,9 +8,10 @@ from flask import ( ) from atst.domain.exceptions import UnauthorizedError +from atst.domain.projects import Projects +from atst.domain.reports import Reports from atst.domain.workspaces import Workspaces from atst.domain.workspace_users import WorkspaceUsers -from atst.domain.projects import Projects from atst.forms.new_project import NewProjectForm from atst.forms.new_member import NewMemberForm from atst.forms.edit_member import EditMemberForm @@ -66,7 +67,16 @@ def workspace_members(workspace_id): @bp.route("/workspaces//reports") def workspace_reports(workspace_id): - return render_template("workspace_reports.html", workspace_id=workspace_id) + workspace = Workspaces.get(g.current_user, workspace_id) + if not Authorization.has_workspace_permission( + g.current_user, workspace, Permissions.VIEW_USAGE_DOLLARS + ): + raise UnauthorizedError(g.current_user, "view workspace reports") + + return render_template( + "workspace_reports.html", + workspace_totals=Reports.workspace_totals(workspace), + ) @bp.route("/workspaces//projects/new") From a9d705daacb43f76bf9f4211f839c9ac7aaeedad Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 15:59:57 -0400 Subject: [PATCH 03/10] Add mock monthly reporting data --- atst/domain/reports.py | 161 ++++++++++++++++++++++++++++++- atst/routes/workspaces.py | 1 + templates/workspace_reports.html | 124 +++++++++--------------- 3 files changed, 200 insertions(+), 86 deletions(-) diff --git a/atst/domain/reports.py b/atst/domain/reports.py index 1d14e006..8ac261a2 100644 --- a/atst/domain/reports.py +++ b/atst/domain/reports.py @@ -1,9 +1,160 @@ -class Reports: +from itertools import groupby + +MONTHLY_SPEND = { + "LC04": { + "Integ": { + "10/2018": 284, + "11/2018": 1210, + "12/2018": 1430, + "01/2019": 1366, + "02/2019": 1169, + "03/2019": 991, + "04/2019": 978, + "05/2019": 737, + }, + "PreProd": { + "10/2018": 812, + "11/2018": 1389, + "12/2018": 1425, + "01/2019": 1306, + "02/2019": 1112, + "03/2019": 936, + "04/2019": 921, + "05/2019": 694, + }, + "Prod": { + "10/2018": 1742, + "11/2018": 1716, + "12/2018": 1866, + "01/2019": 1809, + "02/2019": 1839, + "03/2019": 1633, + "04/2019": 1654, + "05/2019": 1103, + }, + }, + "SF18": { + "Integ": { + "12/2018": 1498, + "01/2019": 1400, + "02/2019": 1394, + "03/2019": 1171, + "04/2019": 1200, + "05/2019": 963, + }, + "PreProd": { + "12/2018": 1780, + "01/2019": 1667, + "02/2019": 1703, + "03/2019": 1474, + "04/2019": 1441, + "05/2019": 933, + }, + "Prod": { + "12/2018": 1686, + "01/2019": 1779, + "02/2019": 1792, + "03/2019": 1570, + "04/2019": 1539, + "05/2019": 986, + }, + }, + "Canton": { + "Prod": { + "01/2019": 28699, + "02/2019": 26766, + "03/2019": 22619, + "04/2019": 24090, + "05/2019": 16719, + } + }, + "BD04": { + "Integ": {}, + "PreProd": { + "10/2018": 7019, + "11/2018": 3004, + "12/2018": 2691, + "01/2019": 2901, + "02/2019": 3463, + "03/2019": 3314, + "04/2019": 3432, + "05/2019": 723, + }, + }, + "SCV18": {"Dev": {"05/2019": 9797}}, + "Crown": { + "CR Portal Dev": { + "11/2018": 208, + "12/2018": 457, + "01/2019": 671, + "02/2019": 136, + "03/2019": 1524, + "04/2019": 2077, + "05/2019": 1858, + }, + "CR Staging": { + "11/2018": 208, + "12/2018": 457, + "01/2019": 671, + "02/2019": 136, + "03/2019": 1524, + "04/2019": 2077, + "05/2019": 1858, + }, + "CR Portal Test 1": {"03/2019": 806, "04/2019": 1966, "05/2019": 2597}, + "Jewels Prod": {"03/2019": 806, "04/2019": 1966, "05/2019": 2597}, + "Jewels Dev": { + "11/2018": 145, + "12/2018": 719, + "01/2019": 1243, + "02/2019": 2214, + "03/2019": 2959, + "04/2019": 4151, + "05/2019": 4260, + }, + }, +} + + +class Reports: @classmethod def workspace_totals(cls, workspace): - return { - 'budget': 100_000_000, - 'spent': 40_000_000, - } + spent = sum( + [ + spend + for project in MONTHLY_SPEND.values() + for env in project.values() + for spend in env.values() + ] + ) + return {"budget": 500_000, "spent": spent} + @classmethod + def monthly_totals(cls, workspace): + project_totals = {} + for project, environments in MONTHLY_SPEND.items(): + project_spend = [ + (month, spend) + for env in environments.values() + for month, spend in env.items() + ] + project_totals[project] = { + month: sum([spend[1] for spend in spends]) + for month, spends in groupby(sorted(project_spend), lambda x: x[0]) + } + + monthly_spend = [ + (month, spend) + for project in project_totals.values() + for month, spend in project.items() + ] + workspace_totals = {} + for month, spends in groupby(sorted(monthly_spend), lambda m: m[0]): + workspace_totals[month] = sum([spend[1] for spend in spends]) + + return { + "environments": MONTHLY_SPEND, + "projects": project_totals, + "workspace": workspace_totals, + } diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 60ab4a67..ebb9375e 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -76,6 +76,7 @@ def workspace_reports(workspace_id): return render_template( "workspace_reports.html", workspace_totals=Reports.workspace_totals(workspace), + monthly_totals=Reports.monthly_totals(workspace), ) diff --git a/templates/workspace_reports.html b/templates/workspace_reports.html index f5469514..f5a70877 100644 --- a/templates/workspace_reports.html +++ b/templates/workspace_reports.html @@ -94,105 +94,67 @@

Total spend per month

- - - + + + + {% set workspace_totals = monthly_totals['workspace'] %} + {% set current_month = '05/2019' %} + {% set prev_month = '04/2019' %} + {% set two_months_ago = '03/2019' %} - - - + + + - - - - - - - + {% for project_name, project_totals in monthly_totals['projects'].items() %} + + + + {% endfor %}
Spending scopeApril 2018May 2018June 2018March 2019April 2019May 2019
Workspace Total$58,000$60,000$62,000{{ workspace_totals[two_months_ago] | dollars }}{{ workspace_totals[prev_month] | dollars }}{{ workspace_totals[current_month] | dollars }} - +
From 0677a1b4dd1a07121b698a72093da4627b71b1c3 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 16:17:16 -0400 Subject: [PATCH 04/10] Change placeholder links to "#" instead of "/" I keep clicking things and being sent to `/` unintentionally. --- templates/workspace_reports.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/workspace_reports.html b/templates/workspace_reports.html index f5a70877..c41fac78 100644 --- a/templates/workspace_reports.html +++ b/templates/workspace_reports.html @@ -70,7 +70,7 @@ - + Manage Task Order @@ -145,7 +145,7 @@ {% for env_name, env_totals in monthly_totals['environments'][project_name].items() %} - {{ Icon('link') }} {{ env_name }} + {{ Icon('link') }} {{ env_name }} {{ env_totals.get(two_months_ago, 0) | dollars }} {{ env_totals.get(prev_month, 0) | dollars }} {{ env_totals.get(current_month, 0) | dollars }} From 4982b80d9bd42f4bf142c1f70e36a64aed5d8835 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 16:22:58 -0400 Subject: [PATCH 05/10] Add alternate set of mock reporting data --- atst/domain/reports.py | 42 ++++++++++++++++++++++++++------ templates/workspace_reports.html | 29 +++++++++++----------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/atst/domain/reports.py b/atst/domain/reports.py index 8ac261a2..42bae5b0 100644 --- a/atst/domain/reports.py +++ b/atst/domain/reports.py @@ -1,7 +1,7 @@ from itertools import groupby -MONTHLY_SPEND = { +MONTHLY_SPEND_AARDVARK = { "LC04": { "Integ": { "10/2018": 284, @@ -116,24 +116,52 @@ MONTHLY_SPEND = { }, } +MONTHLY_SPEND_BELUGA = { + "NP02": { + "Integ": { + "02/2019": 284, + "03/2019": 1210, + }, + "PreProd": { + "02/2019": 812, + "03/2019": 1389, + }, + "Prod": { + "02/2019": 3742, + "03/2019": 4716, + }, + }, + "FM": { + "Integ": { + "03/2019": 1498, + }, + "Prod": { + "03/2019": 5686, + }, + }, +} + class Reports: @classmethod - def workspace_totals(cls, workspace): + def workspace_totals(cls, alternate): + data = MONTHLY_SPEND_BELUGA if alternate else MONTHLY_SPEND_AARDVARK spent = sum( [ spend - for project in MONTHLY_SPEND.values() + for project in data.values() for env in project.values() for spend in env.values() ] ) - return {"budget": 500_000, "spent": spent} + budget = 70_000 if alternate else 500_000 + return {"budget": budget, "spent": spent} @classmethod - def monthly_totals(cls, workspace): + def monthly_totals(cls, alternate): + data = MONTHLY_SPEND_BELUGA if alternate else MONTHLY_SPEND_AARDVARK project_totals = {} - for project, environments in MONTHLY_SPEND.items(): + for project, environments in data.items(): project_spend = [ (month, spend) for env in environments.values() @@ -154,7 +182,7 @@ class Reports: workspace_totals[month] = sum([spend[1] for spend in spends]) return { - "environments": MONTHLY_SPEND, + "environments": data, "projects": project_totals, "workspace": workspace_totals, } diff --git a/templates/workspace_reports.html b/templates/workspace_reports.html index c41fac78..0d08322d 100644 --- a/templates/workspace_reports.html +++ b/templates/workspace_reports.html @@ -89,36 +89,37 @@ + {% set current_month = '03/2019' %} + {% set prev_month = '02/2019' %} + {% set two_months_ago = '01/2019' %} + {% set workspace_totals = monthly_totals['workspace'] %} +

Total spend per month

- - - + + + - {% set workspace_totals = monthly_totals['workspace'] %} - {% set current_month = '05/2019' %} - {% set prev_month = '04/2019' %} - {% set two_months_ago = '03/2019' %} - - - + + + @@ -138,8 +139,8 @@ From 5abd592f78f8e3a111d0ec63891e8824ddb62bbf Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 17:26:26 -0400 Subject: [PATCH 06/10] Add query string args to control date/alternate date source --- atst/routes/workspaces.py | 17 ++++++++++++-- templates/workspace_reports.html | 38 ++++++++++++++++---------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index ebb9375e..a49a5996 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -1,3 +1,5 @@ +from datetime import date, timedelta + from flask import ( Blueprint, render_template, @@ -73,10 +75,21 @@ def workspace_reports(workspace_id): ): raise UnauthorizedError(g.current_user, "view workspace reports") + alternate_reports = http_request.args.get('alternate') + month = http_request.args.get('month', 3) + year = http_request.args.get('year', 2019) + current_month = date(int(year), int(month), 15) + prev_month = current_month - timedelta(days=28) + two_months_ago = prev_month - timedelta(days=28) + + return render_template( "workspace_reports.html", - workspace_totals=Reports.workspace_totals(workspace), - monthly_totals=Reports.monthly_totals(workspace), + workspace_totals=Reports.workspace_totals(alternate_reports), + monthly_totals=Reports.monthly_totals(alternate_reports), + current_month=current_month, + prev_month=prev_month, + two_months_ago=two_months_ago, ) diff --git a/templates/workspace_reports.html b/templates/workspace_reports.html index 0d08322d..060f33e8 100644 --- a/templates/workspace_reports.html +++ b/templates/workspace_reports.html @@ -89,37 +89,37 @@ - {% set current_month = '03/2019' %} - {% set prev_month = '02/2019' %} - {% set two_months_ago = '01/2019' %} {% set workspace_totals = monthly_totals['workspace'] %} + {% set current_month_index = current_month.strftime('%m/%Y') %} + {% set prev_month_index = prev_month.strftime('%m/%Y') %} + {% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}

Total spend per month

Spending scopeMarch 2019April 2019May 2019January 2019February 2019March 2019
Workspace Total{{ workspace_totals[two_months_ago] | dollars }}{{ workspace_totals[prev_month] | dollars }}{{ workspace_totals[current_month] | dollars }}{{ workspace_totals.get(two_months_ago, 0) | dollars }}{{ workspace_totals.get(prev_month, 0) | dollars }}{{ workspace_totals.get(current_month, 0) | dollars }} - +
{{ project_totals.get(prev_month, 0) | dollars }} {{ project_totals.get(current_month, 0) | dollars }} - {{ (100 * (project_totals.get(current_month) / workspace_totals[current_month])) | round | int }}% - + {{ (100 * (project_totals.get(current_month, 0) / workspace_totals.get(current_month, 1))) | round | int }}% +
- - - + + + - - - + + + @@ -135,21 +135,21 @@ {{ project_name }} - - - + + + {% for env_name, env_totals in monthly_totals['environments'][project_name].items() %} - - - + + + {% endfor %} From d6da217e089a6e54d34816b19d49c0d1ec1d27dd Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Wed, 5 Sep 2018 17:31:32 -0400 Subject: [PATCH 07/10] Run that formatter jawn --- atst/domain/reports.py | 24 ++++-------------------- atst/routes/workspaces.py | 7 +++---- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/atst/domain/reports.py b/atst/domain/reports.py index 42bae5b0..89bcf7c9 100644 --- a/atst/domain/reports.py +++ b/atst/domain/reports.py @@ -118,27 +118,11 @@ MONTHLY_SPEND_AARDVARK = { MONTHLY_SPEND_BELUGA = { "NP02": { - "Integ": { - "02/2019": 284, - "03/2019": 1210, - }, - "PreProd": { - "02/2019": 812, - "03/2019": 1389, - }, - "Prod": { - "02/2019": 3742, - "03/2019": 4716, - }, - }, - "FM": { - "Integ": { - "03/2019": 1498, - }, - "Prod": { - "03/2019": 5686, - }, + "Integ": {"02/2019": 284, "03/2019": 1210}, + "PreProd": {"02/2019": 812, "03/2019": 1389}, + "Prod": {"02/2019": 3742, "03/2019": 4716}, }, + "FM": {"Integ": {"03/2019": 1498}, "Prod": {"03/2019": 5686}}, } diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index a49a5996..63524ab6 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -75,14 +75,13 @@ def workspace_reports(workspace_id): ): raise UnauthorizedError(g.current_user, "view workspace reports") - alternate_reports = http_request.args.get('alternate') - month = http_request.args.get('month', 3) - year = http_request.args.get('year', 2019) + alternate_reports = http_request.args.get("alternate") + month = http_request.args.get("month", 3) + year = http_request.args.get("year", 2019) current_month = date(int(year), int(month), 15) prev_month = current_month - timedelta(days=28) two_months_ago = prev_month - timedelta(days=28) - return render_template( "workspace_reports.html", workspace_totals=Reports.workspace_totals(alternate_reports), From f0664e254c88935eb7a6e8956a02bf52e323582b Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Thu, 6 Sep 2018 09:50:45 -0400 Subject: [PATCH 08/10] Use `check_workspace_permission` helper --- atst/routes/workspaces.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 63524ab6..131fcc30 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -70,10 +70,12 @@ def workspace_members(workspace_id): @bp.route("/workspaces//reports") def workspace_reports(workspace_id): workspace = Workspaces.get(g.current_user, workspace_id) - if not Authorization.has_workspace_permission( - g.current_user, workspace, Permissions.VIEW_USAGE_DOLLARS - ): - raise UnauthorizedError(g.current_user, "view workspace reports") + Authorization.check_workspace_permission( + g.current_user, + workspace, + Permissions.VIEW_USAGE_DOLLARS, + "view workspace reports", + ) alternate_reports = http_request.args.get("alternate") month = http_request.args.get("month", 3) From 9d8fe572bb3d1afbd6eea367628f3ee078c3312c Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Thu, 6 Sep 2018 09:57:15 -0400 Subject: [PATCH 09/10] Default to current month for reporting --- atst/routes/workspaces.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/atst/routes/workspaces.py b/atst/routes/workspaces.py index 131fcc30..d6da53ab 100644 --- a/atst/routes/workspaces.py +++ b/atst/routes/workspaces.py @@ -78,8 +78,9 @@ def workspace_reports(workspace_id): ) alternate_reports = http_request.args.get("alternate") - month = http_request.args.get("month", 3) - year = http_request.args.get("year", 2019) + today = date.today() + month = http_request.args.get("month", today.month) + year = http_request.args.get("year", today.year) current_month = date(int(year), int(month), 15) prev_month = current_month - timedelta(days=28) two_months_ago = prev_month - timedelta(days=28) From 0a1cb6a5c9fab44a0cb6be9785d5f231760e51d3 Mon Sep 17 00:00:00 2001 From: Patrick Smith Date: Thu, 6 Sep 2018 10:13:13 -0400 Subject: [PATCH 10/10] Add mock data for cumulative budget reports --- atst/domain/reports.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/atst/domain/reports.py b/atst/domain/reports.py index 89bcf7c9..15df3ba4 100644 --- a/atst/domain/reports.py +++ b/atst/domain/reports.py @@ -116,6 +116,21 @@ MONTHLY_SPEND_AARDVARK = { }, } +CUMULATIVE_BUDGET_AARDVARK = { + "10/2018": {"spend": 9857, "cumulative": 9857}, + "11/2018": {"spend": 7881, "cumulative": 17738}, + "12/2018": {"spend": 14010, "cumulative": 31748}, + "01/2019": {"spend": 43510, "cumulative": 75259}, + "02/2019": {"spend": 41725, "cumulative": 116984}, + "03/2019": {"spend": 41328, "cumulative": 158312}, + "04/2019": {"spend": 47491, "cumulative": 205803}, + "05/2019": {"spend": 45826, "cumulative": 251629}, + "06/2019": {"projected": 296511}, + "07/2019": {"projected": 341393}, + "08/2019": {"projected": 386274}, + "09/2019": {"projected": 431156}, +} + MONTHLY_SPEND_BELUGA = { "NP02": { "Integ": {"02/2019": 284, "03/2019": 1210}, @@ -125,6 +140,17 @@ MONTHLY_SPEND_BELUGA = { "FM": {"Integ": {"03/2019": 1498}, "Prod": {"03/2019": 5686}}, } +CUMULATIVE_BUDGET_BELUGA = { + "02/2019": {"spend": 4838, "cumulative": 4838}, + "03/2019": {"spend": 14500, "cumulative": 19338}, + "04/2019": {"projected": 29007}, + "05/2019": {"projected": 38676}, + "06/2019": {"projected": 48345}, + "07/2019": {"projected": 58014}, + "08/2019": {"projected": 67683}, + "09/2019": {"projected": 77352}, +} + class Reports: @classmethod
Spending scopeJanuary 2019February 2019March 2019{{ two_months_ago.strftime('%B %Y') }}{{ prev_month.strftime('%B %Y') }}{{ current_month.strftime('%B %Y') }}
Workspace Total{{ workspace_totals.get(two_months_ago, 0) | dollars }}{{ workspace_totals.get(prev_month, 0) | dollars }}{{ workspace_totals.get(current_month, 0) | dollars }}{{ workspace_totals.get(two_months_ago_index, 0) | dollars }}{{ workspace_totals.get(prev_month_index, 0) | dollars }}{{ workspace_totals.get(current_month_index, 0) | dollars }} - +
{{ project_totals.get(two_months_ago, 0) | dollars }}{{ project_totals.get(prev_month, 0) | dollars }}{{ project_totals.get(current_month, 0) | dollars }}{{ project_totals.get(two_months_ago_index, 0) | dollars }}{{ project_totals.get(prev_month_index, 0) | dollars }}{{ project_totals.get(current_month_index, 0) | dollars }} - {{ (100 * (project_totals.get(current_month, 0) / workspace_totals.get(current_month, 1))) | round | int }}% - + {{ (100 * (project_totals.get(current_month_index, 0) / workspace_totals.get(current_month_index, 1))) | round | int }}% +
{{ Icon('link') }} {{ env_name }}{{ env_totals.get(two_months_ago, 0) | dollars }}{{ env_totals.get(prev_month, 0) | dollars }}{{ env_totals.get(current_month, 0) | dollars }}{{ env_totals.get(two_months_ago_index, 0) | dollars }}{{ env_totals.get(prev_month_index, 0) | dollars }}{{ env_totals.get(current_month_index, 0) | dollars }}