Skip to content

Commit

Permalink
docs: more playing around with naming
Browse files Browse the repository at this point in the history
  • Loading branch information
jippi committed May 10, 2024
1 parent eecb7e9 commit 00959a0
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 32 deletions.
35 changes: 21 additions & 14 deletions docs/configuration/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,14 @@

## Close Merge Request without recent commit activity

!!! warning "When adopting this script on existing projects"

The script will *NOT* wait 7 days between warning and closing

* On the first run, all MRs with commits older than 21 days will be warned
* On the second run, all MRs with commits older than 28 days will be closed.

This example will close a Merge Request if no commits has been made for 28 days.
This example will close a Merge Request if no activity has happened for 28 days.

The script will warn at 21 days mark that this will happen.

```{.yaml linenums=1}
label:
- name: "mark MR as stale" # (1)!
color: $red
color: $red # (11)!
script: |1 # (2)!
--8<-- "docs/configuration/snippets/close-merge-request/label-script.expr"
Expand All @@ -31,10 +24,10 @@ actions:
if: |1 # (3)!
--8<-- "docs/configuration/snippets/close-merge-request/warn-if.expr"
then:
- action: add_label
- action: add_label # (6)!
name: stale
- action: comment
- action: comment # (9)!
message: |
:wave: Hello!
Expand All @@ -44,12 +37,13 @@ actions:
To disable this behavior, add the `do-not-close` label to the
MR in the right menu or add comment with `/label ~"do-not-close"`
- name: "close"
- name: "close" # (10)!
if: |1 # (4)!
--8<-- "docs/configuration/snippets/close-merge-request/close-if.expr"
then:
- action: close
- action: comment
- action: close # (8)!
- action: comment # (7)!
message: |
:wave: Hello!
Expand Down Expand Up @@ -83,6 +77,19 @@ actions:
```

