Add more triggers for autoflow

Currently, we only run the workflows when the issue is updated (please see the video below: you will see that workflowready for development label is assigned instantly because we triggered issue_updated with the milestone set but workflowin dev is not applied until we manually trigger the issue update with changing an assignee).

It would be great to trigger issue_updated event (for demo purposes) also whenever we apply changes to the related MR. Alternatively, we could create a new event (related_mr_updated?) and start the workflow when it's triggered.

Setting the environment up

  • brew install temporal to install the Temporal CLI.

  • temporal server start-dev to run the dev server.

  • Go to http://127.0.0.1:8233/namespaces/default/workflows to see the server’s UI and workflows

  • In GDK check out ash2k/autoflow-from-natalia (prev ntepluhina/autoflow )branch in gitlab directory.

  • In GDK check out ash2k/autoflow branch in gitlab-k8s-agent directory.

  • In GDK directory open gitlab-k8s-agent-config.yml and add:

    automation:
      temporal:
        host_port: "localhost:7233"

Start the GDK. Do not reconfigure it as this will undo the above changes. Edit .gitlab/flow.py in any project in your GDK GitLab instance.

Example code for flow.py
AUTH_TOKEN = "Bearer <your-token>"

def check_status(resp, expected_status_code=200):
    if resp.status_code != expected_status_code:
        fail("Unexpected HTTP status code", resp.status_code)

def check_content_type(resp, expected_content_type="application/json"):
    ct = resp.header.get("Content-Type", [])
    if len(ct) == 0 or not ct[0].startswith(expected_content_type):
        fail("Unexpected Content-Type", ct)


# https://docs.gitlab.com/ee/api/issues.html#single-issue
def get_issue(w, issue_id):
    resp = w.http.do(
        url="%s/api/v4/issues/%d" % (w.vars.gitlab_url, issue_id),
        header={
            "Accept": "application/json",
            "Authorization": AUTH_TOKEN,
        },
    )
    check_status(resp)
    check_content_type(resp)
    return json.decode(str(resp.body))


# https://docs.gitlab.com/ee/api/issues.html#edit-issue
def edit_issue(w, project_id, issue_iid, labels=None, assignee_ids=None):
    q = {}
    if labels != None:
        q["labels"] = ",".join(labels)
    if assignee_ids != None:
        q["assignee_ids"] = ",".join([
            str(id)
            for id in assignee_ids
        ])
    resp = w.http.do(
        method="PUT",
        url="%s/api/v4/projects/%d/issues/%d" % (w.vars.gitlab_url, project_id, issue_iid),
        header={
            "Authorization": AUTH_TOKEN,
        },
        query=q,
    )
    check_status(resp)


# https://docs.gitlab.com/ee/api/notes.html#create-new-issue-note
def create_issue_note(w, project_id, issue_iid, body):
    resp = w.http.do(
        method="POST",
        url="%s/api/v4/projects/%d/issues/%d/notes" % (w.vars.gitlab_url, project_id, issue_iid),
        header={
            "Authorization": AUTH_TOKEN,
        },
        query={
            "body": body,
        },
    )
    check_status(resp, 201)


# https://docs.gitlab.com/ee/api/issues.html#list-merge-requests-related-to-issue
def get_related_merge_requests(w, project_id, issue_iid):
    resp = w.http.do(
        url="%s/api/v4/projects/%d/issues/%d/related_merge_requests" % (w.vars.gitlab_url, project_id, issue_iid),
        header={
            "Accept": "application/json",
            "Authorization": AUTH_TOKEN,
        },
    )
    check_status(resp)
    check_content_type(resp)
    return json.decode(str(resp.body))


MILESTONE_BACKLOG_TITLE = "Backlog"

def is_milestone_set(issue):
    milestone = issue.get("milestone")
    return milestone != None and milestone["title"] != MILESTONE_BACKLOG_TITLE


def is_all_mrs_merged(mrs):
    for mr in mrs:
        if mr["state"] == "closed":
            pass  # ignore closed MRs
        elif mr["state"] == "merged":
            pass  # found a merged MR, good!
        else:
            return False  # in review, or something else

    return True


def is_open_mrs_with_reviewer(mrs):
    for mr in mrs:
        if mr["state"] == "opened" and len(mr["reviewers"]) > 0:
            return True

    return False


def is_open_mrs(mrs):
    for mr in mrs:
        if mr["state"] == "opened":
            return True

    return False


def ensure_correct_issue_labels(w, ev):
    issue = get_issue(w, ev["proto_data"]["issue"]["id"])
    if issue["state"] == "closed":
        print("Ignoring update to a closed issue")
        return


    labels = set(issue["labels"])  # mutable set. Remove all managed labels.

    label_to_set = None

    mrs = get_related_merge_requests(w, issue["project_id"], issue["iid"])
    
    if is_open_mrs(mrs):
        label_to_set = "workflow::in dev"
    if is_milestone_set(issue):
        label_to_set = "workflow::ready for development"

    if label_to_set != None:
        labels.add(label_to_set)

    edit_issue(w, issue["project_id"], issue["iid"], labels=labels)


on_event(
    type="com.gitlab.events.IssueUpdated",
    handler=ensure_correct_issue_labels,
)

on_event(
    type="com.gitlab.events.NewIssue",
    handler=ensure_correct_issue_labels,
)
Edited by 🤖 GitLab Bot 🤖