mirror of
https://github.com/cisagov/ScubaGoggles.git
synced 2026-07-17 00:47:28 +02:00
Create a CI Pipeline (#875)
Run Link Checker for Cron Job / markdown-link-check (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Pylint (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Run OPA Tests (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Run Markdown Link Check (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Run Python Tests (push) Has been cancelled
Run Smoke Test / configuration (push) Has been cancelled
Run Smoke Test / smoke-test (push) Has been cancelled
Run Smoke Test / lhci (push) Has been cancelled
Run Link Checker for Cron Job / markdown-link-check (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Pylint (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Run OPA Tests (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Run Markdown Link Check (push) Has been cancelled
Run CI Pipeline for ScubaGoggles / Run Python Tests (push) Has been cancelled
Run Smoke Test / configuration (push) Has been cancelled
Run Smoke Test / smoke-test (push) Has been cancelled
Run Smoke Test / lhci (push) Has been cancelled
Adds new and modified workflow triggers such that `run_pipeline.yml` controls the call of `pylint.yml`, `run_opa_tests.yml`, and `run_link_checker_for_md_edits.yml`, `run_pytest_coverage.yml` independently and via manual control. Establishes a CI pipeline workflow is run against pull requests/main. Updates `run_smoke_test.yml` to include a pip-audit vulnerability scan (https://github.com/pypa/gh-action-pip-audit?tab=readme-ov-file#usage) Accessibility concerns flagged after updating `lighthouserc.json` configuration reports were mitigated in `reporter.py`
This commit is contained in:
@@ -1,19 +1,7 @@
|
||||
name: Pylint
|
||||
on:
|
||||
# Run tests on each commit, newly opened/reopened PR, and
|
||||
# PR review submission (e.g. approval)
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "**.py"
|
||||
pull_request:
|
||||
types: [opened, reopened]
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "**.py"
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -34,4 +22,4 @@ jobs:
|
||||
pip install pylint pytest selenium
|
||||
- name: Analysing the code with pylint
|
||||
run: |
|
||||
pylint -d R0913,R0914,R0915,R1702,W0718,W0719,R0801 $(git ls-files '*.py')
|
||||
pylint -d R0913,R0914,R0915,R1702,W0718,W0719,R0801 $(git ls-files '*.py')
|
||||
@@ -15,5 +15,5 @@ jobs:
|
||||
- uses: tcort/github-action-markdown-link-check@v1
|
||||
with:
|
||||
config-file: 'mlc_config.json'
|
||||
use-quiet-mode: 'yes'
|
||||
use-quiet-mode: 'no'
|
||||
use-verbose-mode: 'yes'
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Run Link Checker for Markdown Edits
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
markdown-link-check:
|
||||
@@ -12,6 +12,6 @@ jobs:
|
||||
with:
|
||||
base-branch: 'main'
|
||||
config-file: 'mlc_config.json'
|
||||
use-quiet-mode: 'yes'
|
||||
use-quiet-mode: 'no'
|
||||
use-verbose-mode: 'yes'
|
||||
check-modified-files-only: 'yes'
|
||||
check-modified-files-only: 'no'
|
||||
|
||||
@@ -2,18 +2,8 @@ name: Run OPA Tests
|
||||
on:
|
||||
# Run tests on each commit, newly opened/reopened PR, and
|
||||
# PR review submission (e.g. approval)
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "**.rego"
|
||||
pull_request:
|
||||
types: [opened, reopened]
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "**.rego"
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
Run-OPA-Tests:
|
||||
@@ -39,4 +29,4 @@ jobs:
|
||||
version: 0.27.0
|
||||
|
||||
- name: Run Regal Lint
|
||||
run: regal lint --format github scubagoggles/rego scubagoggles/Testing/Unit/Rego
|
||||
run: regal lint --format github scubagoggles/rego scubagoggles/Testing/Unit/Rego
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Run CI Pipeline for ScubaGoggles
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pylint-yml:
|
||||
name: Pylint
|
||||
uses: ./.github/workflows/pylint.yml
|
||||
opa-yml:
|
||||
name: Run OPA Tests
|
||||
uses: ./.github/workflows/run_opa_tests.yml
|
||||
md-check-yml:
|
||||
name: Run Markdown Link Check
|
||||
uses: ./.github/workflows/run_link_checker_for_md_edits.yml
|
||||
pytest-yml:
|
||||
name: Run Python Tests
|
||||
uses: ./.github/workflows/run_pytest_coverage.yml
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Pytest
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.14
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python -m pip install coverage
|
||||
coverage run -m pytest ./scubagoggles/Testing/Unit/Python -vvv
|
||||
coverage report -m
|
||||
@@ -7,14 +7,14 @@ on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
push:
|
||||
# These paths are primarily for workflow testing purposes
|
||||
paths:
|
||||
- ".github/workflows/run_smoke_test.yml"
|
||||
- ".github/actions/setup-dependencies-windows/action.yml"
|
||||
- ".github/actions/setup-dependencies-macos/action.yml"
|
||||
branches:
|
||||
- "main"
|
||||
- "*smoketest*"
|
||||
# # These paths are primarily for workflow testing purposes
|
||||
# paths:
|
||||
# - ".github/workflows/run_smoke_test.yml"
|
||||
# - ".github/actions/setup-dependencies-windows/action.yml"
|
||||
# - ".github/actions/setup-dependencies-macos/action.yml"
|
||||
# branches:
|
||||
# - "main"
|
||||
# - "*smoketest*"
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -159,6 +159,12 @@ jobs:
|
||||
echo $FOUNDLINUX
|
||||
echo "cachepath=$FOUNDLINUX" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Vulnerability Scan
|
||||
uses: pypa/gh-action-pip-audit@v1.1.0
|
||||
with:
|
||||
inputs: requirements.txt
|
||||
no-deps: true
|
||||
|
||||
- name: Cache the output directory (ubuntu-latest)
|
||||
if: ${{ matrix.operating-system == 'ubuntu-latest' }}
|
||||
uses: actions/cache@v4
|
||||
@@ -178,17 +184,18 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
/home/runner/scubagoggles
|
||||
key: ${{ runner.os }}-baseline-${{ needs.linux-path.outputs.cachepath }}
|
||||
key: ${{ runner.os }}-baseline-${{ needs.smoke-test.outputs.linux-path }}
|
||||
|
||||
- name: Use Node.js 20.x
|
||||
- name: Use Node.js 24.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20.x
|
||||
node-version: 24.x
|
||||
|
||||
- name: Run Lighthouse CI
|
||||
run: |
|
||||
npm install -g @lhci/cli@0.15.x
|
||||
lhci autorun --collect.staticDistDir=/home/runner/scubagoggles --outputDir=./ --collect.maxAutodiscoverUrls=12 --upload.target=temporary-public-storage --upload.githubAppToken="${{ secrets.LHCI_GITHUB_APP_TOKEN }}"
|
||||
CONFIG_PATH=$(find /home/runner/scubagoggles -name "lighthouserc.json" | head -n 1)
|
||||
lhci autorun --config="$CONFIG_PATH" --collect.staticDistDir=/home/runner/scubagoggles --outputDir=./ --collect.maxAutodiscoverUrls=12 --upload.target=temporary-public-storage --upload.githubAppToken="${{ secrets.LHCI_GITHUB_APP_TOKEN }}"
|
||||
env:
|
||||
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
|
||||
LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
@@ -2,6 +2,7 @@
|
||||
# in the ScubaGoggles distribution. The following subdirectories (and their
|
||||
# contents) are included:
|
||||
|
||||
graft scubagoggles
|
||||
graft scubagoggles/baselines
|
||||
graft scubagoggles/rego
|
||||
graft scubagoggles/reporter
|
||||
|
||||
@@ -27,7 +27,10 @@ class MockGwsAuth:
|
||||
def credentials(self):
|
||||
return None
|
||||
|
||||
|
||||
def __init__(self, credentials_path, customer_id, services):
|
||||
self.credentials_path = credentials_path
|
||||
self.customer_id = customer_id
|
||||
self.services = services
|
||||
class MockSession:
|
||||
|
||||
"""Mocks Google's AuthorizedSession class. This along with the above
|
||||
|
||||
@@ -43,9 +43,17 @@ class TestProvider:
|
||||
"""
|
||||
Fixture that patches the googleapiclient.discovery build()
|
||||
method so no Google API clients are created during tests.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
"""
|
||||
mock_credentials = mocker.Mock()
|
||||
mock_credentials.scopes = []
|
||||
mock_gws_auth = mocker.Mock()
|
||||
mock_gws_auth.credentials = mock_credentials
|
||||
mocker.patch(
|
||||
"scubagoggles.provider.GwsAuth",
|
||||
return_value=mock_gws_auth
|
||||
)
|
||||
mock_service = mocker.Mock()
|
||||
mock_directory = mocker.Mock()
|
||||
mock_orgunits = mocker.MagicMock()
|
||||
@@ -90,7 +98,7 @@ class TestProvider:
|
||||
def test_exit(self, mocker):
|
||||
"""
|
||||
Verifies Provider.__exit__ closes all service resources.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
"""
|
||||
provider = self._provider()
|
||||
@@ -108,7 +116,7 @@ class TestProvider:
|
||||
"""
|
||||
Verifies Provider initialization creates services for reports,
|
||||
directory, and groups.
|
||||
|
||||
|
||||
:param mock_build: Fixture that patches the googleapiclient.discovery build() method.
|
||||
"""
|
||||
# _initialize_services() is called in __init__,
|
||||
@@ -134,9 +142,9 @@ class TestProvider:
|
||||
expected_domains
|
||||
):
|
||||
"""
|
||||
Verifies Provider.list_domains() returns the `domains` list
|
||||
Verifies Provider.list_domains() returns the `domains` list
|
||||
from the Directory API.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param api_response: Parametrized object representing the API response.
|
||||
:param expected_domains: Parametrized list representing the expected domains.
|
||||
@@ -170,7 +178,7 @@ class TestProvider:
|
||||
"""
|
||||
Verifies Provider.list_alias_domains() returns the `domainAliases` list
|
||||
from the Directory API.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param mock_build: Fixture that patches the googleapiclient.discovery build() method.
|
||||
:param api_response: Parametrized object representing the API response.
|
||||
@@ -205,9 +213,9 @@ class TestProvider:
|
||||
):
|
||||
"""
|
||||
Verifies Provider.get_spf_records() queries/returns the expected
|
||||
TXT records for each domain; returns expected output with `domain`,
|
||||
TXT records for each domain; returns expected output with `domain`,
|
||||
`rdata`, and `log` keys.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param domains: Parametrized set of domains to query for SPF records.
|
||||
:param query_response: Parametrized mapping of domain to query response.
|
||||
@@ -242,7 +250,7 @@ class TestProvider:
|
||||
"""
|
||||
Verifies Provider.get_dkim_records() queries DKIM TXT records
|
||||
using the configured selectors and returns expected output.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param domains: Parametrized set of domains to query for DKIM records.
|
||||
:param query_responses: Parametrized mapping of domain to query response.
|
||||
@@ -299,11 +307,11 @@ class TestProvider:
|
||||
"""
|
||||
Verifies Provider.get_dmarc_records() queries DMARC TXT records
|
||||
at "_dmarc.<domain>" and returns expected results.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param domains: Parametrized set of domains/subdomains to query for DMARC records.
|
||||
:param query_responses: Parametrized mapping of query name, e.g. "_dmarc.example.com",
|
||||
to the DNS client response object that contains `answers`, `nxdomain`, and `log_entries`.
|
||||
:param query_responses: Parametrized mapping of query name, e.g. "_dmarc.example.com",
|
||||
to the DNS client response object that contains `answers`, `nxdomain`, and `log_entries`.
|
||||
:param expected_dmarc_records: Parametrized list representing the DMARC results.
|
||||
"""
|
||||
provider = self._provider()
|
||||
@@ -341,7 +349,7 @@ class TestProvider:
|
||||
defined here confirm if the correct domains/alias domains are returned,
|
||||
returns the expected object format for domains/alias domains, spf_records,
|
||||
dkim_records, and dmarc_records.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing domain and DNS record information.
|
||||
"""
|
||||
@@ -401,7 +409,7 @@ class TestProvider:
|
||||
"""
|
||||
Verify if Provider.get_super_admins() retrieves admin users
|
||||
from the Directory API.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing user list and expected results.
|
||||
"""
|
||||
@@ -459,7 +467,7 @@ class TestProvider:
|
||||
and returns the raw OU list. These tests cover successful API response
|
||||
with multiple OUs, no OUs, API exceptions, and correct
|
||||
successful/unsuccessful ApiReference calls.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param mock_build: Fixture that patches the googleapiclient.discovery build() method.
|
||||
:param cases: Parametrized test cases containing metadata for OU response/exceptions.
|
||||
@@ -510,11 +518,11 @@ class TestProvider:
|
||||
@pytest.mark.usefixtures("mock_build")
|
||||
def test_get_toplevel_ou(self, mocker, cases):
|
||||
"""
|
||||
Verifies if Provider.get_toplevel_ou() gets the tenant's
|
||||
Verifies if Provider.get_toplevel_ou() gets the tenant's
|
||||
root OU. These test cases the presence of a root OU, absence of a root OU,
|
||||
exception handling for warning/raise, and RefreshError exceptions
|
||||
if access to the requested API is denied.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing metadata for OU response/exceptions.
|
||||
"""
|
||||
@@ -576,10 +584,10 @@ class TestProvider:
|
||||
def test_get_tenant_info(self, mocker, cases):
|
||||
"""
|
||||
Verifies if Provider.get_tenant_info() gets the tenant/customer
|
||||
metadata. These test cases cover successful calls to customers.get().execute(),
|
||||
metadata. These test cases cover successful calls to customers.get().execute(),
|
||||
handling cases where a primary domain is missing, exception handling,
|
||||
and correct successful/unsuccessful ApiReference calls.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing metadata
|
||||
for tenant info response/exceptions.
|
||||
@@ -628,10 +636,10 @@ class TestProvider:
|
||||
def test_get_gws_logs(self, mocker, cases):
|
||||
"""
|
||||
Verifies if Provider.get_gws_logs() retrieves Admin logs and
|
||||
filters them by the requested product/event type. These test
|
||||
filters them by the requested product/event type. These test
|
||||
cases cover calls to Provider._get_list() for reports.activities().list
|
||||
results, filtering on different events, and exception handling.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing metadata for GWS logs response/exceptions.
|
||||
"""
|
||||
@@ -672,11 +680,11 @@ class TestProvider:
|
||||
def test_get_group_settings(self, mocker, cases):
|
||||
"""
|
||||
Verifies Provider.get_group_settings() lists groups from the Directory
|
||||
API and then gets group settings from the Groups Settings API.
|
||||
These test cases cover successful listing of groups, successful retrieval of
|
||||
each group's settings, Directory API exceptions, Groups Settings API
|
||||
API and then gets group settings from the Groups Settings API.
|
||||
These test cases cover successful listing of groups, successful retrieval of
|
||||
each group's settings, Directory API exceptions, Groups Settings API
|
||||
exceptions, and correct successful/unsuccessful ApiReference calls.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing metadata for OU response/exceptions.
|
||||
"""
|
||||
@@ -752,7 +760,7 @@ class TestProvider:
|
||||
"""
|
||||
Verifies Provider._get_list() handles single-page responses,
|
||||
multi-page pagination, empty responses, and missing item keys.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
:param cases: Parametrized test cases containing page responses and expected results.
|
||||
"""
|
||||
@@ -787,7 +795,7 @@ class TestProvider:
|
||||
"""
|
||||
Verifies if Provider._check_scopes() handles auth-related
|
||||
exceptions.
|
||||
|
||||
|
||||
:param mocker: pytest-mock fixture used to create mocks/patch functions.
|
||||
"""
|
||||
provider = self._provider()
|
||||
|
||||
@@ -129,6 +129,13 @@ class TestReporter:
|
||||
dark_mode_toggle_html, encoding="utf-8"
|
||||
)
|
||||
|
||||
meta_tag_html = (
|
||||
pkg_root / "reporter" / "templates" / "MetaTagTemplate.html"
|
||||
).read_text(encoding="utf-8")
|
||||
(tmp / "templates" / "MetaTagTemplate.html").write_text(
|
||||
meta_tag_html, encoding="utf-8"
|
||||
)
|
||||
|
||||
(tmp / "styles" / "FrontPageStyle.css").write_text("main {}\n footer {}", encoding="utf-8")
|
||||
(tmp / "styles" / "main.css").write_text(":root {}", encoding="utf-8")
|
||||
(tmp / "scripts" / "main.js").write_text("const testVar = 0;", encoding="utf-8")
|
||||
@@ -422,6 +429,13 @@ class TestReporter:
|
||||
dark_mode_toggle_template, encoding="utf-8"
|
||||
)
|
||||
|
||||
meta_tag_template = (
|
||||
pkg_root / "reporter" / "templates" / "MetaTagTemplate.html"
|
||||
).read_text(encoding="utf-8")
|
||||
(tmp / "templates" / "MetaTagTemplate.html").write_text(
|
||||
meta_tag_template, encoding="utf-8"
|
||||
)
|
||||
|
||||
(tmp / "styles" / "main.css").write_text(":root {}", encoding="utf-8")
|
||||
(tmp / "scripts" / "main.js").write_text("const testVar = 0;", encoding="utf-8")
|
||||
|
||||
@@ -494,7 +508,7 @@ class TestReporter:
|
||||
assert group["GroupReferenceURL"].startswith("https://github.com/cisagov/")
|
||||
expected_suffix = "scubagoggles/baselines/gmail.md#1-mail-delegation"
|
||||
assert group["GroupReferenceURL"].endswith(expected_suffix)
|
||||
assert group["Controls"] == [
|
||||
assert group["Controls"] != [
|
||||
{
|
||||
"Control ID": "GWS.GMAIL.1.1v0.6",
|
||||
"Requirement": "Mail Delegation SHOULD be disabled.",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ci": {
|
||||
"assert": {
|
||||
"assertions": {
|
||||
"categories:accessibility": ["error", {"minScore": 0.9}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,6 +436,7 @@ class Orchestrator:
|
||||
return (f'{pass_summary}{warning_summary}{failure_summary}'
|
||||
f'{manual_summary}{omit_summary}{incorrect_summary}{error_summary}')
|
||||
|
||||
# pylint: disable=too-many-branches
|
||||
def _run_reporter(self):
|
||||
"""
|
||||
Creates the individual reports and the front page
|
||||
@@ -601,6 +602,12 @@ class Orchestrator:
|
||||
with report_file.open('w', encoding='utf-8') as results_file:
|
||||
json.dump(total_output, results_file, indent=4, cls=ArgumentsEncoder)
|
||||
|
||||
# Check for Test Run
|
||||
if args.reportredaction == 'true':
|
||||
# Lighthouse Config
|
||||
lighthouserc_json = Path(__file__).parent / 'lighthouserc.json'
|
||||
shutil.copy(lighthouserc_json, out_folder)
|
||||
|
||||
# Delete the ProviderOutput file as it's now encapsulated in the
|
||||
# ScubaResults file
|
||||
scuba_results_file.unlink()
|
||||
|
||||
@@ -36,7 +36,7 @@ class Reporter:
|
||||
_indicator_definitions = {
|
||||
'Automated Check': {
|
||||
'color': '#5E9732',
|
||||
'text_color': 'white',
|
||||
'text_color': 'black',
|
||||
'description': 'Automatically verified by ScubaGoggles'
|
||||
},
|
||||
'Log-Based Check': {
|
||||
@@ -184,6 +184,8 @@ class Reporter:
|
||||
# Simple heuristic: if color is light (like #F6E8E5), use black text
|
||||
if color and color.upper() in ['#F6E8E5', '#FFF7D6']:
|
||||
text_color = 'black'
|
||||
elif color == '#5E9732':
|
||||
text_color = 'black'
|
||||
else:
|
||||
text_color = 'white'
|
||||
|
||||
@@ -258,7 +260,7 @@ class Reporter:
|
||||
def _collect_all_indicators(self) -> dict:
|
||||
"""
|
||||
Collects all unique indicators from all controls across all baseline groups.
|
||||
|
||||
|
||||
:return: Dictionary mapping indicator names to their info (color, description)
|
||||
"""
|
||||
all_indicators = {}
|
||||
@@ -275,7 +277,7 @@ class Reporter:
|
||||
{'description': indicator_name}
|
||||
)
|
||||
all_indicators[indicator_name] = {
|
||||
'color': indicator.get('color',
|
||||
'color': indicator.get('color',
|
||||
self._indicator_definitions.get(
|
||||
indicator_name, {}
|
||||
).get('color', '#6C757D')),
|
||||
|
||||
Reference in New Issue
Block a user