Skip to content

Commit e1051fc

Browse files
authored
Merge pull request #1198 from code-corps/1192-save-changeset-errors
Save changeset errors and data for GitHub webhooks
2 parents f82f9cd + 7b77518 commit e1051fc

File tree

19 files changed

+151
-28
lines changed

19 files changed

+151
-28
lines changed

config/config.exs

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ config :sentry,
8282
included_environments: ~w(prod staging)a,
8383
use_error_logger: true
8484

85+
config :code_corps, :sentry, CodeCorps.Sentry.Async
86+
8587
config :code_corps, :processor, CodeCorps.Processor.Async
8688

8789
# Import environment specific config. This must remain at the bottom

config/test.exs

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ config :code_corps,
5757
config :sentry,
5858
environment_name: Mix.env || :test
5959

60+
config :code_corps, :sentry, CodeCorps.Sentry.Sync
61+
6062
config :code_corps, :processor, CodeCorps.Processor.Sync
6163

6264
config :code_corps, CodeCorps.Mailer,

lib/code_corps/github/event.ex

+25-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ defmodule CodeCorps.GitHub.Event do
77
alias CodeCorps.{GithubEvent, Repo}
88
alias Ecto.Changeset
99

10+
defmodule GitHubEventError do
11+
defexception [:reason]
12+
13+
def exception(reason),
14+
do: %__MODULE__{reason: reason}
15+
16+
def message(%__MODULE__{reason: reason}),
17+
do: reason
18+
end
19+
1020
@type error :: atom | Changeset.t
1121

