Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
680 changes: 345 additions & 335 deletions assets/css/style.css

Large diffs are not rendered by default.

47 changes: 40 additions & 7 deletions assets/template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ <h6 class="fw-semibold mb-1">Filters</h6>
</div>
<div class="drop-body">
<div class="form-group mb-3">
<label for="resourceTypeSelect" class="form-label mb-2">Resource</label>
<select class="form-select" id="resourceTypeSelect">
<option value="all" selected>Select Resource Type</option>
{% for resource in resource_inventory %}
Expand All @@ -562,6 +563,17 @@ <h6 class="fw-semibold mb-1">Filters</h6>
{% endfor %}
</select>
</div>
<div class="form-group mb-3">
<label for="regionSelect" class="form-label mb-2">Region</label>
<select class="form-select" id="regionSelect">
<option value="all" selected>All Regions</option>
<option value="european-union">European Union</option>
<option value="united-kingdom">United Kingdom</option>
<option value="switzerland">Switzerland</option>
<option value="united-states">United States</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-toggle-switch mb-3 d-flex justify-content-between align-items-center">
<h6 class="mb-0">Open Source</h6>
<div class="toggle-switch">
Expand Down Expand Up @@ -596,24 +608,37 @@ <h6 class="mb-0">Enterprise Support</h6>
<div class="p-3 p-lg-4">
<div class="row gapy-3" id="alt-tech-grid">
{% if alternative_technologies %} {% for alt_tech in
alternative_technologies %} {% set alt_resource = namespace(name="Resource Type " ~ alt_tech.resource_type_id) %}
{% for resource in resource_inventory %}
{% if resource.resource_type|string == alt_tech.resource_type_id|string %}
{% set alt_resource.name = resource.name %}
{% endif %}
{% endfor %}
alternative_technologies %}
<div
class="col-lg-6 alt-tech-item alt-tech-card-item"
data-resource-type="{{ alt_tech.resource_type_id }}"
data-open-source="{{ 'true' if alt_tech.open_source else 'false' }}"
data-enterprise-support="{{ 'true' if alt_tech.support_plan else 'false' }}"
data-org-region="{{ alt_tech.organization_region }}"
data-org-country="{{ alt_tech.organization_country_code }}"
>
<div class="p-3 p-lg-4 rounded-4 alttech-card">
<div
class="d-flex align-items-center justify-content-between alttech-title"
>
<div>
<h6 class="mb-1">Category: {{ alt_resource.name }}</h6>
<h6 class="mb-1 d-flex align-items-center gap-2 flex-wrap">
<i class="bi bi-building"></i>
<span>{{ alt_tech.organization_name }}</span>
{% if alt_tech.organization_url %}
<a
href="{{ alt_tech.organization_url }}{% if '?' in alt_tech.organization_url %}&{% else %}?{% endif %}utm_source=escapecloud&utm_medium=referral"
target="_blank"
rel="noopener noreferrer"
aria-label="Visit {{ alt_tech.organization_name }}"
class="d-inline-flex align-items-center text-decoration-none"
>
<i class="bi bi-box-arrow-up-right"></i>
</a>
{% endif %}
<span class="text-muted">|</span>
<span>{% if alt_tech.organization_flag %}{{ alt_tech.organization_flag }} {% endif %}{{ alt_tech.organization_country_code }} • {{ alt_tech.organization_region_label }}</span>
</h6>
<h3 class="mb-0">{{ alt_tech.product_name }}</h3>
</div>
</div>
Expand Down Expand Up @@ -816,6 +841,7 @@ <h3 class="mb-0">{{ alt_tech.product_name }}</h3>
const applyFiltersBtn = document.getElementById("applyFilters");
const clearFiltersBtn = document.getElementById("clearFilters");
const resourceTypeSelect = document.getElementById("resourceTypeSelect");
const regionSelect = document.getElementById("regionSelect");
const openSourceSwitch = document.getElementById("openSourceSwitch");
const enterpriseSupportSwitch = document.getElementById(
"enterpriseSupportSwitch"
Expand All @@ -828,6 +854,7 @@ <h3 class="mb-0">{{ alt_tech.product_name }}</h3>
applyFiltersBtn &&
clearFiltersBtn &&
resourceTypeSelect &&
regionSelect &&
openSourceSwitch &&
enterpriseSupportSwitch &&
searchInput &&
Expand Down Expand Up @@ -866,6 +893,7 @@ <h3 class="mb-0">{{ alt_tech.product_name }}</h3>

function applyAlternativeFilters() {
const selectedResourceType = resourceTypeSelect.value;
const selectedRegion = regionSelect.value;
const isOpenSource = openSourceSwitch.checked;
const hasEnterpriseSupport = enterpriseSupportSwitch.checked;
const searchQuery = searchInput.value.trim().toLowerCase();
Expand All @@ -874,6 +902,9 @@ <h3 class="mb-0">{{ alt_tech.product_name }}</h3>
const matchesResourceType =
selectedResourceType === "all" ||
box.dataset.resourceType === selectedResourceType;
const matchesRegion =
selectedRegion === "all" ||
box.dataset.orgRegion === selectedRegion;
const matchesOpenSource =
!isOpenSource || box.dataset.openSource === "true";
const matchesEnterpriseSupport =
Expand All @@ -883,6 +914,7 @@ <h3 class="mb-0">{{ alt_tech.product_name }}</h3>

box.dataset.filtered =
matchesResourceType &&
matchesRegion &&
matchesOpenSource &&
matchesEnterpriseSupport &&
matchesSearch
Expand All @@ -899,6 +931,7 @@ <h3 class="mb-0">{{ alt_tech.product_name }}</h3>

clearFiltersBtn.addEventListener("click", function () {
resourceTypeSelect.value = "all";
regionSelect.value = "all";
openSourceSwitch.checked = false;
enterpriseSupportSwitch.checked = false;
searchInput.value = "";
Expand Down
4 changes: 4 additions & 0 deletions core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,9 @@ def generate_report(
risk_definitions = load_data("risk", db_path=db_path)
alternatives = load_data("alternative", db_path=db_path)
alternative_technologies = load_data("alternativetechnology", db_path=db_path)
alternative_technology_organizations = load_data(
"alternativetechnologyorganization", db_path=db_path
)
resource_inventory = load_data("resource_inventory", db_path=db_path)
cost_data = load_data("cost_inventory", db_path=db_path)
risk_data = load_data("risk_inventory", db_path=db_path)
Expand Down Expand Up @@ -481,6 +484,7 @@ def generate_report(
alternatives,
alternative_technologies,
exit_strategy,
alternative_technology_organizations,
)

# Generate PDF report
Expand Down
1 change: 1 addition & 0 deletions core/utils_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"scoring_data",
"alternative",
"alternativetechnology",
"alternativetechnologyorganization",
"risk",
}

Expand Down
7 changes: 6 additions & 1 deletion core/utils_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def generate_html_report(
alternatives: list[dict[str, Any]],
alternative_technologies: list[dict[str, Any]],
exit_strategy: int,
alternative_technology_organizations: list[dict[str, Any]] | None = None,
) -> str:

# Transform resource inventory
Expand Down Expand Up @@ -117,7 +118,11 @@ def generate_html_report(

# Transform alternative technologies
alternative_technologies_data = transform_alt_tech_for_html(
resource_inventory, alternatives, alternative_technologies, exit_strategy
resource_inventory,
alternatives,
alternative_technologies,
exit_strategy,
alternative_technology_organizations=alternative_technology_organizations,
)

# Scoring Data
Expand Down
86 changes: 86 additions & 0 deletions core/utils_report_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,73 @@
"EUR": "€",
}

EU_COUNTRY_CODES = {
"AT",
"BE",
"BG",
"HR",
"CY",
"CZ",
"DK",
"EE",
"FI",
"FR",
"DE",
"GR",
"HU",
"IE",
"IT",
"LV",
"LT",
"LU",
"MT",
"NL",
"PL",
"PT",
"RO",
"SK",
"SI",
"ES",
"SE",
}

REGION_LABELS = {
"european-union": "European Union",
"united-kingdom": "United Kingdom",
"switzerland": "Switzerland",
"united-states": "United States",
"other": "Other",
}


def normalize_country_code(country_code: Any) -> str | None:
if not isinstance(country_code, str):
return None
normalized = country_code.strip().upper()
return normalized if len(normalized) == 2 and normalized.isalpha() else None


def country_code_to_region(country_code: Any) -> str:
normalized = normalize_country_code(country_code)
if not normalized:
return "other"
if normalized in EU_COUNTRY_CODES:
return "european-union"
if normalized == "GB":
return "united-kingdom"
if normalized == "CH":
return "switzerland"
if normalized == "US":
return "united-states"
return "other"


def country_code_to_flag(country_code: Any) -> str:
normalized = normalize_country_code(country_code)
if not normalized:
return ""
return chr(127397 + ord(normalized[0])) + chr(127397 + ord(normalized[1]))


def sort_cost_data(cost_data: list[dict[str, Any]]) -> list[dict[str, Any]]:
return sorted(cost_data, key=lambda x: datetime.strptime(x["month"], "%Y-%m-%d"))
Expand Down Expand Up @@ -119,6 +186,7 @@ def summarize_alternative_technologies(
alternatives: list[dict[str, Any]],
alternative_technologies: list[dict[str, Any]],
exit_strategy: int,
alternative_technology_organizations: list[dict[str, Any]] | None = None,
) -> dict[str, list[dict[str, Any]]]:
active_technologies = {
tech["id"]: tech
Expand All @@ -129,6 +197,9 @@ def summarize_alternative_technologies(
grouped_alt_tech: dict[str, list[dict[str, Any]]] = {
str(resource["resource_type"]): [] for resource in resource_inventory
}
organization_by_id = {
org["id"]: org for org in (alternative_technology_organizations or [])
}

for alt in alternatives:
if str(alt["strategy_type"]) != str(exit_strategy):
Expand All @@ -138,6 +209,11 @@ def summarize_alternative_technologies(
tech = active_technologies.get(alt["alternative_technology"])
if not tech or resource_type not in grouped_alt_tech:
continue
organization = organization_by_id.get(tech.get("organization_id"))
organization_country_code = normalize_country_code(
organization.get("country_code") if organization else None
)
organization_region = country_code_to_region(organization_country_code)

grouped_alt_tech[resource_type].append(
{
Expand All @@ -147,6 +223,16 @@ def summarize_alternative_technologies(
"open_source": tech["open_source"] == "t",
"support_plan": tech["support_plan"] == "t",
"status": tech["status"] == "t",
"organization_name": (
organization.get("name") if organization else "Unknown Organization"
),
"organization_url": organization.get("url") if organization else None,
"organization_country_code": organization_country_code or "N/A",
"organization_region": organization_region,
"organization_region_label": REGION_LABELS.get(
organization_region, "Other"
),
"organization_flag": country_code_to_flag(organization_country_code),
}
)

Expand Down
2 changes: 2 additions & 0 deletions core/utils_report_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ def transform_alt_tech_for_html(
alternatives: list[dict[str, Any]],
alternative_technologies: list[dict[str, Any]],
exit_strategy: int,
alternative_technology_organizations: list[dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
alt_tech_data = []
grouped_alt_tech = summarize_alternative_technologies(
resource_inventory,
alternatives,
alternative_technologies,
exit_strategy,
alternative_technology_organizations=alternative_technology_organizations,
)
for resource in resource_inventory:
resource_type = str(resource.get("resource_type"))
Expand Down
13 changes: 13 additions & 0 deletions tests/report_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ def build_report_fixture():
"open_source": "t",
"support_plan": "t",
"status": "t",
"organization_id": 10,
}
]
alternative_technology_organizations = [
{
"id": 10,
"name": "OpenInfra Foundation",
"url": "https://openinfra.org/",
"country_code": "US",
"stability_tier": 5,
"years_in_business": 14,
}
]
return {
Expand All @@ -69,6 +80,7 @@ def build_report_fixture():
"risk_data": risk_data,
"alternatives": alternatives,
"alternative_technologies": alternative_technologies,
"alternative_technology_organizations": alternative_technology_organizations,
"exit_strategy": 1,
}

Expand Down Expand Up @@ -96,6 +108,7 @@ def build_empty_report_fixture():
"risk_data": [],
"alternatives": [],
"alternative_technologies": [],
"alternative_technology_organizations": [],
"exit_strategy": 1,
}

Expand Down
5 changes: 5 additions & 0 deletions tests/test_report_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def test_generate_html_report_creates_expected_output(self):
fixture["alternatives"],
fixture["alternative_technologies"],
fixture["exit_strategy"],
fixture["alternative_technology_organizations"],
)

self.assertTrue(Path(html_path).exists())
Expand All @@ -41,6 +42,9 @@ def test_generate_html_report_creates_expected_output(self):
self.assertIn("Smoke Test Assessment", html)
self.assertIn("Amazon Web Services", html)
self.assertIn("OpenStack", html)
self.assertIn("OpenInfra Foundation", html)
self.assertIn("All Regions", html)
self.assertIn("data-org-region", html)
self.assertIn("EC2 Instance", html)

def test_generate_html_report_renders_empty_state_output(self):
Expand All @@ -59,6 +63,7 @@ def test_generate_html_report_renders_empty_state_output(self):
fixture["alternatives"],
fixture["alternative_technologies"],
fixture["exit_strategy"],
fixture["alternative_technology_organizations"],
)

self.assertTrue(Path(html_path).exists())
Expand Down
Loading