Skip to content

Commit 3559486

Browse files
committed
feat: update middleware [OpsPortal]
Signed-off-by: Damian Skrzyński <polprog.tech@gmail.com>
1 parent 9502c59 commit 3559486

33 files changed

Lines changed: 1334 additions & 544 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ jobs:
2525
python-version: ${{ matrix.python-version }}
2626

2727
- name: Install dependencies
28-
run: pip install -e ".[dev]" httpx
28+
run: pip install -e ".[dev]"
2929

3030
- name: Lint with ruff
31-
run: ruff check src/ tests/
31+
run: |
32+
ruff check src/ tests/
33+
ruff format --check src/ tests/
3234
3335
- name: Run tests
3436
run: pytest --cov=flowboard --cov-report=xml -v --junitxml=test-results.xml

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ venv/
2727
*.swo
2828
*~
2929
.DS_Store
30+
31+
# OpsPortal runtime
32+
opsportal.yaml
33+
work/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p align="center">
2-
<img src="docs/assets/logo-full.svg" alt="FlowBoard" width="420">
2+
<img src="docs/assets/logo-full.svg" alt="FlowBoard" width="370">
33
</p>
44

55
<p align="center">

docs/assets/logo-full.svg

Lines changed: 1 addition & 1 deletion
Loading

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dev = [
3939
"pytest>=7.4,<9",
4040
"pytest-cov>=4.1,<6",
4141
"responses>=0.24,<1",
42+
"httpx>=0.27,<1",
4243
"ruff>=0.4,<1",
4344
"mypy>=1.8,<2",
4445
]

src/flowboard/application/orchestrator.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,21 @@
2626

2727
def _timed(stage: str):
2828
"""Log pipeline stage timing at INFO level."""
29+
2930
class _Timer:
3031
def __enter__(self):
3132
self.start = time.monotonic()
3233
logger.info("Pipeline stage '%s' started.", stage)
3334
return self
35+
3436
def __exit__(self, *exc):
3537
elapsed = time.monotonic() - self.start
3638
logger.info(
3739
"Pipeline stage '%s' completed in %.2fs.",
38-
stage, elapsed,
40+
stage,
41+
elapsed,
3942
)
43+
4044
return _Timer()
4145

4246

@@ -70,7 +74,9 @@ def run(self) -> Path:
7074
total = time.monotonic() - pipeline_start
7175
logger.info(
7276
"Dashboard generated: %s (total pipeline: %.2fs, issues: %d)",
73-
output_path, total, len(snapshot.issues),
77+
output_path,
78+
total,
79+
len(snapshot.issues),
7480
)
7581
return output_path
7682

@@ -93,9 +99,7 @@ def _render(self, snapshot: BoardSnapshot) -> Path:
9399
# Blocker #16: pre-check output directory writability
94100
output_path.parent.mkdir(parents=True, exist_ok=True)
95101
if not os.access(output_path.parent, os.W_OK):
96-
raise PermissionError(
97-
f"Output directory is not writable: {output_path.parent}"
98-
)
102+
raise PermissionError(f"Output directory is not writable: {output_path.parent}")
99103
output_path.write_text(html, encoding="utf-8")
100104
return output_path
101105

@@ -117,6 +121,7 @@ def snapshot_from_payload(self, raw: dict[str, Any]) -> BoardSnapshot:
117121
# Standalone analysis helper (no orchestrator instance needed)
118122
# ------------------------------------------------------------------
119123