1222
@doc ~S"""
@@ -33,11 +43,24 @@ defmodule CodeCorps.GitHub.Event do
3343
|> Changeset.change(%{status: "processed"})
3444
|> Repo.update()
3545
end
36-
def stop_processing({:error, reason}, %GithubEvent{} = event) do
37-
changes = %{status: "errored", failure_reason: reason |> Atom.to_string}
46+
def stop_processing({:error, reason, error}, %GithubEvent{} = event) do
47+
%GitHubEventError{reason: error}
48+
|> CodeCorps.Sentry.capture_exception([stacktrace: System.stacktrace()])
49+
50+
changes = %{
51+
status: "errored",
52+
data: error |> format_data_if_exists(),
53+
error: error |> Kernel.inspect(pretty: true),
54+
failure_reason: reason |> Atom.to_string()
55+
}
3856

3957
event
4058
|> Changeset.change(changes)
4159
|> Repo.update()
4260
end
61+
62+
defp format_data_if_exists(%Ecto.Changeset{data: data}) do
63+
data |> Kernel.inspect(pretty: true)
64+
end
65+
defp format_data_if_exists(_error), do: nil
4366
end

lib/code_corps/github/sync/sync.ex

+14-15
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule CodeCorps.GitHub.Sync do
2020
@type outcome :: {:ok, list(Comment.t)}
2121
| {:ok, GithubPullRequest.t}
2222
| {:ok, list(CodeCorps.Task.t)}
23-
| {:error, :repo_not_found}
23+
| {:error, :repo_not_found, %{}}
2424
| {:error, :fetching_issue}
2525
| {:error, :fetching_pull_request}
2626
| {:error, :multiple_issue_users_match}
@@ -232,18 +232,17 @@ defmodule CodeCorps.GitHub.Sync do
232232
defp marshall_result({:ok, %{github_pull_request: _, github_issue: _}} = result), do: result
233233
defp marshall_result({:ok, %{github_pull_request: pull_request}}), do: {:ok, pull_request}
234234
defp marshall_result({:ok, %{task: task}}), do: {:ok, task}
235-
defp marshall_result({:error, :repo, :unmatched_project, _steps}), do: {:ok, []}
236-
defp marshall_result({:error, :repo, :unmatched_repository, _steps}), do: {:error, :repo_not_found}
237-
defp marshall_result({:error, :fetch_issue, _, _steps}), do: {:error, :fetching_issue}
238-
defp marshall_result({:error, :fetch_pull_request, _, _steps}), do: {:error, :fetching_pull_request}
239-
defp marshall_result({:error, :github_pull_request, %Ecto.Changeset{}, _steps}), do: {:error, :validating_github_pull_request}
240-
defp marshall_result({:error, :github_issue, %Ecto.Changeset{}, _steps}), do: {:error, :validating_github_issue}
241-
defp marshall_result({:error, :github_comment, %Ecto.Changeset{}, _steps}), do: {:error, :validating_github_comment}
242-
defp marshall_result({:error, :comment_user, %Ecto.Changeset{}, _steps}), do: {:error, :validating_user}
243-
defp marshall_result({:error, :comment_user, :multiple_users, _steps}), do: {:error, :multiple_comment_users_match}
244-
defp marshall_result({:error, :issue_user, %Ecto.Changeset{}, _steps}), do: {:error, :validating_user}
245-
defp marshall_result({:error, :issue_user, :multiple_users, _steps}), do: {:error, :multiple_issue_users_match}
246-
defp marshall_result({:error, :comment, {_comments, _errors}, _steps}), do: {:error, :validating_comment}
247-
defp marshall_result({:error, :task, %Ecto.Changeset{}, _steps}), do: {:error, :validating_task}
248-
defp marshall_result({:error, _errored_step, _error_response, _steps}), do: {:error, :unexpected_transaction_outcome}
235+
defp marshall_result({:error, :repo, :unmatched_repository, _steps}), do: {:error, :repo_not_found, %{}}
236+
defp marshall_result({:error, :fetch_issue, _, _steps}), do: {:error, :fetching_issue, %{}}
237+
defp marshall_result({:error, :fetch_pull_request, _, _steps}), do: {:error, :fetching_pull_request, %{}}
238+
defp marshall_result({:error, :github_pull_request, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_github_pull_request, changeset}
239+
defp marshall_result({:error, :github_issue, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_github_issue, changeset}
240+
defp marshall_result({:error, :github_comment, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_github_comment, changeset}
241+
defp marshall_result({:error, :comment_user, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_user, changeset}
242+
defp marshall_result({:error, :comment_user, :multiple_users, _steps}), do: {:error, :multiple_comment_users_match, %{}}
243+
defp marshall_result({:error, :issue_user, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_user, changeset}
244+
defp marshall_result({:error, :issue_user, :multiple_users, _steps}), do: {:error, :multiple_issue_users_match, %{}}
245+
defp marshall_result({:error, :comment, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_comment, changeset}
246+
defp marshall_result({:error, :task, %Ecto.Changeset{} = changeset, _steps}), do: {:error, :validating_task, changeset}
247+
defp marshall_result({:error, _errored_step, error_response, _steps}), do: {:error, :unexpected_transaction_outcome, error_response}
249248
end

lib/code_corps/model/github_event.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ defmodule CodeCorps.GithubEvent do
66

77
schema "github_events" do
88
field :action, :string
9+
field :data, :string
910
field :failure_reason, :string
1011
field :github_delivery_id, :string
1112
field :payload, :map
13+
field :error, :string
1214
field :status, :string
1315
field :type, :string
1416

@@ -20,7 +22,7 @@ defmodule CodeCorps.GithubEvent do
2022
"""
2123
def changeset(struct, params \\ %{}) do
2224
struct
23-
|> cast(params, [:action, :github_delivery_id, :payload, :status, :type])
25+
|> cast(params, [:action, :data, :github_delivery_id, :payload, :error, :status, :type])
2426
|> validate_required([:action, :github_delivery_id, :payload, :status, :type])
2527
end
2628
end

lib/code_corps/sentry/async.ex

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
defmodule CodeCorps.Sentry.Async do
2+
def capture_exception(exception, opts \\ []) do
3+
exception
4+
|> Sentry.capture_exception(opts)
5+
end
6+
end

lib/code_corps/sentry/sentry.ex

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
defmodule CodeCorps.Sentry do
2+
@sentry Application.get_env(:code_corps, :sentry)
3+
4+
def capture_exception(exception, opts \\ []) do
5+
@sentry.capture_exception(exception, opts)
6+
end
7+
end

lib/code_corps/sentry/sync.ex

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
defmodule CodeCorps.Sentry.Sync do
2+
def capture_exception(exception, opts \\ []) do
3+
exception
4+
|> Sentry.capture_exception(opts |> Keyword.put(:result, :sync))
5+
end
6+
end

lib/code_corps_web/views/github_event_view.ex

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ defmodule CodeCorpsWeb.GithubEventView do
44
use JaSerializer.PhoenixView
55

66
attributes [
7-
:action, :event_type, :failure_reason, :github_delivery_id, :inserted_at,
8-
:payload, :status, :updated_at
7+
:action, :data, :event_type, :error, :failure_reason, :github_delivery_id,
8+
:inserted_at, :payload, :status, :updated_at
99
]
1010

1111
def event_type(github_event, _conn) do
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defmodule CodeCorps.Repo.Migrations.AddSerializedErrorToGithubEvents do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:github_events) do
6+
add :data, :text
7+
add :error, :text
8+
end
9+
end
10+
end

