diff --git a/modules/structs/hook.go b/modules/structs/hook.go index aaa9fbc9d364d..76ba97539a88d 100644 --- a/modules/structs/hook.go +++ b/modules/structs/hook.go @@ -286,6 +286,8 @@ const ( HookIssueReOpened HookIssueAction = "reopened" // HookIssueEdited edited HookIssueEdited HookIssueAction = "edited" + // HookIssueDeleted is an issue action for deleting an issue + HookIssueDeleted HookIssueAction = "deleted" // HookIssueAssigned assigned HookIssueAssigned HookIssueAction = "assigned" // HookIssueUnassigned unassigned diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index f7c2e5049bbc2..1eb23f3cc1ac3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2371,7 +2371,7 @@ settings.event_repository = Repository settings.event_repository_desc = Repository created or deleted. settings.event_header_issue = Issue Events settings.event_issues = Issues -settings.event_issues_desc = Issue opened, closed, reopened, or edited. +settings.event_issues_desc = Issue opened, closed, reopened, edited or deleted. settings.event_issue_assign = Issue Assigned settings.event_issue_assign_desc = Issue assigned or unassigned. settings.event_issue_label = Issue Labeled @@ -2382,7 +2382,7 @@ settings.event_issue_comment = Issue Comment settings.event_issue_comment_desc = Issue comment created, edited, or deleted. settings.event_header_pull_request = Pull Request Events settings.event_pull_request = Pull Request -settings.event_pull_request_desc = Pull request opened, closed, reopened, or edited. +settings.event_pull_request_desc = Pull request opened, closed, reopened, edited or deleted. settings.event_pull_request_assign = Pull Request Assigned settings.event_pull_request_assign_desc = Pull request assigned or unassigned. settings.event_pull_request_label = Pull Request Labeled diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index 9e3f21de290ee..6b7eae93a76ca 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -295,6 +295,43 @@ func (m *webhookNotifier) NewIssue(ctx context.Context, issue *issues_model.Issu } } +func (m *webhookNotifier) DeleteIssue(ctx context.Context, doer *user_model.User, issue *issues_model.Issue) { + permission, _ := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) + if issue.IsPull { + if err := issue.LoadPullRequest(ctx); err != nil { + log.Error("LoadPullRequest: %v", err) + return + } + if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventPullRequest, &api.PullRequestPayload{ + Action: api.HookIssueDeleted, + Index: issue.Index, + PullRequest: convert.ToAPIPullRequest(ctx, issue.PullRequest, doer), + Repository: convert.ToRepo(ctx, issue.Repo, permission), + Sender: convert.ToUser(ctx, doer, nil), + }); err != nil { + log.Error("PrepareWebhooks: %v", err) + } + } else { + if err := issue.LoadRepo(ctx); err != nil { + log.Error("issue.LoadRepo: %v", err) + return + } + if err := issue.LoadPoster(ctx); err != nil { + log.Error("issue.LoadPoster: %v", err) + return + } + if err := PrepareWebhooks(ctx, EventSource{Repository: issue.Repo}, webhook_module.HookEventIssues, &api.IssuePayload{ + Action: api.HookIssueDeleted, + Index: issue.Index, + Issue: convert.ToAPIIssue(ctx, issue.Poster, issue), + Repository: convert.ToRepo(ctx, issue.Repo, permission), + Sender: convert.ToUser(ctx, doer, nil), + }); err != nil { + log.Error("PrepareWebhooks: %v", err) + } + } +} + func (m *webhookNotifier) NewPullRequest(ctx context.Context, pull *issues_model.PullRequest, mentions []*user_model.User) { if err := pull.LoadIssue(ctx); err != nil { log.Error("pull.LoadIssue: %v", err) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 2e6a12df2cf47..7969634cdfdbc 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -151,6 +151,13 @@ func testNewIssue(t *testing.T, session *TestSession, user, repo, title, content return issueURL } +func testIssueDelete(t *testing.T, session *TestSession, issueURL string) { + req := NewRequestWithValues(t, "POST", path.Join(issueURL, "delete"), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusSeeOther) +} + func testIssueAssign(t *testing.T, session *TestSession, repoLink string, issueID, assigneeID int64) { req := NewRequestWithValues(t, "POST", fmt.Sprintf(repoLink+"/issues/assignee?issue_ids=%d", issueID), map[string]string{ "_csrf": GetUserCSRFToken(t, session), diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 438dd3211d3c0..77d00a8a39d5c 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "path" "strings" "testing" "time" @@ -450,6 +451,39 @@ func Test_WebhookIssue(t *testing.T) { }) } +func Test_WebhookIssueDelete(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.IssuePayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.IssuePayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "issue" + }, http.StatusOK) + defer provider.Close() + + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "issues") + issueURL := testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") + + // 2. trigger the webhook + testIssueDelete(t, session, issueURL) + + // 3. validate the webhook is triggered + assert.Equal(t, "issue", triggeredEvent) + require.Len(t, payloads, 2) + assert.EqualValues(t, "deleted", payloads[1].Action) + assert.Equal(t, "repo1", payloads[1].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[1].Issue.Repo.FullName) + assert.Equal(t, "Title1", payloads[1].Issue.Title) + assert.Equal(t, "Description1", payloads[1].Issue.Body) + }) +} + func Test_WebhookIssueAssign(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { var payloads []api.PullRequestPayload @@ -596,6 +630,44 @@ func Test_WebhookPullRequest(t *testing.T) { }) } +func Test_WebhookPullRequestDelete(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + var payloads []api.PullRequestPayload + var triggeredEvent string + provider := newMockWebhookProvider(func(r *http.Request) { + content, _ := io.ReadAll(r.Body) + var payload api.PullRequestPayload + err := json.Unmarshal(content, &payload) + assert.NoError(t, err) + payloads = append(payloads, payload) + triggeredEvent = "pull_request" + }, http.StatusOK) + defer provider.Close() + + // 1. create a new webhook with special webhook for repo1 + session := loginUser(t, "user2") + testAPICreateWebhookForRepo(t, session, "user2", "repo1", provider.URL(), "pull_request") + + testAPICreateBranch(t, session, "user2", "repo1", "master", "master2", http.StatusCreated) + + repo1 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1}) + issueURL := testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") + + // 2. trigger the webhook + testIssueDelete(t, session, path.Join(repo1.Link(), "pulls", issueURL)) + + // 3. validate the webhook is triggered + assert.Equal(t, "pull_request", triggeredEvent) + require.Len(t, payloads, 2) + assert.EqualValues(t, "deleted", payloads[1].Action) + assert.Equal(t, "repo1", payloads[1].PullRequest.Base.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[1].PullRequest.Base.Repository.FullName) + assert.Equal(t, 0, *payloads[1].PullRequest.Additions) + assert.Equal(t, 0, *payloads[1].PullRequest.ChangedFiles) + assert.Equal(t, 0, *payloads[1].PullRequest.Deletions) + }) +} + func Test_WebhookPullRequestComment(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { var payloads []api.IssueCommentPayload