124+
120125
def analyse_raw_payload(
121126
raw: dict[str, Any],
122127
config: FlowBoardConfig,

src/flowboard/application/services.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ def describe_config(config: FlowBoardConfig, t: Translator | None = None) -> dic
3333
return {
3434
"jira_url": config.jira.base_url or t("config.not_set"),
3535
"auth_email": config.jira.auth_email or t("config.not_set"),
36-
"auth_token": mask_secret(config.jira.auth_token) if config.jira.auth_token else t("config.not_set"),
36+
"auth_token": mask_secret(config.jira.auth_token)
37+
if config.jira.auth_token
38+
else t("config.not_set"),
3739
"projects": ", ".join(config.jira.projects) or t("config.all"),
3840
"boards": ", ".join(str(b) for b in config.jira.boards) or t("config.auto_detect"),
3941
"teams": str(len(config.teams)),

src/flowboard/domain/kanban_compute.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,11 @@ def compute_flow_metrics(
221221

222222
avg_ct = statistics.mean(ct_values) if ct_values else 0.0
223223
median_ct = statistics.median(ct_values) if ct_values else 0.0
224-
p85_ct = sorted(ct_values)[min(int(len(ct_values) * 0.85), len(ct_values) - 1)] if len(ct_values) > 1 else avg_ct
224+
p85_ct = (
225+
sorted(ct_values)[min(int(len(ct_values) * 0.85), len(ct_values) - 1)]
226+
if len(ct_values) > 1
227+
else avg_ct
228+
)
225229
avg_lt = statistics.mean(lt_values) if lt_values else 0.0
226230

227231
# Throughput per week (average of non-zero weeks)

src/flowboard/domain/pi.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
# Data structures
1919
# ---------------------------------------------------------------------------
2020

21+
2122
@dataclass(frozen=True, slots=True)
2223
class PISprintSlot:
2324
"""One sprint slot inside a PI."""
25+
2426
index: int # 1-based (1 … sprints_per_pi)
2527
name: str
2628
start_date: date
@@ -34,6 +36,7 @@ class PISprintSlot:
3436
@dataclass(slots=True)
3537
class PISnapshot:
3638
"""Complete PI-level snapshot for the presentation layer."""
39+
3740
name: str
3841
start_date: date
3942
end_date: date
@@ -50,6 +53,7 @@ class PISnapshot:
5053
# Business-day helpers
5154
# ---------------------------------------------------------------------------
5255

56+
5357
def _to_wd_set(working_days: list[int] | frozenset[int] | None) -> frozenset[int]:
5458
if working_days is None:
5559
return DEFAULT_WORKING_DAYS
@@ -104,6 +108,7 @@ def count_working_days(start: date, end: date, working_days: list[int] | None =
104108
# Sprint boundary computation
105109
# ---------------------------------------------------------------------------
106110

111+
107112
def compute_sprint_boundaries(
108113
pi_start: date,
109114
sprint_length: int = 10,
@@ -139,6 +144,7 @@ def compute_sprint_boundaries(
139144
# PI snapshot computation
140145
# ---------------------------------------------------------------------------
141146

147+
142148
def compute_pi_snapshot(
143149
name: str,
144150
pi_start_iso: str,
@@ -192,19 +198,23 @@ def compute_pi_snapshot(
192198
elapsed = total_wd if today > s_end else 0
193199
remaining = 0 if today > s_end else total_wd
194200

195-
slots.append(PISprintSlot(
196-
index=i,
197-
name=f"{sprint_name_prefix} {i}",
198-
start_date=s_start,
199-
end_date=s_end,
200-
is_current=is_current,
201-
working_days_total=total_wd,
202-
working_days_elapsed=elapsed,
203-
working_days_remaining=remaining,
204-
))
201+
slots.append(
202+
PISprintSlot(
203+
index=i,
204+
name=f"{sprint_name_prefix} {i}",
205+
start_date=s_start,
206+
end_date=s_end,
207+
is_current=is_current,
208+
working_days_total=total_wd,
209+
working_days_elapsed=elapsed,
210+
working_days_remaining=remaining,
211+
)
212+
)
205213

206214
total_wd = count_working_days(pi_start, pi_end, wd_list)
207-
elapsed_wd = count_working_days(pi_start, min(today, pi_end), wd_list) if today >= pi_start else 0
215+
elapsed_wd = (
216+
count_working_days(pi_start, min(today, pi_end), wd_list) if today >= pi_start else 0
217+
)
208218
remaining_wd = max(0, total_wd - elapsed_wd)
209219
progress = (elapsed_wd / total_wd * 100) if total_wd > 0 else 0.0
210220

src/flowboard/domain/risk.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def detect_all_risks(
4343
today = today or date.today()
4444
if t is None:
4545
from flowboard.i18n.translator import get_translator
46+
4647
t = get_translator()
4748
signals: list[RiskSignal] = []
4849
signals.extend(_detect_overload_risks(workload_records, thresholds, t=t))
@@ -67,6 +68,7 @@ def detect_all_risks(
6768
# Individual detectors
6869
# ------------------------------------------------------------------
6970

71+
7072
def _detect_overload_risks(
7173
records: list[WorkloadRecord],
7274
thresholds: Thresholds,
@@ -121,7 +123,9 @@ def _detect_aging_risks(
121123
continue
122124
if issue.created:
123125
# Compare at date level to avoid timezone mismatch issues
124-
created_date = issue.created.date() if isinstance(issue.created, datetime) else issue.created
126+
created_date = (
127+
issue.created.date() if isinstance(issue.created, datetime) else issue.created
128+
)
125129
age = (today - created_date).days
126130
if age < 0:
127131
continue # future-dated issue, skip
@@ -162,7 +166,8 @@ def _detect_blocked_risks(issues: list[Issue], *, t: Translator | None = None) -
162166
)
163167
for issue in blocked:
164168
blockers = [
165-
lnk.target_key for lnk in issue.links
169+
lnk.target_key
170+
for lnk in issue.links
166171
if lnk.link_type in (LinkType.IS_BLOCKED_BY, LinkType.DEPENDS_ON)
167172
and not lnk.is_resolved
168173
]

0 commit comments

Comments
 (0)