Skip to content

Commit

Permalink
PTFE-1981 handle cancelled build on same sha
Browse files Browse the repository at this point in the history
  • Loading branch information
tcarmet committed Oct 11, 2024
1 parent 056cb63 commit f503e77
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 100 deletions.
184 changes: 84 additions & 100 deletions bert_e/git_host/github/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,13 @@ def get_commit_status(self, ref):
combined = AggregatedStatus.get(self.client,
owner=self.owner,
repo=self.slug, ref=ref)
actions = AggregatedCheckSuites.get(client=self.client,
owner=self.owner,
repo=self.slug, ref=ref)
actions = AggregatedWorkflowRuns.get(
client=self.client,
owner=self.owner,
repo=self.slug,
params={
'head_sha': ref
})
combined.status[actions.key] = actions

except HTTPError as err:
Expand Down Expand Up @@ -581,71 +585,58 @@ class AggregatedWorkflowRuns(base.AbstractGitHostObject):
GET_URL = "/repos/{owner}/{repo}/actions/runs"
SCHEMA = schema.AggregateWorkflowRuns

@property
def total_count(self):
return self.data['total_count']

@property
def workflow_runs(self):
return self.data['workflow_runs']

@property
def check_suite_ids(self):
return [wf['check_suite_id'] for wf in self.workflow_runs]


class AggregatedCheckSuites(base.AbstractGitHostObject,
base.AbstractBuildStatus):
"""
The Endpoint to have access infos about github actions runs
"""
GET_URL = '/repos/{owner}/{repo}/commits/{ref}/check-suites'
SCHEMA = schema.AggregateCheckSuites

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._check_suites = [elem for elem in self.data['check_suites']]
self._workflow_runs = [elem for elem in self.data['workflow_runs']]

@property
def url(self):
return f"https://github.com/{self.full_repo}/commit/{self.commit}"

def is_pending(self, check_suites=None):
if check_suites is None:
check_suites = self._check_suites
@property
def commit(self) -> str | None:
if len(self._workflow_runs) == 0:
return None
return self._workflow_runs[0]['head_sha']

@property
def full_repo(self) -> str | None:
if len(self._workflow_runs) == 0:
return None
return self._workflow_runs[0]['repository']['full_name']

def is_pending(self, workflow_runs=None):
if workflow_runs is None:
workflow_runs = self._workflow_runs
return len([
elem for elem in check_suites if elem['status'] == 'pending'
elem for elem in workflow_runs if elem['status'] == 'pending'
]) > 0

def is_queued(self, check_suites=None):
if check_suites is None:
check_suites = self._check_suites
def is_queued(self, workflow_runs=None):
if workflow_runs is None:
workflow_runs = self._workflow_runs
return len([
elem for elem in check_suites if elem['status'] == 'queued'
elem for elem in workflow_runs if elem['status'] == 'queued'
]) > 0

def _get_aggregate_workflow_dispatched(self, page, prev_dispatches=list()):
"""Return a list of check-suite IDs for workflow_dispatch runs"""
@property
def owner(self) -> str | None:
if self._workflow_runs.__len__() > 0:
return self._workflow_runs[0]['repository']['owner']['login']
return None

response = AggregatedWorkflowRuns.get(
client=self.client,
owner=self.owner, repo=self.repo, ref=self.commit,
params={
'branch': self.branch,
'page': page,
'event': 'workflow_dispatch',
'per_page': 100
})
@property
def repo(self) -> str | None:
if self._workflow_runs.__len__() > 0:
return self._workflow_runs[0]['repository']['name']
return None

prev_dispatches.extend(response.check_suite_ids)
if len(prev_dispatches) < response.total_count:
page += 1
return self._get_aggregate_workflow_dispatched(
page,
prev_dispatches
)
@property
def branch(self) -> str | None:
if self._workflow_runs.__len__() > 0:
return self._workflow_runs[0]['head_branch']
return None

return prev_dispatches

def remove_unwanted_workflows(self):
"""
Expand All @@ -654,42 +645,52 @@ def remove_unwanted_workflows(self):
- check-suites workflow triggerd by a `workflow_dispatch` event
- Same workflow with different result
"""
if self._check_suites.__len__() == 0:
if self._workflow_runs.__len__() == 0:
return

page = 1
workflow_dispatches = self._get_aggregate_workflow_dispatched(page)

