Refactor mock reporting data and class methods
All mock reporting data was moved to a JSON file. The concept of what JEDI CLIN a particular environment drew money from was added to the data. This change had a cascade effect to the reporting class methods, templates, and Vue components that ingested that reporting data. Many of these files were modified to adapt to these changes. This also included modifying the obligated funding bar graphs to reflect new design changes.
This commit is contained in:
parent
d9c79b9b58
commit
dc9a21a501
390
atst/domain/csp/fixture_spend_data.json
Normal file
390
atst/domain/csp/fixture_spend_data.json
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
{
|
||||||
|
"A-Wing": {
|
||||||
|
"applications": [
|
||||||
|
{
|
||||||
|
"name": "LC04",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Integ",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 663,
|
||||||
|
"JEDI_CLIN_2": 397
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 590,
|
||||||
|
"JEDI_CLIN_2": 829
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 42467,
|
||||||
|
"JEDI_CLIN_2": 33873
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PreProd",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 1000,
|
||||||
|
"JEDI_CLIN_2": 626
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 685,
|
||||||
|
"JEDI_CLIN_2": 331
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 21874,
|
||||||
|
"JEDI_CLIN_2": 25506
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Prod",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 856,
|
||||||
|
"JEDI_CLIN_2": 627
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 921,
|
||||||
|
"JEDI_CLIN_2": 473
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 35566,
|
||||||
|
"JEDI_CLIN_2": 42514
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SF18",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Integ",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 777,
|
||||||
|
"JEDI_CLIN_2": 850
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 584,
|
||||||
|
"JEDI_CLIN_2": 362
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 44505,
|
||||||
|
"JEDI_CLIN_2": 21378
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PreProd",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 487,
|
||||||
|
"JEDI_CLIN_2": 733
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 542,
|
||||||
|
"JEDI_CLIN_2": 999
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 8713,
|
||||||
|
"JEDI_CLIN_2": 10586
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Prod",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 420,
|
||||||
|
"JEDI_CLIN_2": 503
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 756,
|
||||||
|
"JEDI_CLIN_2": 941
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 43003,
|
||||||
|
"JEDI_CLIN_2": 20601
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Canton",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Prod",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 661,
|
||||||
|
"JEDI_CLIN_2": 599
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 962,
|
||||||
|
"JEDI_CLIN_2": 383
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 24501,
|
||||||
|
"JEDI_CLIN_2": 7551
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BD04",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Integ",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 790,
|
||||||
|
"JEDI_CLIN_2": 513
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 886,
|
||||||
|
"JEDI_CLIN_2": 991
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 43684,
|
||||||
|
"JEDI_CLIN_2": 40196
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PreProd",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 513,
|
||||||
|
"JEDI_CLIN_2": 706
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 945,
|
||||||
|
"JEDI_CLIN_2": 380
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 28189,
|
||||||
|
"JEDI_CLIN_2": 9759
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SCV18",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Dev",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 933,
|
||||||
|
"JEDI_CLIN_2": 993
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 319,
|
||||||
|
"JEDI_CLIN_2": 619
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 40585,
|
||||||
|
"JEDI_CLIN_2": 28872
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Crown",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "CR Portal Dev",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 711,
|
||||||
|
"JEDI_CLIN_2": 413
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 908,
|
||||||
|
"JEDI_CLIN_2": 632
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 18753,
|
||||||
|
"JEDI_CLIN_2": 4004
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CR Staging",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 440,
|
||||||
|
"JEDI_CLIN_2": 918
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 370,
|
||||||
|
"JEDI_CLIN_2": 472
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 40602,
|
||||||
|
"JEDI_CLIN_2": 6834
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CR Portal Test 1",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 928,
|
||||||
|
"JEDI_CLIN_2": 796
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 680,
|
||||||
|
"JEDI_CLIN_2": 312
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 36058,
|
||||||
|
"JEDI_CLIN_2": 42375
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jewels Prod",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 304,
|
||||||
|
"JEDI_CLIN_2": 428
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 898,
|
||||||
|
"JEDI_CLIN_2": 729
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 3162,
|
||||||
|
"JEDI_CLIN_2": 49836
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jewels Dev",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 498,
|
||||||
|
"JEDI_CLIN_2": 890
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 506,
|
||||||
|
"JEDI_CLIN_2": 659
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 6248,
|
||||||
|
"JEDI_CLIN_2": 3866
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"B-Wing": {
|
||||||
|
"applications": [
|
||||||
|
{
|
||||||
|
"name": "NP02",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Integ",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 455,
|
||||||
|
"JEDI_CLIN_2": 746
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 973,
|
||||||
|
"JEDI_CLIN_2": 504
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 11493,
|
||||||
|
"JEDI_CLIN_2": 17751
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PreProd",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 582,
|
||||||
|
"JEDI_CLIN_2": 339
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 392,
|
||||||
|
"JEDI_CLIN_2": 885
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 41856,
|
||||||
|
"JEDI_CLIN_2": 46399
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Prod",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 446,
|
||||||
|
"JEDI_CLIN_2": 670
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 368,
|
||||||
|
"JEDI_CLIN_2": 963
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 10030,
|
||||||
|
"JEDI_CLIN_2": 29253
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FM",
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"name": "Integ",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 994,
|
||||||
|
"JEDI_CLIN_2": 573
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 699,
|
||||||
|
"JEDI_CLIN_2": 418
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 27881,
|
||||||
|
"JEDI_CLIN_2": 37092
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Prod",
|
||||||
|
"spending": {
|
||||||
|
"this_month": {
|
||||||
|
"JEDI_CLIN_1": 838,
|
||||||
|
"JEDI_CLIN_2": 839
|
||||||
|
},
|
||||||
|
"last_month": {
|
||||||
|
"JEDI_CLIN_1": 775,
|
||||||
|
"JEDI_CLIN_2": 946
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"JEDI_CLIN_1": 45007,
|
||||||
|
"JEDI_CLIN_2": 16197
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -1,344 +1,124 @@
|
|||||||
from itertools import groupby
|
from collections import defaultdict
|
||||||
import pendulum
|
import json
|
||||||
|
import os
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
|
|
||||||
class ReportingInterface:
|
def load_fixture_data():
|
||||||
def monthly_totals_for_environment(environment):
|
with open(
|
||||||
"""Return the monthly totals for the specified environment.
|
os.path.join(os.path.dirname(__file__), "fixture_spend_data.json"), "r"
|
||||||
|
) as json_file:
|
||||||
|
return json.load(json_file)
|
||||||
|
|
||||||
Data should be in the format of a dictionary with the month as the key
|
|
||||||
and the spend in that month as the value. For example:
|
|
||||||
|
|
||||||
{ "01/2018": 79.85, "02/2018": 86.54 }
|
class MockReportingProvider:
|
||||||
|
FIXTURE_SPEND_DATA = load_fixture_data()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_portfolio_monthly_spending(cls, portfolio):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
returns an array of application and environment spending for the
|
||||||
|
portfolio. Applications and their nested environments are sorted in
|
||||||
|
alphabetical order by name.
|
||||||
class MockEnvironment:
|
[
|
||||||
def __init__(self, id_, env_name):
|
|
||||||
self.id = id_
|
|
||||||
self.name = env_name
|
|
||||||
|
|
||||||
|
|
||||||
class MockApplication:
|
|
||||||
def __init__(self, application_name, envs):
|
|
||||||
def make_env(name):
|
|
||||||
return MockEnvironment("{}_{}".format(application_name, name), name)
|
|
||||||
|
|
||||||
self.name = application_name
|
|
||||||
self.environments = [make_env(env_name) for env_name in envs]
|
|
||||||
|
|
||||||
|
|
||||||
def generate_sample_dates(_max=8):
|
|
||||||
current = pendulum.now()
|
|
||||||
sample_dates = []
|
|
||||||
for _i in range(_max):
|
|
||||||
sample_dates.append(current.strftime("%m/%Y"))
|
|
||||||
current = current.subtract(months=1)
|
|
||||||
|
|
||||||
reversed(sample_dates)
|
|
||||||
return sample_dates
|
|
||||||
|
|
||||||
|
|
||||||
class MockReportingProvider(ReportingInterface):
|
|
||||||
MOCK_PERCENT_EXPENDED_FUNDS = 0.75
|
|
||||||
|
|
||||||
FIXTURE_MONTHS = generate_sample_dates()
|
|
||||||
|
|
||||||
MONTHLY_SPEND_BY_ENVIRONMENT = {
|
|
||||||
"LC04_Integ": {
|
|
||||||
FIXTURE_MONTHS[7]: 284,
|
|
||||||
FIXTURE_MONTHS[6]: 1210,
|
|
||||||
FIXTURE_MONTHS[5]: 1430,
|
|
||||||
FIXTURE_MONTHS[4]: 1366,
|
|
||||||
FIXTURE_MONTHS[3]: 1169,
|
|
||||||
FIXTURE_MONTHS[2]: 991,
|
|
||||||
FIXTURE_MONTHS[1]: 978,
|
|
||||||
FIXTURE_MONTHS[0]: 737,
|
|
||||||
},
|
|
||||||
"LC04_PreProd": {
|
|
||||||
FIXTURE_MONTHS[7]: 812,
|
|
||||||
FIXTURE_MONTHS[6]: 1389,
|
|
||||||
FIXTURE_MONTHS[5]: 1425,
|
|
||||||
FIXTURE_MONTHS[4]: 1306,
|
|
||||||
FIXTURE_MONTHS[3]: 1112,
|
|
||||||
FIXTURE_MONTHS[2]: 936,
|
|
||||||
FIXTURE_MONTHS[1]: 921,
|
|
||||||
FIXTURE_MONTHS[0]: 694,
|
|
||||||
},
|
|
||||||
"LC04_Prod": {
|
|
||||||
FIXTURE_MONTHS[7]: 1742,
|
|
||||||
FIXTURE_MONTHS[6]: 1716,
|
|
||||||
FIXTURE_MONTHS[5]: 1866,
|
|
||||||
FIXTURE_MONTHS[4]: 1809,
|
|
||||||
FIXTURE_MONTHS[3]: 1839,
|
|
||||||
FIXTURE_MONTHS[2]: 1633,
|
|
||||||
FIXTURE_MONTHS[1]: 1654,
|
|
||||||
FIXTURE_MONTHS[0]: 1103,
|
|
||||||
},
|
|
||||||
"SF18_Integ": {
|
|
||||||
FIXTURE_MONTHS[5]: 1498,
|
|
||||||
FIXTURE_MONTHS[4]: 1400,
|
|
||||||
FIXTURE_MONTHS[3]: 1394,
|
|
||||||
FIXTURE_MONTHS[2]: 1171,
|
|
||||||
FIXTURE_MONTHS[1]: 1200,
|
|
||||||
FIXTURE_MONTHS[0]: 963,
|
|
||||||
},
|
|
||||||
"SF18_PreProd": {
|
|
||||||
FIXTURE_MONTHS[5]: 1780,
|
|
||||||
FIXTURE_MONTHS[4]: 1667,
|
|
||||||
FIXTURE_MONTHS[3]: 1703,
|
|
||||||
FIXTURE_MONTHS[2]: 1474,
|
|
||||||
FIXTURE_MONTHS[1]: 1441,
|
|
||||||
FIXTURE_MONTHS[0]: 933,
|
|
||||||
},
|
|
||||||
"SF18_Prod": {
|
|
||||||
FIXTURE_MONTHS[5]: 1686,
|
|
||||||
FIXTURE_MONTHS[4]: 1779,
|
|
||||||
FIXTURE_MONTHS[3]: 1792,
|
|
||||||
FIXTURE_MONTHS[2]: 1570,
|
|
||||||
FIXTURE_MONTHS[1]: 1539,
|
|
||||||
FIXTURE_MONTHS[0]: 986,
|
|
||||||
},
|
|
||||||
"Canton_Prod": {
|
|
||||||
FIXTURE_MONTHS[4]: 28699,
|
|
||||||
FIXTURE_MONTHS[3]: 26766,
|
|
||||||
FIXTURE_MONTHS[2]: 22619,
|
|
||||||
FIXTURE_MONTHS[1]: 24090,
|
|
||||||
FIXTURE_MONTHS[0]: 16719,
|
|
||||||
},
|
|
||||||
"BD04_Integ": {},
|
|
||||||
"BD04_PreProd": {
|
|
||||||
FIXTURE_MONTHS[7]: 7019,
|
|
||||||
FIXTURE_MONTHS[6]: 3004,
|
|
||||||
FIXTURE_MONTHS[5]: 2691,
|
|
||||||
FIXTURE_MONTHS[4]: 2901,
|
|
||||||
FIXTURE_MONTHS[3]: 3463,
|
|
||||||
FIXTURE_MONTHS[2]: 3314,
|
|
||||||
FIXTURE_MONTHS[1]: 3432,
|
|
||||||
FIXTURE_MONTHS[0]: 723,
|
|
||||||
},
|
|
||||||
"SCV18_Dev": {FIXTURE_MONTHS[1]: 9797},
|
|
||||||
"Crown_CR Portal Dev": {
|
|
||||||
FIXTURE_MONTHS[6]: 208,
|
|
||||||
FIXTURE_MONTHS[5]: 457,
|
|
||||||
FIXTURE_MONTHS[4]: 671,
|
|
||||||
FIXTURE_MONTHS[3]: 136,
|
|
||||||
FIXTURE_MONTHS[2]: 1524,
|
|
||||||
FIXTURE_MONTHS[1]: 2077,
|
|
||||||
FIXTURE_MONTHS[0]: 1858,
|
|
||||||
},
|
|
||||||
"Crown_CR Staging": {
|
|
||||||
FIXTURE_MONTHS[6]: 208,
|
|
||||||
FIXTURE_MONTHS[5]: 457,
|
|
||||||
FIXTURE_MONTHS[4]: 671,
|
|
||||||
FIXTURE_MONTHS[3]: 136,
|
|
||||||
FIXTURE_MONTHS[2]: 1524,
|
|
||||||
FIXTURE_MONTHS[1]: 2077,
|
|
||||||
FIXTURE_MONTHS[0]: 1858,
|
|
||||||
},
|
|
||||||
"Crown_CR Portal Test 1": {
|
|
||||||
FIXTURE_MONTHS[2]: 806,
|
|
||||||
FIXTURE_MONTHS[1]: 1966,
|
|
||||||
FIXTURE_MONTHS[0]: 2597,
|
|
||||||
},
|
|
||||||
"Crown_Jewels Prod": {
|
|
||||||
FIXTURE_MONTHS[2]: 806,
|
|
||||||
FIXTURE_MONTHS[1]: 1966,
|
|
||||||
FIXTURE_MONTHS[0]: 2597,
|
|
||||||
},
|
|
||||||
"Crown_Jewels Dev": {
|
|
||||||
FIXTURE_MONTHS[6]: 145,
|
|
||||||
FIXTURE_MONTHS[5]: 719,
|
|
||||||
FIXTURE_MONTHS[4]: 1243,
|
|
||||||
FIXTURE_MONTHS[3]: 2214,
|
|
||||||
FIXTURE_MONTHS[2]: 2959,
|
|
||||||
FIXTURE_MONTHS[1]: 4151,
|
|
||||||
FIXTURE_MONTHS[0]: 4260,
|
|
||||||
},
|
|
||||||
"NP02_Integ": {FIXTURE_MONTHS[1]: 284, FIXTURE_MONTHS[0]: 1210},
|
|
||||||
"NP02_PreProd": {FIXTURE_MONTHS[1]: 812, FIXTURE_MONTHS[0]: 1389},
|
|
||||||
"NP02_Prod": {FIXTURE_MONTHS[1]: 3742, FIXTURE_MONTHS[0]: 4716},
|
|
||||||
"FM_Integ": {FIXTURE_MONTHS[1]: 1498},
|
|
||||||
"FM_Prod": {FIXTURE_MONTHS[0]: 5686},
|
|
||||||
}
|
|
||||||
|
|
||||||
REPORT_FIXTURE_MAP = {
|
|
||||||
"A-Wing": {
|
|
||||||
"applications": [
|
|
||||||
MockApplication("LC04", ["Integ", "PreProd", "Prod"]),
|
|
||||||
MockApplication("SF18", ["Integ", "PreProd", "Prod"]),
|
|
||||||
MockApplication("Canton", ["Prod"]),
|
|
||||||
MockApplication("BD04", ["Integ", "PreProd"]),
|
|
||||||
MockApplication("SCV18", ["Dev"]),
|
|
||||||
MockApplication(
|
|
||||||
"Crown",
|
|
||||||
[
|
|
||||||
"CR Portal Dev",
|
|
||||||
"CR Staging",
|
|
||||||
"CR Portal Test 1",
|
|
||||||
"Jewels Prod",
|
|
||||||
"Jewels Dev",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
"budget": 500_000,
|
|
||||||
},
|
|
||||||
"B-Wing": {
|
|
||||||
"applications": [
|
|
||||||
MockApplication("NP02", ["Integ", "PreProd", "Prod"]),
|
|
||||||
MockApplication("FM", ["Integ", "Prod"]),
|
|
||||||
],
|
|
||||||
"budget": 70000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _rollup_application_totals(self, data):
|
|
||||||
application_totals = {}
|
|
||||||
for application, environments in data.items():
|
|
||||||
application_spend = [
|
|
||||||
(month, spend)
|
|
||||||
for env in environments.values()
|
|
||||||
if env
|
|
||||||
for month, spend in env.items()
|
|
||||||
]
|
|
||||||
application_totals[application] = {
|
|
||||||
month: sum([spend[1] for spend in spends])
|
|
||||||
for month, spends in groupby(sorted(application_spend), lambda x: x[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return application_totals
|
|
||||||
|
|
||||||
def _rollup_portfolio_totals(self, application_totals):
|
|
||||||
monthly_spend = [
|
|
||||||
(month, spend)
|
|
||||||
for application in application_totals.values()
|
|
||||||
for month, spend in application.items()
|
|
||||||
]
|
|
||||||
portfolio_totals = {}
|
|
||||||
for month, spends in groupby(sorted(monthly_spend), lambda m: m[0]):
|
|
||||||
portfolio_totals[month] = sum([spend[1] for spend in spends])
|
|
||||||
|
|
||||||
return portfolio_totals
|
|
||||||
|
|
||||||
def monthly_totals_for_environment(self, environment_id):
|
|
||||||
"""Return the monthly totals for the specified environment.
|
|
||||||
|
|
||||||
Data should be in the format of a dictionary with the month as the key
|
|
||||||
and the spend in that month as the value. For example:
|
|
||||||
|
|
||||||
{ "01/2018": 79.85, "02/2018": 86.54 }
|
|
||||||
|
|
||||||
"""
|
|
||||||
environment_monthly_totals = self.MONTHLY_SPEND_BY_ENVIRONMENT.get(
|
|
||||||
environment_id, {}
|
|
||||||
).copy()
|
|
||||||
|
|
||||||
environment_monthly_totals["total_spend_to_date"] = sum(
|
|
||||||
monthly_total for monthly_total in environment_monthly_totals.values()
|
|
||||||
)
|
|
||||||
return environment_monthly_totals
|
|
||||||
|
|
||||||
def monthly_totals(self, portfolio):
|
|
||||||
"""Return month totals rolled up by environment, application, and portfolio.
|
|
||||||
|
|
||||||
Data should returned with three top level keys, "portfolio", "applications",
|
|
||||||
and "environments".
|
|
||||||
The "applications" key will have budget data per month for each application,
|
|
||||||
The "environments" key will have budget data for each environment.
|
|
||||||
The "portfolio" key will be total monthly spending for the portfolio.
|
|
||||||
For example:
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"environments": { "X-Wing": { "Prod": { "01/2018": 75.42 } } },
|
name
|
||||||
"applications": { "X-Wing": { "01/2018": 75.42 } },
|
this_month
|
||||||
"portfolio": { "01/2018": 75.42 },
|
last_month
|
||||||
|
total
|
||||||
|
environments [
|
||||||
|
{
|
||||||
|
name
|
||||||
|
this_month
|
||||||
|
last_month
|
||||||
|
total
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
"""
|
"""
|
||||||
applications = portfolio.applications
|
if portfolio.name in cls.FIXTURE_SPEND_DATA:
|
||||||
if portfolio.name in self.REPORT_FIXTURE_MAP:
|
applications = cls.FIXTURE_SPEND_DATA[portfolio.name]["applications"]
|
||||||
applications = self.REPORT_FIXTURE_MAP[portfolio.name]["applications"]
|
return sorted(
|
||||||
environments = {
|
[
|
||||||
application.name: {
|
cls._get_application_monthly_totals(application)
|
||||||
env.name: self.monthly_totals_for_environment(env.id)
|
for application in applications
|
||||||
for env in application.environments
|
],
|
||||||
}
|
key=lambda app: app["name"],
|
||||||
for application in applications
|
|
||||||
}
|
|
||||||
|
|
||||||
application_totals = self._rollup_application_totals(environments)
|
|
||||||
portfolio_totals = self._rollup_portfolio_totals(application_totals)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"environments": environments,
|
|
||||||
"applications": application_totals,
|
|
||||||
"portfolio": portfolio_totals,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_obligated_funds_by_JEDI_clin(self, portfolio):
|
|
||||||
"""
|
|
||||||
Returns a dictionary of obligated funds and spending per JEDI CLIN
|
|
||||||
{
|
|
||||||
JEDI_CLIN: {
|
|
||||||
obligated_funds,
|
|
||||||
expended_funds
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
if portfolio.name in self.REPORT_FIXTURE_MAP:
|
|
||||||
return_dict = {}
|
|
||||||
for jedi_clin, clins in groupby(
|
|
||||||
portfolio.active_clins, lambda clin: clin.jedi_clin_type
|
|
||||||
):
|
|
||||||
obligated_funds = sum(clin.obligated_amount for clin in clins)
|
|
||||||
return_dict[jedi_clin.value] = {
|
|
||||||
"obligated_funds": obligated_funds,
|
|
||||||
"expended_funds": (
|
|
||||||
obligated_funds * Decimal(self.MOCK_PERCENT_EXPENDED_FUNDS)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
return OrderedDict(
|
|
||||||
# 0 index for dict item, -1 for last digit of 4 digit CLIN, e.g. 0001
|
|
||||||
sorted(return_dict.items(), key=lambda clin: clin[0][-1])
|
|
||||||
)
|
)
|
||||||
return {}
|
return []
|
||||||
|
|
||||||
def get_expired_task_orders(self, portfolio):
|
@classmethod
|
||||||
def sorted_task_orders(to_list):
|
def _get_environment_monthly_totals(cls, environment):
|
||||||
return sorted(to_list, key=lambda to: to["number"])
|
"""
|
||||||
|
returns a dictionary that represents spending totals for an environment e.g.
|
||||||
|
{
|
||||||
|
name
|
||||||
|
this_month
|
||||||
|
last_month
|
||||||
|
total
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"name": environment["name"],
|
||||||
|
"this_month": sum(environment["spending"]["this_month"].values()),
|
||||||
|
"last_month": sum(environment["spending"]["last_month"].values()),
|
||||||
|
"total": sum(environment["spending"]["total"].values()),
|
||||||
|
}
|
||||||
|
|
||||||
def sorted_clins(clin_list):
|
@classmethod
|
||||||
return sorted(clin_list, key=lambda clin: clin["number"])
|
def _get_application_monthly_totals(cls, application):
|
||||||
|
"""
|
||||||
def serialize_clin(clin):
|
returns a dictionary that represents spending totals for an application
|
||||||
return {
|
and its environments e.g.
|
||||||
"number": clin.number,
|
{
|
||||||
"jedi_clin_type": clin.jedi_clin_type,
|
name
|
||||||
"period_of_performance": {
|
this_month
|
||||||
"start_date": clin.start_date,
|
last_month
|
||||||
"end_date": clin.end_date,
|
total
|
||||||
},
|
environments: [
|
||||||
"total_value": clin.total_amount,
|
{
|
||||||
"total_obligated_funds": clin.obligated_amount,
|
name
|
||||||
"expended_funds": (
|
this_month
|
||||||
clin.obligated_amount * Decimal(self.MOCK_PERCENT_EXPENDED_FUNDS)
|
last_month
|
||||||
),
|
total
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
"""
|
||||||
return sorted_task_orders(
|
environments = sorted(
|
||||||
[
|
[
|
||||||
{
|
cls._get_environment_monthly_totals(env)
|
||||||
"id": task_order.id,
|
for env in application["environments"]
|
||||||
"number": task_order.number,
|
],
|
||||||
"clins": sorted_clins(
|
key=lambda env: env["name"],
|
||||||
[serialize_clin(clin) for clin in task_order.clins]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
for task_order in portfolio.task_orders
|
|
||||||
if task_order.is_expired
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
return {
|
||||||
|
"name": application["name"],
|
||||||
|
"this_month": sum(env["this_month"] for env in environments),
|
||||||
|
"last_month": sum(env["last_month"] for env in environments),
|
||||||
|
"total": sum(env["total"] for env in environments),
|
||||||
|
"environments": environments,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_spending_by_JEDI_clin(cls, portfolio):
|
||||||
|
"""
|
||||||
|
returns an dictionary of spending per JEDI CLIN for a portfolio
|
||||||
|
{
|
||||||
|
jedi_clin: {
|
||||||
|
invoiced
|
||||||
|
estimated
|
||||||
|
},
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if portfolio.name in cls.FIXTURE_SPEND_DATA:
|
||||||
|
CLIN_spend_dict = defaultdict(lambda: defaultdict(Decimal))
|
||||||
|
for application in cls.FIXTURE_SPEND_DATA[portfolio.name]["applications"]:
|
||||||
|
for environment in application["environments"]:
|
||||||
|
for clin, spend in environment["spending"]["this_month"].items():
|
||||||
|
CLIN_spend_dict[clin]["estimated"] += Decimal(spend)
|
||||||
|
for clin, spend in environment["spending"]["total"].items():
|
||||||
|
CLIN_spend_dict[clin]["invoiced"] += Decimal(spend)
|
||||||
|
return CLIN_spend_dict
|
||||||
|
return {}
|
||||||
|
@ -1,15 +1,36 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from itertools import groupby
|
||||||
|
|
||||||
|
|
||||||
class Reports:
|
class Reports:
|
||||||
@classmethod
|
@classmethod
|
||||||
def monthly_totals(cls, portfolio):
|
def monthly_spending(cls, portfolio):
|
||||||
return current_app.csp.reports.monthly_totals(portfolio)
|
return current_app.csp.reports.get_portfolio_monthly_spending(portfolio)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def expired_task_orders(cls, portfolio):
|
def expired_task_orders(cls, portfolio):
|
||||||
return current_app.csp.reports.get_expired_task_orders(portfolio)
|
return [
|
||||||
|
task_order for task_order in portfolio.task_orders if task_order.is_expired
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def obligated_funds_by_JEDI_clin(cls, portfolio):
|
def obligated_funds_by_JEDI_clin(cls, portfolio):
|
||||||
return current_app.csp.reports.get_obligated_funds_by_JEDI_clin(portfolio)
|
clin_spending = current_app.csp.reports.get_spending_by_JEDI_clin(portfolio)
|
||||||
|
active_clins = portfolio.active_clins
|
||||||
|
for jedi_clin, clins in groupby(
|
||||||
|
active_clins, key=lambda clin: clin.jedi_clin_type
|
||||||
|
):
|
||||||
|
if not clin_spending.get(jedi_clin.name):
|
||||||
|
clin_spending[jedi_clin.name] = {}
|
||||||
|
clin_spending[jedi_clin.name]["obligated"] = sum(
|
||||||
|
clin.obligated_amount for clin in clins
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"name": clin,
|
||||||
|
"invoiced": clin_spending[clin].get("invoiced", 0),
|
||||||
|
"estimated": clin_spending[clin].get("estimated", 0),
|
||||||
|
"obligated": clin_spending[clin].get("obligated", 0),
|
||||||
|
}
|
||||||
|
for clin in sorted(clin_spending.keys())
|
||||||
|
]
|
||||||
|
@ -5,6 +5,7 @@ from flask import render_template
|
|||||||
from jinja2 import contextfilter
|
from jinja2 import contextfilter
|
||||||
from jinja2.exceptions import TemplateNotFound
|
from jinja2.exceptions import TemplateNotFound
|
||||||
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
|
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode
|
||||||
|
from decimal import DivisionByZero as DivisionByZeroException
|
||||||
|
|
||||||
|
|
||||||
def iconSvg(name):
|
def iconSvg(name):
|
||||||
@ -38,6 +39,14 @@ def usPhone(number):
|
|||||||
return "+1 ({}) {} - {}".format(phone[0:3], phone[3:6], phone[6:])
|
return "+1 ({}) {} - {}".format(phone[0:3], phone[3:6], phone[6:])
|
||||||
|
|
||||||
|
|
||||||
|
def obligatedFundingGraphWidth(values):
|
||||||
|
numerator, denominator = values
|
||||||
|
try:
|
||||||
|
return (numerator / denominator) * 100
|
||||||
|
except DivisionByZeroException:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def formattedDate(value, formatter="%m/%d/%Y"):
|
def formattedDate(value, formatter="%m/%d/%Y"):
|
||||||
if value:
|
if value:
|
||||||
return value.strftime(formatter)
|
return value.strftime(formatter)
|
||||||
@ -76,6 +85,7 @@ def register_filters(app):
|
|||||||
app.jinja_env.filters["pageWindow"] = pageWindow
|
app.jinja_env.filters["pageWindow"] = pageWindow
|
||||||
app.jinja_env.filters["renderAuditEvent"] = renderAuditEvent
|
app.jinja_env.filters["renderAuditEvent"] = renderAuditEvent
|
||||||
app.jinja_env.filters["withExtraParams"] = with_extra_params
|
app.jinja_env.filters["withExtraParams"] = with_extra_params
|
||||||
|
app.jinja_env.filters["obligatedFundingGraphWidth"] = obligatedFundingGraphWidth
|
||||||
|
|
||||||
@contextfilter
|
@contextfilter
|
||||||
def translateWithoutCache(context, *kwargs):
|
def translateWithoutCache(context, *kwargs):
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from datetime import date, datetime, timedelta
|
from datetime import datetime
|
||||||
|
|
||||||
from flask import redirect, render_template, url_for, request as http_request, g
|
from flask import redirect, render_template, url_for, request as http_request, g
|
||||||
|
|
||||||
@ -35,9 +35,6 @@ def create_portfolio():
|
|||||||
@user_can(Permissions.VIEW_PORTFOLIO_REPORTS, message="view portfolio reports")
|
@user_can(Permissions.VIEW_PORTFOLIO_REPORTS, message="view portfolio reports")
|
||||||
def reports(portfolio_id):
|
def reports(portfolio_id):
|
||||||
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
portfolio = Portfolios.get(g.current_user, portfolio_id)
|
||||||
today = date.today()
|
|
||||||
current_month = date(int(today.year), int(today.month), 15)
|
|
||||||
prev_month = current_month - timedelta(days=28)
|
|
||||||
# wrapped in str() because the sum of obligated funds returns a Decimal object
|
# wrapped in str() because the sum of obligated funds returns a Decimal object
|
||||||
total_portfolio_value = str(
|
total_portfolio_value = str(
|
||||||
sum(
|
sum(
|
||||||
@ -51,10 +48,10 @@ def reports(portfolio_id):
|
|||||||
total_portfolio_value=total_portfolio_value,
|
total_portfolio_value=total_portfolio_value,
|
||||||
current_obligated_funds=Reports.obligated_funds_by_JEDI_clin(portfolio),
|
current_obligated_funds=Reports.obligated_funds_by_JEDI_clin(portfolio),
|
||||||
expired_task_orders=Reports.expired_task_orders(portfolio),
|
expired_task_orders=Reports.expired_task_orders(portfolio),
|
||||||
monthly_totals=Reports.monthly_totals(portfolio),
|
monthly_spending=Reports.monthly_spending(portfolio),
|
||||||
current_month=current_month,
|
current_month=current_month,
|
||||||
prev_month=prev_month,
|
prev_month=prev_month,
|
||||||
now=datetime.now(), # mocked datetime of reporting data retrival
|
retrieved=datetime.now(), # mocked datetime of reporting data retrival
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import { set } from 'vue/dist/vue'
|
import { set } from 'vue/dist/vue'
|
||||||
import { formatDollars } from '../../lib/dollars'
|
import { formatDollars } from '../../lib/dollars'
|
||||||
|
import { set as _set } from 'lodash'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'spend-table',
|
name: 'spend-table',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
applications: Object,
|
applications: Array,
|
||||||
environments: Object,
|
|
||||||
currentMonthIndex: String,
|
|
||||||
prevMonthIndex: String,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
@ -18,20 +16,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
created: function() {
|
created: function() {
|
||||||
Object.keys(this.applications).forEach(application => {
|
this.applicationsState.forEach(application => {
|
||||||
set(this.applicationsState[application], 'isVisible', false)
|
application.isVisible = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
toggle: function(e, applicationName) {
|
toggle: function(e, applicationIndex) {
|
||||||
this.applicationsState = Object.assign(this.applicationsState, {
|
set(this.applicationsState, applicationIndex, {
|
||||||
[applicationName]: Object.assign(
|
...this.applicationsState[applicationIndex],
|
||||||
this.applicationsState[applicationName],
|
isVisible: !this.applicationsState[applicationIndex].isVisible,
|
||||||
{
|
|
||||||
isVisible: !this.applicationsState[applicationName].isVisible,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -73,6 +73,10 @@
|
|||||||
color: $color-green;
|
color: $color-green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: $color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
.user-permission {
|
.user-permission {
|
||||||
font-weight: $font-normal;
|
font-weight: $font-normal;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.jedi-clin-funding {
|
.jedi-clin-funding {
|
||||||
|
$insufficient-gradient: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
$color-secondary-dark,
|
||||||
|
$color-secondary-dark 10px,
|
||||||
|
$color-secondary-darkest 11px,
|
||||||
|
$color-secondary-darkest 14px
|
||||||
|
);
|
||||||
|
|
||||||
|
$graph-bar-height: 2rem;
|
||||||
|
|
||||||
padding-top: $gap * 3;
|
padding-top: $gap * 3;
|
||||||
padding-bottom: $gap * 3;
|
padding-bottom: $gap * 3;
|
||||||
|
|
||||||
@ -37,14 +47,36 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__meter {
|
&__graph {
|
||||||
margin: 10px 0;
|
|
||||||
-moz-transform: scale(-1, 1);
|
|
||||||
-webkit-transform: scale(-1, 1);
|
|
||||||
-o-transform: scale(-1, 1);
|
|
||||||
-ms-transform: scale(-1, 1);
|
|
||||||
transform: scale(-1, 1);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: $graph-bar-height;
|
||||||
|
margin-top: $gap * 2;
|
||||||
|
margin-bottom: $gap * 2;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&-bar {
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
margin-right: $gap / 2;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--invoiced {
|
||||||
|
background: $color-green;
|
||||||
|
}
|
||||||
|
&--estimated {
|
||||||
|
background: $color-green-lighter;
|
||||||
|
}
|
||||||
|
&--remaining {
|
||||||
|
background: $color-primary-darkest;
|
||||||
|
}
|
||||||
|
&--insufficient {
|
||||||
|
background: $insufficient-gradient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-values {
|
&-values {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -52,13 +84,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__meta {
|
&__meta {
|
||||||
&--remaining {
|
margin-right: $gap * 5;
|
||||||
margin-left: auto;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
&-header {
|
&-header {
|
||||||
@include small-copy;
|
@include small-copy;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-key {
|
||||||
|
height: $graph-bar-height;
|
||||||
|
width: $graph-bar-height;
|
||||||
|
margin-right: $gap / 2;
|
||||||
|
|
||||||
|
&--invoiced {
|
||||||
|
background: $color-green;
|
||||||
|
}
|
||||||
|
&--estimated {
|
||||||
|
background: $color-green-lighter;
|
||||||
|
}
|
||||||
|
&--remaining {
|
||||||
|
background: $color-primary-darkest;
|
||||||
|
}
|
||||||
|
&--insufficient {
|
||||||
|
background: $insufficient-gradient;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&-value {
|
&-value {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -24,12 +24,7 @@
|
|||||||
) }}
|
) }}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<spend-table
|
<spend-table v-bind:applications='{{ monthly_spending | tojson }}' inline-template>
|
||||||
v-bind:applications='{{ monthly_totals['applications'] | tojson }}'
|
|
||||||
v-bind:environments='{{ monthly_totals['environments'] | tojson }}'
|
|
||||||
current-month-index='{{ current_month_index }}'
|
|
||||||
prev-month-index='{{ prev_month_index }}'
|
|
||||||
inline-template>
|
|
||||||
<div class="responsive-table-wrapper">
|
<div class="responsive-table-wrapper">
|
||||||
<table class="atat-table">
|
<table class="atat-table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -41,41 +36,41 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for='(application, name) in applicationsState'>
|
<template v-for='(application, applicationIndex) in applicationsState'>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<button v-on:click='toggle($event, name)' class='icon-link icon-link--large'>
|
<button v-on:click='toggle($event, applicationIndex)' class='icon-link icon-link--large'>
|
||||||
<span v-html='name'></span>
|
<span v-html='application.name'></span>
|
||||||
<template v-if='application.isVisible'>{{ Icon('caret_down') }}</template>
|
<template v-if='application.isVisible'>{{ Icon('caret_down') }}</template>
|
||||||
<template v-else>{{ Icon('caret_up') }}</template>
|
<template v-else>{{ Icon('caret_up') }}</template>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell--align-right">
|
<td class="table-cell--align-right">
|
||||||
<span v-html='formatDollars(application[currentMonthIndex] || 0)'></span>
|
<span v-html='formatDollars(application.this_month || 0)'></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell--align-right">
|
<td class="table-cell--align-right">
|
||||||
<span v-html='formatDollars(application[prevMonthIndex] || 0)'></span>
|
<span v-html='formatDollars(application.last_month || 0)'></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell--align-right">
|
<td class="table-cell--align-right">
|
||||||
<span v-html='formatDollars(application["total_spend_to_date"])'></span>
|
<span v-html='formatDollars(application.total)'></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-for='(environment, envName, index) in environments[name]'
|
|
||||||
v-show='application.isVisible'
|
v-show='application.isVisible'
|
||||||
v-bind:class="[ index == Object.keys(environments[name]).length -1 ? 'reporting-spend-table__env-row--last' : '']"
|
v-for='(environment, index) in application.environments'
|
||||||
|
v-bind:class="[ index == application.environments.length -1 ? 'reporting-spend-table__env-row--last' : '']"
|
||||||
>
|
>
|
||||||
<td>
|
<td>
|
||||||
<span class="reporting-spend-table__env-row-label" v-html='envName'></span>
|
<span class="reporting-spend-table__env-row-label" v-html='environment.name'></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell--align-right">
|
<td class="table-cell--align-right">
|
||||||
<span v-html='formatDollars(environment[currentMonthIndex] || 0)'></span>
|
<span v-html='formatDollars(environment.this_month || 0)'></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell--align-right">
|
<td class="table-cell--align-right">
|
||||||
<span v-html='formatDollars(environment[prevMonthIndex] || 0)'></span>
|
<span v-html='formatDollars(environment.last_month || 0)'></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="table-cell--align-right">
|
<td class="table-cell--align-right">
|
||||||
<span v-html='formatDollars(environment["total_spend_to_date"])'></span>
|
<span v-html='formatDollars(environment.total)'></span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
@ -35,13 +35,13 @@
|
|||||||
<div>{{ ("{}".format(clin.jedi_clin_type) | translate)[15:] }}</div>
|
<div>{{ ("{}".format(clin.jedi_clin_type) | translate)[15:] }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ clin.period_of_performance.start_date | formattedDate(formatter="%b %d, %Y") }}
|
{{ clin.start_date | formattedDate(formatter="%b %d, %Y") }}
|
||||||
-
|
-
|
||||||
{{ clin.period_of_performance.end_date | formattedDate(formatter="%b %d, %Y") }}
|
{{ clin.end_date | formattedDate(formatter="%b %d, %Y") }}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ clin.total_value | dollars }}</td>
|
<td>{{ clin.total_amount | dollars }}</td>
|
||||||
<td>{{ clin.total_obligated_funds | dollars }}</td>
|
<td>{{ clin.obligated_amount | dollars }}</td>
|
||||||
<td>{{ (clin.total_obligated_funds - clin.expended_funds) | dollars }}</td>
|
<td>{{ 0 | dollars }}</td>
|
||||||
<tr>
|
<tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -3,28 +3,62 @@
|
|||||||
<section>
|
<section>
|
||||||
<header class="reporting-section-header">
|
<header class="reporting-section-header">
|
||||||
<h2 class="reporting-section-header__header">Current Obligated funds</h2>
|
<h2 class="reporting-section-header__header">Current Obligated funds</h2>
|
||||||
<span class="reporting-section-header__subheader">As of {{ now | formattedDate(formatter="%B %d, %Y at %H:%M") }}</span>
|
<span class="reporting-section-header__subheader">As of {{ retrieved | formattedDate(formatter="%B %d, %Y at %H:%M") }}</span>
|
||||||
</header>
|
</header>
|
||||||
<div class='panel'>
|
<div class='panel'>
|
||||||
<div class='panel__content jedi-clin-funding'>
|
<div class='panel__content jedi-clin-funding'>
|
||||||
{% for JEDI_clin, funds in current_obligated_funds.items() %}
|
{% for JEDI_clin in current_obligated_funds %}
|
||||||
{% set remaining_funds = (funds["obligated_funds"] - funds["expended_funds"]) %}
|
{% set remaining_funds = JEDI_clin.obligated - (JEDI_clin.invoiced + JEDI_clin.estimated) %}
|
||||||
<div class="jedi-clin-funding__clin-wrapper">
|
<div class="jedi-clin-funding__clin-wrapper">
|
||||||
<h3 class="h5 jedi-clin-funding__header">
|
<h3 class="h5 jedi-clin-funding__header">
|
||||||
{{ "JEDICLINType.{}".format(JEDI_clin) | translate }}
|
{{ "JEDICLINType.{}".format(JEDI_clin.name) | translate }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="jedi-clin-funding__subheader">Total obligated amount: {{ funds["obligated_funds"] | dollars }}</p>
|
<p class="jedi-clin-funding__subheader">Total obligated amount: {{ JEDI_clin.obligated | dollars }}</p>
|
||||||
<meter class="jedi-clin-funding__meter" value='{{remaining_funds}}' min='0' max='{{ funds["obligated_funds"] }}' title='{{ JEDI_clin }}'>
|
<div class="jedi-clin-funding__graph">
|
||||||
<div class='jedi-clin-funding__meter-fallback' style='width:{{ (funds["expended_funds"] / funds["obligated_funds"]) * 100 }}%;'></div>
|
{% if remaining_funds < 0 %}
|
||||||
</meter>
|
<span style="width:100%" class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--insufficient"></span>
|
||||||
<div class="jedi-clin-funding__meter-values">
|
{% else %}
|
||||||
|
{% set invoiced_width = (JEDI_clin.invoiced, JEDI_clin.obligated) | obligatedFundingGraphWidth %}
|
||||||
|
{% if invoiced_width %}
|
||||||
|
<span style="width:{{ invoiced_width }}%"
|
||||||
|
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--invoiced">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set estimated_width = (JEDI_clin.estimated, JEDI_clin.obligated) | obligatedFundingGraphWidth %}
|
||||||
|
{% if estimated_width %}
|
||||||
|
<span style="width:{{ (JEDI_clin.estimated, JEDI_clin.obligated) | obligatedFundingGraphWidth }}%"
|
||||||
|
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--estimated">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<span style="width:{{ (remaining_funds, JEDI_clin.obligated) | obligatedFundingGraphWidth }}%"
|
||||||
|
class="jedi-clin-funding__graph-bar jedi-clin-funding__graph-bar--remaining">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="jedi-clin-funding__graph-values">
|
||||||
<div class="jedi-clin-funding__meta">
|
<div class="jedi-clin-funding__meta">
|
||||||
<p class="jedi-clin-funding__meta-header">Funds expended:</p>
|
<p class="jedi-clin-funding__meta-header">
|
||||||
<p class="h3 jedi-clin-funding__meta-value">{{ funds["expended_funds"] | dollars }}</p>
|
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--invoiced"></span>
|
||||||
|
Invoiced expended funds:
|
||||||
|
</p>
|
||||||
|
<p class="h3 jedi-clin-funding__meta-value">{{ JEDI_clin.invoiced | dollars }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="jedi-clin-funding__meta jedi-clin-funding__meta--remaining">
|
<div class="jedi-clin-funding__meta">
|
||||||
<p class="jedi-clin-funding__meta-header">Remaining funds:</p>
|
<p class="jedi-clin-funding__meta-header">
|
||||||
<p class="h3 jedi-clin-funding__meta-value">{{ remaining_funds | dollars }}</p>
|
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--estimated"></span>
|
||||||
|
Estimated expended funds:
|
||||||
|
</p>
|
||||||
|
<p class="h3 jedi-clin-funding__meta-value">{{ JEDI_clin.estimated | dollars }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="jedi-clin-funding__meta">
|
||||||
|
<p class="jedi-clin-funding__meta-header">
|
||||||
|
<span class="jedi-clin-funding__meta-key jedi-clin-funding__meta-key--{{"remaining" if remaining_funds > 0 else "insufficient"}}"></span>
|
||||||
|
Remaining funds:
|
||||||
|
</p>
|
||||||
|
<p class="h3 jedi-clin-funding__meta-value {% if remaining_funds < 0 %}text-danger{% endif %}">{{ remaining_funds | dollars }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user