diff --git a/.github/workflows/experiment_validation.yml b/.github/workflows/experiment_validation.yml index 8da3486..beeaaaa 100644 --- a/.github/workflows/experiment_validation.yml +++ b/.github/workflows/experiment_validation.yml @@ -12,7 +12,10 @@ name: Experiment Validation jobs: validate: if: contains(github.event.issue.labels.*.name, 'monthly-optimization-task') || inputs.issue_number != '' - runs-on: ubuntu-latest + runs-on: + - self-hosted + - Linux + - X64 permissions: contents: read issues: write diff --git a/.github/workflows/monthly_optimization_planner.yml b/.github/workflows/monthly_optimization_planner.yml index 7c7e6c1..1311c7b 100644 --- a/.github/workflows/monthly_optimization_planner.yml +++ b/.github/workflows/monthly_optimization_planner.yml @@ -212,6 +212,65 @@ jobs: print(f"issue_number={fanout.get('issue_number') or ''}", file=output) PY + - name: Trigger BinancePlatform experiment validation by label + if: steps.downstream_experiment_target.outputs.should_dispatch == 'true' + env: + GITHUB_TOKEN: ${{ secrets.CROSS_REPO_GITHUB_TOKEN }} + TARGET_REPO: ${{ inputs.downstream_repo }} + ISSUE_NUMBER: ${{ steps.downstream_experiment_target.outputs.issue_number }} + run: | + python3 - <<'PY' + import json + import os + import urllib.error + import urllib.parse + import urllib.request + + token = os.environ["GITHUB_TOKEN"] + repo = os.environ["TARGET_REPO"] + issue_number = os.environ["ISSUE_NUMBER"] + label_name = "experiment-validation" + api_base = f"https://api.github.com/repos/{repo}" + headers = { + "Authorization": f"Bearer {token}", + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "monthly-optimization-planner", + } + + def request_json(method: str, url: str, payload: dict | None = None): + data = None + if payload is not None: + data = json.dumps(payload).encode("utf-8") + request = urllib.request.Request(url, data=data, method=method, headers=headers) + with urllib.request.urlopen(request) as response: + raw = response.read().decode("utf-8") + return json.loads(raw) if raw else {} + + label_path = urllib.parse.quote(label_name, safe="") + try: + request_json("GET", f"{api_base}/labels/{label_path}") + except urllib.error.HTTPError as exc: + if exc.code != 404: + raise + request_json( + "POST", + f"{api_base}/labels", + { + "name": label_name, + "color": "1D76DB", + "description": "Trigger experiment validation for monthly optimization tasks", + }, + ) + + issue = request_json("GET", f"{api_base}/issues/{issue_number}") + existing_labels = [label["name"] for label in issue.get("labels", [])] + if label_name in existing_labels: + request_json("DELETE", f"{api_base}/issues/{issue_number}/labels/{label_path}") + request_json("POST", f"{api_base}/issues/{issue_number}/labels", {"labels": [label_name]}) + print(f"Toggled {label_name} on issue #{issue_number} in {repo}") + PY + - name: Dispatch BinancePlatform experiment validation if: steps.downstream_experiment_target.outputs.should_dispatch == 'true' env: diff --git a/tests/test_experiment_validation_workflow_config.py b/tests/test_experiment_validation_workflow_config.py index 5f555a7..db57c5c 100644 --- a/tests/test_experiment_validation_workflow_config.py +++ b/tests/test_experiment_validation_workflow_config.py @@ -15,6 +15,7 @@ def test_workflow_runs_shadow_build_and_posts_comment(self) -> None: self.assertIn("issues:", workflow) self.assertIn("workflow_dispatch:", workflow) self.assertIn("issue_number:", workflow) + self.assertIn("self-hosted", workflow) self.assertIn("prepare_experiment_validation.py", workflow) self.assertIn("download_history.py", workflow) self.assertIn("run_monthly_shadow_build.py", workflow) diff --git a/tests/test_monthly_optimization_planner_workflow_config.py b/tests/test_monthly_optimization_planner_workflow_config.py index 5233455..2439ca9 100644 --- a/tests/test_monthly_optimization_planner_workflow_config.py +++ b/tests/test_monthly_optimization_planner_workflow_config.py @@ -32,6 +32,8 @@ def test_planner_workflow_downloads_artifacts_posts_issue_and_fans_out_tasks(sel self.assertIn("Resolve upstream experiment validation target", workflow) self.assertIn("Dispatch CryptoLeaderRotation experiment validation", workflow) self.assertIn("Resolve downstream experiment validation target", workflow) + self.assertIn("Trigger BinancePlatform experiment validation by label", workflow) + self.assertIn("experiment-validation", workflow) self.assertIn("Dispatch BinancePlatform experiment validation", workflow) self.assertIn("gh workflow run experiment_validation.yml", workflow) self.assertIn("--allow-permission-skip", workflow)