5. Send "warning" about the MR being inactive
6. Add the `stale` label to the MR (if it doesn't exists)
7. Add a comment to the MR
8. Close the MR
9. Add a comment to the MR
10. Close the MR if no activity has happened after 7 days.

!!! question "Why 7 days?"

The `merge_request.updated_at` updated when we commented and added the `stale` label at the 21 day mark.

So instead we count 7 days from *that* point in time for the `close` step.

11. You can use [Twitter Bootstrap color variables](https://getbootstrap.com/docs/5.3/customize/color/#all-colors){target="_blank"} instead of HEX values.

## Add label if a file extension is modified

Expand Down
6 changes: 3 additions & 3 deletions docs/configuration/snippets/close-merge-request/close-if.expr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
merge_request.is_open()
merge_request.state_is("opened")
&& merge_request.has_label("stale")
&& merge_request.lacks_label("do-not-close")
&& since(merge_request.updated_at) > duration("7d")
&& merge_request.has_no_label("do-not-close")
&& merge_request.has_no_activity_within("7d")
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
merge_request.is_open()
&& merge_request.lacks_label("do-not-close")
&& since(merge_request.updated_at) > duration("21d")
merge_request.state_is("opened")
&& merge_request.has_no_label("do-not-close")
&& merge_request.has_no_user_activity_within("21d")
8 changes: 4 additions & 4 deletions docs/configuration/snippets/close-merge-request/warn-if.expr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
merge_request.is_open()
&& merge_request.lacks_label("stale")
&& merge_request.lacks_label("do-not-close")
&& since(merge_request.updated_at) > duration("21d")
merge_request.state_is("opened")
&& merge_request.has_no_label("stale")
&& merge_request.has_no_label("do-not-close")
&& merge_request.has_no_user_activity_within("21d")
6 changes: 6 additions & 0 deletions pkg/scm/gitlab/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,18 @@ func NewContext(ctx context.Context, baseURL, token string) (*Context, error) {
evalContext.MergeRequest = evalContext.Project.MergeRequest
evalContext.Project.MergeRequest = nil

// Copy "current user" into MR
evalContext.MergeRequest.CurrentUser = evalContext.CurrentUser

evalContext.MergeRequest.Labels = evalContext.MergeRequest.ResponseLabels.Nodes
evalContext.MergeRequest.ResponseLabels = nil

evalContext.Group = evalContext.Project.ResponseGroup
evalContext.Project.ResponseGroup = nil

evalContext.MergeRequest.Notes = evalContext.MergeRequest.ResponseNotes.Nodes
evalContext.MergeRequest.ResponseNotes.Nodes = nil

if len(evalContext.MergeRequest.ResponseFirstCommits.Nodes) > 0 {
evalContext.MergeRequest.FirstCommit = &evalContext.MergeRequest.ResponseFirstCommits.Nodes[0]

Expand Down
81 changes: 78 additions & 3 deletions pkg/scm/gitlab/context_merge_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package gitlab

import (
"errors"
"fmt"
"path/filepath"
"regexp"
"strings"
"time"

"github.com/jippi/scm-engine/pkg/stdlib"
)

func (e ContextMergeRequest) HasLabel(in string) bool {
Expand All @@ -17,12 +21,83 @@ func (e ContextMergeRequest) HasLabel(in string) bool {
return false
}

func (e ContextMergeRequest) LacksLabel(in string) bool {
func (e ContextMergeRequest) HasNoLabel(in string) bool {
return !e.HasLabel(in)
}

func (e ContextMergeRequest) IsOpen() bool {
return e.State != "close"
func (e ContextMergeRequest) StateIs(anyOf ...string) bool {
for _, stateString := range anyOf {
state := MergeRequestState(stateString)
if !state.IsValid() {
panic(fmt.Errorf("unknown state value: %q", stateString))
}

if state == e.State {
return true
}
}

return false
}

// has_no_activity_within
func (e ContextMergeRequest) HasNoActivityWithin(input any) bool {
return !e.HasAnyActivityWithin(input)
}

// has_any_activity_within
func (e ContextMergeRequest) HasAnyActivityWithin(input any) bool {
dur := stdlib.ToDuration(input)
now := time.Now()

for _, note := range e.Notes {
if now.Sub(note.UpdatedAt) < dur {
return true
}
}

if e.LastCommit != nil {
if now.Sub(*e.LastCommit.CommittedDate) < dur {
return true
}
}

return false
}

// has_no_user_activity_within
func (e ContextMergeRequest) HasNoUserActivityWithin(input any) bool {
return !e.HasUserActivityWithin(input)
}

// has_user_activity_within
func (e ContextMergeRequest) HasUserActivityWithin(input any) bool {
dur := stdlib.ToDuration(input)
now := time.Now()

for _, note := range e.Notes {
// Ignore "my" activity
if e.CurrentUser.Username == note.Author.Username {
continue
}

// Ignore bots
if e.Author.Bot {
continue
}

if now.Sub(note.UpdatedAt) < dur {
return true
}
}

if e.LastCommit != nil {
if now.Sub(*e.LastCommit.CommittedDate) < dur {
return true
}
}

return false
}

func (e ContextMergeRequest) ModifiedFilesList(patterns ...string) []string {
Expand Down
26 changes: 26 additions & 0 deletions pkg/stdlib/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package stdlib

import (
"fmt"
"time"

"github.com/xhit/go-str2duration/v2"
)

func ToDuration(input any) time.Duration {
switch val := input.(type) {
case time.Duration:
return val

case string:
dur, err := str2duration.ParseDuration(val)
if err != nil {
panic(err)
}

return dur

default:
panic(fmt.Errorf("unsupported input type for duration: %T", val))
}
}
66 changes: 61 additions & 5 deletions schema/gitlab.schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,50 @@ type Context {
"Information about the Merge Request"
MergeRequest: ContextMergeRequest @generated

"Get information about current user"
CurrentUser: ContextUser!

"Information about the event that triggered the evaluation. Empty when not using webhook server."
WebhookEvent: Any @generated
}

enum MergeRequestState {
"All available"
all

"In closed state"
closed

"Discussion has been locked"
locked

"Merge request has been merged"
merged

"Opened merge request"
opened
}

enum UserState {
"User is active and can use the system"
active

"User has been blocked by an administrator and cannot use the system"
blocked

"User is no longer active and cannot use the system"
deactivated

"User is blocked, and their contributions are hidden"
banned

"User has been blocked by the system"
ldap_blocked

"User is blocked and pending approval"
blocked_pending_approval
}

input ListMergeRequestsQueryInput {
project_id: ID!
state: MergeRequestState! = "opened"
Expand All @@ -69,11 +101,8 @@ type ListMergeRequestsProjectMergeRequest {
ID: String! @graphql(key: "iid") @internal
}

# https://docs.gitlab.com/ee/api/graphql/reference/#project
type ContextProject {
#
# Native GraphQL fields - https://docs.gitlab.com/ee/api/graphql/reference/#project
#

"Indicates the archived status of the project"
Archived: Boolean!
"Timestamp of the project creation"
Expand Down Expand Up @@ -141,6 +170,8 @@ type ContextMergeRequest {
ApprovalsRequired: Int
"Indicates if the merge request has all the required approvals"
Approved: Boolean!
"User who created this merge request"
Author: ContextUser!
"Indicates if auto merge is enabled for the merge request"
AutoMergeEnabled: Boolean!
"Selected auto merge strategy"
Expand Down Expand Up @@ -194,7 +225,7 @@ type ContextMergeRequest {
"Indicates if the merge request will be squashed when merged"
SquashOnMerge: Boolean!
"State of the merge request"
State: String!
State: MergeRequestState!
"Target branch of the merge request"
TargetBranch: String!
"Indicates if the target branch of the merge request exists"
Expand All @@ -219,16 +250,41 @@ type ContextMergeRequest {
ResponseLabels: ContextLabelNode @internal @graphql(key: "labels(first: 200)")
ResponseFirstCommits: ContextCommitsNode @internal @graphql(key: "first_commit: commits(first:1)")
ResponseLastCommits: ContextCommitsNode @internal @graphql(key: "last_commit: commits(last:1)")
ResponseNotes: ContextNotesNode @internal @graphql(key: "notes(last: 10)")

#
# scm-engine customs
#

Notes: [ContextNote!] @generated
FirstCommit: ContextCommit @generated()
LastCommit: ContextCommit @generated()
TimeBetweenFirstAndLastCommit: Duration @generated()
TimeSinceFirstCommit: Duration @generated()
TimeSinceLastCommit: Duration @generated()

CurrentUser: ContextUser! @generated @internal
}

# https://docs.gitlab.com/ee/api/graphql/reference/#note
type ContextNote {
Body: String!
UpdatedAt: Time!
CreatedAt: Time!
Author: ContextUser!
}

# Internal only, used to de-nest connections
type ContextNotesNode {
Nodes: [ContextNote!] @internal
}

# https://docs.gitlab.com/ee/api/graphql/reference/#user
type ContextUser {
Username: String!
Bot: Boolean!
PublicEmail: String
State: UserState!
}

# https://docs.gitlab.com/ee/api/graphql/reference/#commit
Expand Down

0 comments on commit 00959a0

Please sign in to comment.