github-security-report 0.17.0__tar.gz → 0.18.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {github_security_report-0.17.0 → github_security_report-0.18.0}/PKG-INFO +36 -2
- {github_security_report-0.17.0 → github_security_report-0.18.0}/README.md +34 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/pyproject.toml +2 -2
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/_version.py +2 -2
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/extras.py +2 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/config/loader.py +27 -16
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/config/models.py +6 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/config/schema.py +6 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/issues.py +73 -3
- {github_security_report-0.17.0 → github_security_report-0.18.0}/.gitignore +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/LICENSE +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/LICENSES/Apache-2.0.txt +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/scripts/README.md +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/authors.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/categories/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/categories/keys.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/categories/signals.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/categories/tables.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/classify.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/__main__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/app.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/boundary.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/modes.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/options.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/outputs.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/publish.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/cli/serialise.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/alerts.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/batch_errors.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/codeql_parsers.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/copilot.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/endpoints.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/errors.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/org_reads.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/parsers.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/queries.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/reads.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/reviews.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/transport.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/client/writes.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/codeql/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/codeql/cleanup.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/codeql/facts.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/codeql/tables.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/codeql.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/context.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/org.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/protocols.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/repo.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/collect/scoping.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/config/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/config/order.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/gating.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/gitctx.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/layout.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/models.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/ordering.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/posture/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/posture/enablement.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/posture/facts.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/posture/releases.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/pulls/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/pulls/columns.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/pulls/counting.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/pulls/presentation.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/pulls/table.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/py.typed +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/ranking.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/remediate/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/remediate/codeql.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/remediate/model.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/render/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/render/html.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/render/markdown.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/render/slack.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/render/slack_limits.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/render/terminal.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/report/__init__.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/report/aggregate.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/report/display.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/report/signals.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/report/tables.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/rulesets.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/runner.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/scope.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/secret_patterns.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/severity.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/summary.py +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/templates/index.html.j2 +0 -0
- {github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/templates/report.html.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: github-security-report
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Security and quality reporting across GitHub organisations
|
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/github-security-report-action
|
|
6
6
|
Project-URL: Repository, https://github.com/lfreleng-actions/github-security-report-action
|
|
@@ -36,7 +36,7 @@ Requires-Dist: pytest-asyncio==1.4.0; extra == 'dev'
|
|
|
36
36
|
Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
|
|
37
37
|
Requires-Dist: pytest==9.1.1; extra == 'dev'
|
|
38
38
|
Requires-Dist: respx==0.23.1; extra == 'dev'
|
|
39
|
-
Requires-Dist: ruff==0.16.
|
|
39
|
+
Requires-Dist: ruff==0.16.9; extra == 'dev'
|
|
40
40
|
Requires-Dist: syrupy==6.1.1; extra == 'dev'
|
|
41
41
|
Requires-Dist: types-jsonschema==4.26.0.20260518; extra == 'dev'
|
|
42
42
|
Requires-Dist: types-pyyaml==6.0.12.20250915; extra == 'dev'
|
|
@@ -345,6 +345,8 @@ environment-variable name, never embedded.
|
|
|
345
345
|
"repo_min_age_days": 28,
|
|
346
346
|
"release_max_age_days": 60,
|
|
347
347
|
"codeql_stale_days": 30,
|
|
348
|
+
"issue_age_warn_days": 30,
|
|
349
|
+
"issue_age_error_days": 60,
|
|
348
350
|
"graph_batch": 10,
|
|
349
351
|
"order": { "style": "auto" }
|
|
350
352
|
},
|
|
@@ -837,6 +839,38 @@ with HTTP 200 and this one field null, so affected repositories are reported as
|
|
|
837
839
|
`❓ Unknown` rather than counted as having no open issues — an unreadable backlog
|
|
838
840
|
is never presented as a clean one.
|
|
839
841
|
|
|
842
|
+
#### Issue colours and age thresholds
|
|
843
|
+
|
|
844
|
+
On the terminal the counts are coloured so the table reads at a glance: `Bug`
|
|
845
|
+
and `Untriaged` **red**, `Docs` **green**. As in the Pull Requests table, only
|
|
846
|
+
**non-zero** counts are coloured, so the eye lands on the rows with something
|
|
847
|
+
in them. The colours follow the column **name** (case-insensitively), so they
|
|
848
|
+
apply to a custom `issue_labels` that keeps a `Bug` or `Docs` column and simply
|
|
849
|
+
do not appear for one that renames them.
|
|
850
|
+
|
|
851
|
+
`Oldest` is coloured against two age thresholds, in days:
|
|
852
|
+
|
|
853
|
+
```json
|
|
854
|
+
{
|
|
855
|
+
"report": {
|
|
856
|
+
"issue_age_warn_days": 30,
|
|
857
|
+
"issue_age_error_days": 60
|
|
858
|
+
},
|
|
859
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
860
|
+
}
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
- **Green** while the oldest open issue is at most `issue_age_warn_days`
|
|
864
|
+
(default `30`) old.
|
|
865
|
+
- **Yellow** once it is older than `issue_age_warn_days`.
|
|
866
|
+
- **Red** once it is older than `issue_age_error_days` (default `60`).
|
|
867
|
+
|
|
868
|
+
An `unknown` age is never coloured. Either threshold may be set to `0` to
|
|
869
|
+
switch that level off; with both off the column is left uncoloured.
|
|
870
|
+
`issue_age_error_days` must be **strictly greater** than `issue_age_warn_days`
|
|
871
|
+
(or `0`), otherwise the yellow level could never be reached, so it is rejected
|
|
872
|
+
at load. Both are settable per organisation.
|
|
873
|
+
|
|
840
874
|
### Pull Requests
|
|
841
875
|
|
|
842
876
|
The `pull_requests` category counts each repository's **open pull requests**,
|
|
@@ -301,6 +301,8 @@ environment-variable name, never embedded.
|
|
|
301
301
|
"repo_min_age_days": 28,
|
|
302
302
|
"release_max_age_days": 60,
|
|
303
303
|
"codeql_stale_days": 30,
|
|
304
|
+
"issue_age_warn_days": 30,
|
|
305
|
+
"issue_age_error_days": 60,
|
|
304
306
|
"graph_batch": 10,
|
|
305
307
|
"order": { "style": "auto" }
|
|
306
308
|
},
|
|
@@ -793,6 +795,38 @@ with HTTP 200 and this one field null, so affected repositories are reported as
|
|
|
793
795
|
`❓ Unknown` rather than counted as having no open issues — an unreadable backlog
|
|
794
796
|
is never presented as a clean one.
|
|
795
797
|
|
|
798
|
+
#### Issue colours and age thresholds
|
|
799
|
+
|
|
800
|
+
On the terminal the counts are coloured so the table reads at a glance: `Bug`
|
|
801
|
+
and `Untriaged` **red**, `Docs` **green**. As in the Pull Requests table, only
|
|
802
|
+
**non-zero** counts are coloured, so the eye lands on the rows with something
|
|
803
|
+
in them. The colours follow the column **name** (case-insensitively), so they
|
|
804
|
+
apply to a custom `issue_labels` that keeps a `Bug` or `Docs` column and simply
|
|
805
|
+
do not appear for one that renames them.
|
|
806
|
+
|
|
807
|
+
`Oldest` is coloured against two age thresholds, in days:
|
|
808
|
+
|
|
809
|
+
```json
|
|
810
|
+
{
|
|
811
|
+
"report": {
|
|
812
|
+
"issue_age_warn_days": 30,
|
|
813
|
+
"issue_age_error_days": 60
|
|
814
|
+
},
|
|
815
|
+
"organizations": [{ "name": "lfreleng-actions" }]
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
- **Green** while the oldest open issue is at most `issue_age_warn_days`
|
|
820
|
+
(default `30`) old.
|
|
821
|
+
- **Yellow** once it is older than `issue_age_warn_days`.
|
|
822
|
+
- **Red** once it is older than `issue_age_error_days` (default `60`).
|
|
823
|
+
|
|
824
|
+
An `unknown` age is never coloured. Either threshold may be set to `0` to
|
|
825
|
+
switch that level off; with both off the column is left uncoloured.
|
|
826
|
+
`issue_age_error_days` must be **strictly greater** than `issue_age_warn_days`
|
|
827
|
+
(or `0`), otherwise the yellow level could never be reached, so it is rejected
|
|
828
|
+
at load. Both are settable per organisation.
|
|
829
|
+
|
|
796
830
|
### Pull Requests
|
|
797
831
|
|
|
798
832
|
The `pull_requests` category counts each repository's **open pull requests**,
|
|
@@ -65,7 +65,7 @@ dev = [
|
|
|
65
65
|
"respx==0.23.1",
|
|
66
66
|
"syrupy==6.1.1",
|
|
67
67
|
"mypy==2.3.1",
|
|
68
|
-
"ruff==0.16.
|
|
68
|
+
"ruff==0.16.9",
|
|
69
69
|
"types-jsonschema==4.26.0.20260518",
|
|
70
70
|
"types-PyYAML==6.0.12.20250915",
|
|
71
71
|
]
|
|
@@ -97,7 +97,7 @@ dev = [
|
|
|
97
97
|
"respx==0.23.1",
|
|
98
98
|
"syrupy==6.1.1",
|
|
99
99
|
"mypy==2.3.1",
|
|
100
|
-
"ruff==0.16.
|
|
100
|
+
"ruff==0.16.9",
|
|
101
101
|
"types-jsonschema==4.26.0.20260518",
|
|
102
102
|
"types-PyYAML==6.0.12.20250915",
|
|
103
103
|
]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.18.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 18, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -129,6 +129,8 @@ async def attach_extra_tables(
|
|
|
129
129
|
in_scope,
|
|
130
130
|
generated_at=report.generated_at,
|
|
131
131
|
label_columns=report_cfg.issue_labels,
|
|
132
|
+
age_warn_days=report_cfg.issue_age_warn_days,
|
|
133
|
+
age_error_days=report_cfg.issue_age_error_days,
|
|
132
134
|
members=members,
|
|
133
135
|
)
|
|
134
136
|
report.pull_requests = build_pull_requests_table(
|
|
@@ -167,6 +167,8 @@ def _report_from(data: dict, base: ReportConfig) -> ReportConfig:
|
|
|
167
167
|
"codeql_stale_days",
|
|
168
168
|
"dependabot_warn_threshold",
|
|
169
169
|
"dependabot_error_threshold",
|
|
170
|
+
"issue_age_warn_days",
|
|
171
|
+
"issue_age_error_days",
|
|
170
172
|
"gating",
|
|
171
173
|
"graph_batch",
|
|
172
174
|
}
|
|
@@ -195,24 +197,33 @@ def _report_from(data: dict, base: ReportConfig) -> ReportConfig:
|
|
|
195
197
|
result = replace(
|
|
196
198
|
result, excluded_display=ExcludedDisplay(data["excluded_display"])
|
|
197
199
|
)
|
|
198
|
-
|
|
199
|
-
result
|
|
200
|
-
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
_require_reachable_warning(
|
|
201
|
+
result, "dependabot_warn_threshold", "dependabot_error_threshold"
|
|
202
|
+
)
|
|
203
|
+
_require_reachable_warning(result, "issue_age_warn_days", "issue_age_error_days")
|
|
204
|
+
return result
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _require_reachable_warning(
|
|
208
|
+
report: ReportConfig, warn_name: str, error_name: str
|
|
209
|
+
) -> None:
|
|
210
|
+
"""Reject an error threshold that leaves its warning level unreachable.
|
|
211
|
+
|
|
212
|
+
The error level is checked first, so a warning threshold at or above it
|
|
213
|
+
can never be reached: every value that would warn has already errored.
|
|
214
|
+
Rejecting beats silently ignoring one of the two knobs the operator
|
|
215
|
+
deliberately set. A zero error threshold is exempt -- that is the
|
|
216
|
+
documented way to turn the error level off, leaving a warning-only
|
|
217
|
+
configuration.
|
|
218
|
+
"""
|
|
219
|
+
warn = getattr(report, warn_name)
|
|
220
|
+
error = getattr(report, error_name)
|
|
221
|
+
if error and error <= warn:
|
|
208
222
|
raise ConfigError(
|
|
209
|
-
"report.
|
|
210
|
-
f"({
|
|
211
|
-
"
|
|
212
|
-
f"({result.dependabot_warn_threshold}), or 0 to disable the error "
|
|
213
|
-
"level; otherwise the warning level can never be reached"
|
|
223
|
+
f"report.{error_name} ({error}) must be greater than "
|
|
224
|
+
f"report.{warn_name} ({warn}), or 0 to disable the error level; "
|
|
225
|
+
"otherwise the warning level can never be reached"
|
|
214
226
|
)
|
|
215
|
-
return result
|
|
216
227
|
|
|
217
228
|
|
|
218
229
|
def _categories_from(
|
|
@@ -178,6 +178,12 @@ class ReportConfig:
|
|
|
178
178
|
# Error (red) at or above this many: GitHub's default open-pull-requests
|
|
179
179
|
# limit, at which no further updates arrive for that ecosystem.
|
|
180
180
|
dependabot_error_threshold: int = 5
|
|
181
|
+
# Age thresholds, in days, colouring the GitHub Issues table's Oldest
|
|
182
|
+
# column: green at or below the warn threshold, yellow once the oldest open
|
|
183
|
+
# issue is older than it, red once it is older than the error threshold.
|
|
184
|
+
# 0 switches that level off.
|
|
185
|
+
issue_age_warn_days: int = 30
|
|
186
|
+
issue_age_error_days: int = 60
|
|
181
187
|
# Organisation feature gating for the workflow-driven signals (Scorecard,
|
|
182
188
|
# zizmor, aislop): when true, each is collected only after a cheap check
|
|
183
189
|
# finds organisation support (an org ruleset requiring the workflow,
|
|
@@ -147,6 +147,12 @@ CONFIG_SCHEMA: dict = {
|
|
|
147
147
|
# ecosystem while Auto counts every automation author.
|
|
148
148
|
"dependabot_warn_threshold": {"type": "integer", "minimum": 0},
|
|
149
149
|
"dependabot_error_threshold": {"type": "integer", "minimum": 0},
|
|
150
|
+
# Oldest-issue age thresholds colouring the GitHub Issues
|
|
151
|
+
# table's Oldest column: yellow once the oldest open issue is
|
|
152
|
+
# older than the first, red once older than the second
|
|
153
|
+
# (0 switches that level off).
|
|
154
|
+
"issue_age_warn_days": {"type": "integer", "minimum": 0},
|
|
155
|
+
"issue_age_error_days": {"type": "integer", "minimum": 0},
|
|
150
156
|
# Organisation feature gating: when true (the default) the
|
|
151
157
|
# workflow-driven signals (Scorecard, zizmor, aislop) are
|
|
152
158
|
# probed only after a cheap support check (org ruleset,
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/issues.py
RENAMED
|
@@ -33,7 +33,13 @@ from collections.abc import Mapping, Set
|
|
|
33
33
|
from github_security_report.authors import is_external_author
|
|
34
34
|
from github_security_report.categories import CategoryKey, category_meta
|
|
35
35
|
from github_security_report.models import IssueRef, Repo, RepoGraphData
|
|
36
|
-
from github_security_report.report import
|
|
36
|
+
from github_security_report.report import (
|
|
37
|
+
CELL_BAD,
|
|
38
|
+
CELL_GOOD,
|
|
39
|
+
CELL_WARN,
|
|
40
|
+
TableRow,
|
|
41
|
+
TableSection,
|
|
42
|
+
)
|
|
37
43
|
|
|
38
44
|
# Columns appended after the configured label classes. ``Other`` is "labelled,
|
|
39
45
|
# but not as anything we were asked about"; ``Untriaged`` is "not labelled at
|
|
@@ -68,6 +74,17 @@ TRUNCATED_MARKER = "+"
|
|
|
68
74
|
# distinct from an absent backlog: the repository has open issues either way.
|
|
69
75
|
UNKNOWN_AGE = "unknown"
|
|
70
76
|
|
|
77
|
+
# Emphasis for the count columns, keyed case-insensitively by header so it
|
|
78
|
+
# follows ``Bug`` and ``Docs`` into an ``issue_labels`` that keeps those names,
|
|
79
|
+
# and simply does not apply to one that renames them. Untriaged is the triage
|
|
80
|
+
# gap the table exists to surface, and Bug the class most likely to hurt a
|
|
81
|
+
# user, so both are red; documentation work is a healthy backlog, so green.
|
|
82
|
+
_COLUMN_LEVELS: Mapping[str, str] = {
|
|
83
|
+
"bug": CELL_BAD,
|
|
84
|
+
"docs": CELL_GOOD,
|
|
85
|
+
UNTRIAGED_COLUMN.casefold(): CELL_BAD,
|
|
86
|
+
}
|
|
87
|
+
|
|
71
88
|
|
|
72
89
|
def classify_issue(
|
|
73
90
|
issue: IssueRef, label_columns: Mapping[str, tuple[str, ...]]
|
|
@@ -160,6 +177,43 @@ def _oldest_cell(age: int | None, truncated: bool) -> str:
|
|
|
160
177
|
return f"{text} {TRUNCATED_MARKER}" if truncated else text
|
|
161
178
|
|
|
162
179
|
|
|
180
|
+
def age_level(age: int | None, *, warn_days: int, error_days: int) -> str | None:
|
|
181
|
+
"""Emphasis for an oldest open issue that is ``age`` days old.
|
|
182
|
+
|
|
183
|
+
Both thresholds read "older than": warning once the age exceeds
|
|
184
|
+
``warn_days``, error once it exceeds ``error_days``, and good at or below
|
|
185
|
+
every active threshold. Either may be ``0`` to turn that level off, matching
|
|
186
|
+
the ``0 = no limit`` idiom used elsewhere; with both off the column is left
|
|
187
|
+
plain rather than painted uniformly green. An unknown age has nothing to
|
|
188
|
+
measure, so it is never coloured.
|
|
189
|
+
"""
|
|
190
|
+
if age is None or not (warn_days or error_days):
|
|
191
|
+
return None
|
|
192
|
+
if error_days and age > error_days:
|
|
193
|
+
return CELL_BAD
|
|
194
|
+
if warn_days and age > warn_days:
|
|
195
|
+
return CELL_WARN
|
|
196
|
+
return CELL_GOOD
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _cell_levels(
|
|
200
|
+
columns: tuple[str, ...],
|
|
201
|
+
counts: Mapping[str, int],
|
|
202
|
+
age: str | None,
|
|
203
|
+
) -> tuple[str | None, ...]:
|
|
204
|
+
"""Semantic emphasis for one row's cells, parallel to its columns.
|
|
205
|
+
|
|
206
|
+
Only *non-zero* counts are emphasised, as in the Pull Requests table: a
|
|
207
|
+
column of red zeros trains the reader to ignore the colour. Total and Ext
|
|
208
|
+
carry none -- Total sums classes that disagree about what good looks like.
|
|
209
|
+
"""
|
|
210
|
+
counted = (
|
|
211
|
+
_COLUMN_LEVELS.get(column.casefold()) if counts[column] else None
|
|
212
|
+
for column in columns
|
|
213
|
+
)
|
|
214
|
+
return (*counted, None, None, age)
|
|
215
|
+
|
|
216
|
+
|
|
163
217
|
def _describe(base: str, age_cells: list[str], members: Set[str] | None) -> str:
|
|
164
218
|
"""Extend the category description with the caveats the table earned.
|
|
165
219
|
|
|
@@ -198,6 +252,8 @@ def build_issues_table(
|
|
|
198
252
|
*,
|
|
199
253
|
generated_at: dt.datetime,
|
|
200
254
|
label_columns: Mapping[str, tuple[str, ...]],
|
|
255
|
+
age_warn_days: int,
|
|
256
|
+
age_error_days: int,
|
|
201
257
|
members: Set[str] | None = frozenset(),
|
|
202
258
|
) -> TableSection:
|
|
203
259
|
"""The GitHub Issues table, largest backlog first.
|
|
@@ -209,7 +265,8 @@ def build_issues_table(
|
|
|
209
265
|
with equal backlogs surface the less-triaged one first.
|
|
210
266
|
|
|
211
267
|
``members`` is the organisation's membership, used to count the issues
|
|
212
|
-
raised from outside it (see :mod:`authors`).
|
|
268
|
+
raised from outside it (see :mod:`authors`). ``age_warn_days`` and
|
|
269
|
+
``age_error_days`` colour the Oldest column (see :func:`age_level`).
|
|
213
270
|
"""
|
|
214
271
|
columns = (*label_columns, OTHER_COLUMN, UNTRIAGED_COLUMN)
|
|
215
272
|
rows: list[tuple[int, int, str, TableRow]] = []
|
|
@@ -266,7 +323,20 @@ def build_issues_table(
|
|
|
266
323
|
data.open_issues,
|
|
267
324
|
counts[UNTRIAGED_COLUMN],
|
|
268
325
|
repo.name,
|
|
269
|
-
TableRow(
|
|
326
|
+
TableRow(
|
|
327
|
+
repo=repo,
|
|
328
|
+
cells=cells,
|
|
329
|
+
sort_values=sort_values,
|
|
330
|
+
cell_levels=_cell_levels(
|
|
331
|
+
columns,
|
|
332
|
+
counts,
|
|
333
|
+
age_level(
|
|
334
|
+
oldest,
|
|
335
|
+
warn_days=age_warn_days,
|
|
336
|
+
error_days=age_error_days,
|
|
337
|
+
),
|
|
338
|
+
),
|
|
339
|
+
),
|
|
270
340
|
)
|
|
271
341
|
)
|
|
272
342
|
# Negated numerics so the whole sort runs ascending, keeping the name
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/gating.py
RENAMED
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/gitctx.py
RENAMED
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/layout.py
RENAMED
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/runner.py
RENAMED
|
File without changes
|
{github_security_report-0.17.0 → github_security_report-0.18.0}/src/github_security_report/scope.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|