self._check_suites = list(filter(
lambda elem: elem['id'] not in workflow_dispatches,
self._check_suites
self._workflow_runs = list(filter(
lambda elem: elem['event'] != 'workflow_dispatch',
self._workflow_runs
))

self._check_suites = list(filter(
self._workflow_runs = list(filter(
lambda elem: elem['app']['slug'] == 'github-actions',
self._check_suites
self._workflow_runs
))

def branch_state(self, branch_check_suite):
# if two workflows runs have the same workflow_id pick the one that is either running or successful
# and remove the other
conclusion_ranking = {'success': 4, None: 3, 'failure': 2, 'cancelled': 1}
best_runs = {}
for run in self._workflow_runs:
workflow_id = run['workflow_id']
conclusion = run['conclusion']
if (workflow_id not in best_runs or
conclusion_ranking[conclusion] > conclusion_ranking[best_runs[workflow_id]['conclusion']]):
best_runs[workflow_id] = run
self._workflow_runs = list(best_runs.values())


def branch_state(self, branch_workflow_runs):
all_complete = all(
elem['conclusion'] is not None for elem in branch_check_suite
elem['conclusion'] is not None for elem in branch_workflow_runs
)

all_success = all(
elem['conclusion'] == 'success'
for elem in branch_check_suite
for elem in branch_workflow_runs
)
LOG.info(f'State on {self.branch}: '
f'complete: {all_complete} '
f'success: {all_success} '
f'pending: {self.is_pending(branch_check_suite)} '
f'queued: {self.is_queued(branch_check_suite)}')
LOG.info(f'branch check suites {branch_check_suite}')
f'pending: {self.is_pending(branch_workflow_runs)} '
f'queued: {self.is_queued(branch_workflow_runs)}')
LOG.info(f'branch check suites {branch_workflow_runs}')

if branch_check_suite.__len__() == 0:
if branch_workflow_runs.__len__() == 0:
return 'NOTSTARTED'
elif (self.is_pending(branch_check_suite) or
self.is_queued(branch_check_suite) or not all_complete):
elif (self.is_pending(branch_workflow_runs) or
self.is_queued(branch_workflow_runs) or not all_complete):
return 'INPROGRESS'
elif all_complete and all_success:
return 'SUCCESSFUL'
Expand All @@ -700,7 +701,7 @@ def branch_state(self, branch_check_suite):
def state(self):
self.remove_unwanted_workflows()
res = [list(v) for i, v in groupby(
self._check_suites,
self._workflow_runs,
lambda elem: elem['head_branch']
)]

Expand All @@ -726,38 +727,20 @@ def key(self) -> str:
return 'github_actions'

@property
def commit(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]["head_sha"]
return None

@property
def branch(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]["head_branch"]
return None

@property
def full_repo(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]['repository']['full_name']
return None

@property
def repo(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]['repository']['name']
return None
def total_count(self):
return self.data['total_count']

@property
def owner(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]['repository']['owner']['login']
return None
def workflow_runs(self):
return self._workflow_runs

def __str__(self) -> str:
return self.state

@property
def check_suite_ids(self):
return [wf['check_suite_id'] for wf in self.workflow_runs]


class PullRequest(base.AbstractGitHostObject, base.AbstractPullRequest):
LIST_URL = '/repos/{owner}/{repo}/pulls'
Expand Down Expand Up @@ -1143,14 +1126,15 @@ def owner(self) -> str or None:

@property
def status(self):
return AggregatedCheckSuites.get(
return AggregatedWorkflowRuns.get(
client=self.client,
owner=self.owner,
repo=self.repo,
ref=self.commit
params={
'head_sha': self.commit,
}
)


class User(base.AbstractGitHostObject):
SCHEMA = schema.User
GET_URL = '/user'
83 changes: 83 additions & 0 deletions bert_e/tests/unit/test_github_build_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

from tabnanny import check
from bert_e.git_host.github import AggregatedWorkflowRuns, Client

from pytest import fixture

@fixture
def client():
return Client(
login='login',
password='password',
email='[email protected]',
base_url="http://localhost:4010",
accept_header="application/json"
)

def test_github_check_suite(client, monkeypatch):
workflow_run_json = {
'workflow_runs': [
{
'id': 1,
'head_sha': 'd6fde92930d4715a2b49857d24b940956b26d2d3',
'head_branch': 'q/1',
'status': 'completed',
'event': 'pull_request',
'workflow_id': 1,
'check_suite_id': 1,
'conclusion': 'success',
'app': {
'slug': 'github-actions'
},
'pull_requests': [
{
'number': 1
}
],
'repository': {
'full_name': 'octo-org/Hello-World',
'owner': {
'login': 'octo-org'
},
'name': 'Hello-World'
}
},
{
'id': 2,
'head_sha': 'd6fde92930d4715a2b49857d24b940956b26d2d3',
'head_branch': 'q/1',
'workflow_id': 1,
'check_suite_id': 2,
'event': 'pull_request',
'status': 'completed',
'conclusion': 'cancelled',
'app': {
'slug': 'github-actions'
},
'pull_requests': [
{
'number': 1
}
],
'repository': {
'full_name': 'octo-org/Hello-World',
'owner': {
'login': 'octo-org'
},
'name': 'Hello-World'
}
}
],
'total_count': 2
}

workflow_runs = AggregatedWorkflowRuns(client, **workflow_run_json)
# set the method get of AggregatedCheckSuites to return the above workflow_runs defined
monkeypatch.setattr(AggregatedWorkflowRuns, 'get', lambda *args, **kwargs: workflow_runs)
get_workflow_run = AggregatedWorkflowRuns.get(
client=client,
owner=workflow_run_json['workflow_runs'][0]['repository']['owner']['login'],
repo=workflow_run_json['workflow_runs'][0]['repository']['name'],
ref=workflow_run_json['workflow_runs'][0]['head_sha']
)
assert get_workflow_run.state == 'SUCCESSFUL'

0 comments on commit f503e77

Please sign in to comment.