Skip to content

Add more triggers for autoflow

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

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).

workflow

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 🤖