priv/repo/structure.sql

+4-2
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,9 @@ CREATE TABLE github_events (
284284
inserted_at timestamp without time zone NOT NULL,
285285
updated_at timestamp without time zone NOT NULL,
286286
payload jsonb,
287-
failure_reason character varying(255)
287+
failure_reason character varying(255),
288+
data text,
289+
error text
288290
);
289291

290292

@@ -3799,5 +3801,5 @@ ALTER TABLE ONLY users
37993801
-- PostgreSQL database dump complete
38003802
--
38013803

3802-
INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713), (20170308222552), (20170313130611), (20170318032449), (20170318082740), (20170324194827), (20170424215355), (20170501225441), (20170505224222), (20170526095401), (20170602000208), (20170622205732), (20170626231059), (20170628092119), (20170628213609), (20170629183404), (20170630140136), (20170706132431), (20170707213648), (20170711122252), (20170717092127), (20170725060612), (20170727052644), (20170731130121), (20170814131722), (20170913114958), (20170921014405), (20170925214512), (20170925230419), (20170926134646), (20170927100300), (20170928234412), (20171003134956), (20171003225853), (20171006063358), (20171006161407), (20171012215106), (20171012221231), (20171016125229), (20171016125516), (20171016223356), (20171016235656), (20171017235433), (20171019191035), (20171025184225), (20171026010933), (20171027061833), (20171028011642), (20171028173508), (20171030182857), (20171031232023), (20171031234356), (20171101023309), (20171104013543), (20171106045740), (20171106050209), (20171106103153), (20171106200036), (20171109231538), (20171110001134), (20171114010851), (20171114033357), (20171114225214), (20171114225713), (20171114232534), (20171115201624);
3804+
INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713), (20170308222552), (20170313130611), (20170318032449), (20170318082740), (20170324194827), (20170424215355), (20170501225441), (20170505224222), (20170526095401), (20170602000208), (20170622205732), (20170626231059), (20170628092119), (20170628213609), (20170629183404), (20170630140136), (20170706132431), (20170707213648), (20170711122252), (20170717092127), (20170725060612), (20170727052644), (20170731130121), (20170814131722), (20170913114958), (20170921014405), (20170925214512), (20170925230419), (20170926134646), (20170927100300), (20170928234412), (20171003134956), (20171003225853), (20171006063358), (20171006161407), (20171012215106), (20171012221231), (20171016125229), (20171016125516), (20171016223356), (20171016235656), (20171017235433), (20171019191035), (20171025184225), (20171026010933), (20171027061833), (20171028011642), (20171028173508), (20171030182857), (20171031232023), (20171031234356), (20171101023309), (20171104013543), (20171106045740), (20171106050209), (20171106103153), (20171106200036), (20171109231538), (20171110001134), (20171114010851), (20171114033357), (20171114225214), (20171114225713), (20171114232534), (20171115201624), (20171115225358);
38033805

test/lib/code_corps/github/event/issue_comment/issue_comment_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ defmodule CodeCorps.GitHub.Event.IssueCommentTest do
3535
end
3636

3737
test "returns error if unmatched repository" do
38-
assert IssueComment.handle(@payload) == {:error, :repo_not_found}
38+
assert IssueComment.handle(@payload) == {:error, :repo_not_found, %{}}
3939
refute Repo.one(User)
4040
end
4141

test/lib/code_corps/github/event/issues/issues_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ defmodule CodeCorps.GitHub.Event.IssuesTest do
3535
end
3636

3737
test "returns error if unmatched repository" do
38-
assert Issues.handle(@payload) == {:error, :repo_not_found}
38+
assert Issues.handle(@payload) == {:error, :repo_not_found, %{}}
3939
refute Repo.one(User)
4040
end
4141

test/lib/code_corps/github/event/pull_request/pull_request_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ defmodule CodeCorps.GitHub.Event.PullRequestTest do
3636
end
3737

3838
test "returns error if unmatched repository" do
39-
assert PullRequest.handle(@payload) == {:error, :repo_not_found}
39+
assert PullRequest.handle(@payload) == {:error, :repo_not_found, %{}}
4040
refute Repo.one(User)
4141
end
4242

test/lib/code_corps/github/event_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ defmodule CodeCorps.GitHub.EventTest do
2525

2626
test "marks event errored, with failure_reason, if resulting tuple starts with :error" do
2727
event = insert(:github_event, status: "processing")
28-
{:ok, %GithubEvent{} = updated_event} = Event.stop_processing({:error, :bar}, event)
28+
{:ok, %GithubEvent{} = updated_event} = Event.stop_processing({:error, :bar, %{}}, event)
2929
assert updated_event.status == "errored"
3030
assert updated_event.failure_reason == "bar"
3131
end

test/lib/code_corps/github/sync/sync_test.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ defmodule CodeCorps.GitHub.SyncTest do
144144
github_repo = setup_coderly_repo()
145145

146146
with_real_api do
147-
Sync.sync_repo(github_repo)
147+
Sync.sync_repo(github_repo)
148148
end
149149

150150
repo = Repo.one(GithubRepo)

test/lib/code_corps/github/webhook/handler_test.exs

+47-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ defmodule CodeCorps.GitHub.Webhook.HandlerTest do
44
use CodeCorps.DbAccessCase
55

66
import CodeCorps.GitHub.TestHelpers
7+
import CodeCorps.TestEnvironmentHelper
78

89
alias CodeCorps.{
910
GithubEvent,
1011
GitHub.Webhook.Handler,
11-
Repo
12+
Repo,
13+
Task
1214
}
1315

1416
defp setup_repo(github_repo_id) do
@@ -240,6 +242,50 @@ defmodule CodeCorps.GitHub.Webhook.HandlerTest do
240242
end
241243
end
242244

245+
describe "handle_supported/3 when there are errors" do
246+
test "serializes error output" do
247+
%{"repository" => %{"id" => github_repo_id}}
248+
= opened_payload = load_event_fixture("issues_opened")
249+
250+
setup_repo(github_repo_id)
251+
252+
{:ok, %GithubEvent{}} = Handler.handle_supported("issues", "abc-123", opened_payload)
253+
254+
edited_payload = load_event_fixture("issues_edited")
255+
256+
edited_payload =
257+
edited_payload
258+
|> put_in(["issue", "updated_at"], "2006-05-05T23:40:28Z")
259+
260+
task = Repo.one(Task)
261+
changeset = Task.update_changeset(task, %{title: "New title", updated_from: "codecorps"})
262+
Repo.update!(changeset)
263+
264+
bypass = Bypass.open
265+
Bypass.expect bypass, fn conn ->
266+
{:ok, body, conn} = Plug.Conn.read_body(conn)
267+
assert body =~ "GitHubEventError"
268+
assert body =~ "CodeCorps"
269+
assert conn.request_path == "/api/1/store/"
270+
assert conn.method == "POST"
271+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
272+
end
273+
274+
modify_env(:sentry, environment_name: :prod)
275+
modify_env(:sentry, dsn: "http://public:secret@localhost:#{bypass.port}/1")
276+
277+
{:ok, %GithubEvent{} = event} = Handler.handle_supported("issues", "abc-456", edited_payload)
278+
279+
assert event.action == "edited"
280+
assert event.github_delivery_id == "abc-456"
281+
assert event.data == Repo.one(Task) |> Kernel.inspect(pretty: true)
282+
assert event.error # This is difficult to test, so just assert presence
283+
assert event.payload == edited_payload
284+
assert event.status == "errored"
285+
assert event.type == "issues"
286+
end
287+
end
288+
243289
describe "handle_unsupported/3" do
244290
[
245291
{"installation", "deleted"},

test/lib/code_corps_web/views/github_event_view_test.exs

+2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ defmodule CodeCorpsWeb.GithubEventViewTest do
1414
"type" => "github-event",
1515
"attributes" => %{
1616
"action" => github_event.action,
17+
"data" => github_event.data,
1718
"event-type" => github_event.type,
19+
"error" => github_event.error,
1820
"failure-reason" => github_event.failure_reason,
1921
"github-delivery-id" => github_event.github_delivery_id,
2022
"inserted-at" => github_event.inserted_at,
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
defmodule CodeCorps.TestEnvironmentHelper do
2+
def modify_env(app, overrides) do
3+
original_env = Application.get_all_env(app)
4+
Enum.each(overrides, fn {key, value} -> Application.put_env(app, key, value) end)
5+
6+
ExUnit.Callbacks.on_exit(fn ->
7+
Enum.each overrides, fn {key, _} ->
8+
if Keyword.has_key?(original_env, key) do
9+
Application.put_env(app, key, Keyword.fetch!(original_env, key))
10+
else
11+
Application.delete_env(app, key)
12+
end
13+
end
14+
end)
15+
end
16+
end

0 commit comments

Comments
 (0)