11defmodule CodeCorps.GitHub.Event.IssueComment do
22 @ moduledoc ~S"""
3- In charge of dealing with "IssueComment" GitHub Webhook events
4-
5- https://developer.github.com/v3/activity/events/types/#issuecommentevent
3+ In charge of handling a GitHub Webhook payload for the IssueComment event type
4+ [https://developer.github.com/v3/activity/events/types/#issuecommentevent](https://developer.github.com/v3/activity/events/types/#issuecommentevent)
65 """
76
7+ @ behaviour CodeCorps.GitHub.Event.Handler
8+
89 alias CodeCorps . {
910 Comment ,
1011 GithubEvent ,
@@ -18,63 +19,86 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
1819 }
1920 alias Ecto.Multi
2021
21- @ typep outcome :: { :ok , list ( Comment . t ) } |
22- { :error , :unexpected_payload } |
23- { :error , :unexpected_action } |
24- { :error , :unmatched_repository }
22+ @ type outcome :: { :ok , list ( Comment . t ) } |
23+ { :error , :unexpected_action } |
24+ { :error , :unexpected_payload } |
25+ { :error , :repository_not_found } |
26+ { :error , :validation_error_on_inserting_user_for_task } |
27+ { :error , :multiple_github_users_matched_same_cc_user_for_task } |
28+ { :error , :validation_error_on_inserting_user_for_comment } |
29+ { :error , :multiple_github_users_matched_same_cc_user_for_comment } |
30+ { :error , :validation_error_on_syncing_tasks } |
31+ { :error , :validation_error_on_syncing_comments } |
32+ { :error , :unexpected_transaction_outcome }
2533
2634 @ doc ~S"""
27- Handles the "Issues " GitHub webhook
35+ Handles the "IssueComment " GitHub webhook
2836
2937 The process is as follows
3038 - validate the payload is structured as expected
31- - try and find the appropriate `GithubRepo` record.
32- - for each `ProjectGithubRepo` belonging to that `Project `
33- - find or initialize a `Task `
34- - if initializing, associate with existing, or create `User `
35- - find or initialize a `Comment`
36- - if initializing, associate with existing , or create `User `
37- - commit the change as an insert or update action
39+ - validate the action is properly supported
40+ - match payload with affected `CodeCorps.GithubRepo` record using `CodeCorps.GitHub.Event.Common.RepoFinder `
41+ - match issue part of the payload with a `CodeCorps.User` using `CodeCorps.GitHub.Event.Issues.UserLinker `
42+ - match comment part of the payload with a `CodeCorps.User` using `CodeCorps.GitHub.Event.IssueComment.UserLinker `
43+ - for each `CodeCorps.ProjectGithubRepo` belonging to matched repo
44+ - match and update , or create a `CodeCorps.Task` on the associated `CodeCorps.Project `
45+ - match and update, or create a `CodeCorps.Comment` associated to `CodeCorps.Task`
3846
39- Depending on the success of the process, the function will return one of
40- - `{:ok, list_of_tasks}`
41- - `{:error, :not_fully_implemented}` - while we're aware of this action, we have not implemented support for it yet
42- - `{:error, :unexpected_payload}` - the payload was not as expected
43- - `{:error, :unexpected_action}` - the action was not of type we are aware of
44- - `{:error, :unmatched_repository}` - the repository for this issue was not found
47+ If the process runs all the way through, the function will return an `:ok`
48+ tuple with a list of affected (created or updated) comments.
4549
46- Note that it is also possible to have a matched GithubRepo, but with that
47- record not having any ProjectGithubRepo children. The outcome of that case
48- should NOT be an errored event, since it simply means that the GithubRepo
49- was not linked to a Project by the Project owner. This is allowed and
50- relatively common.
50+ If it fails, it will instead return an `:error` tuple, where the second
51+ element is the atom indicating a reason.
5152 """
5253 @ spec handle ( GithubEvent . t , map ) :: outcome
53- def handle ( % GithubEvent { action: action } , payload ) when action in ~w( created edited deleted) do
54- case payload |> IssueComment.Validator . valid? do
55- true -> do_handle ( payload )
56- false -> { :error , :unexpected_payload }
57- end
54+ def handle ( % GithubEvent { } , payload ) do
55+ Multi . new
56+ |> Multi . run ( :payload , fn _ -> payload |> validate_payload ( ) end )
57+ |> Multi . run ( :action , fn _ -> payload |> validate_action ( ) end )
58+ |> Multi . append ( payload |> operational_multi ( ) )
59+ |> Repo . transaction
60+ |> marshall_result ( )
61+ end
62+
63+ @ spec operational_multi ( map ) :: Multi . t
64+ defp operational_multi ( % { "action" => action } = payload ) when action in ~w( created edited) do
65+ Multi . new
66+ |> Multi . run ( :repo , fn _ -> RepoFinder . find_repo ( payload ) end )
67+ |> Multi . run ( :issue_user , fn _ -> Issues.UserLinker . find_or_create_user ( payload ) end )
68+ |> Multi . run ( :comment_user , fn _ -> IssueComment.UserLinker . find_or_create_user ( payload ) end )
69+ |> Multi . run ( :tasks , fn % { repo: github_repo , issue_user: user } -> TaskSyncer . sync_all ( github_repo , user , payload ) end )
70+ |> Multi . run ( :comments , fn % { tasks: tasks , comment_user: user } -> CommentSyncer . sync_all ( tasks , user , payload ) end )
71+ end
72+ defp operational_multi ( % { "action" => "deleted" } = payload ) do
73+ Multi . new
74+ |> Multi . run ( :comments , fn _ -> CommentDeleter . delete_all ( payload ) end )
5875 end
59- def handle ( % GithubEvent { action: _action } , _payload ) , do: { :error , :unexpected_action }
76+ defp operational_multi ( % { } ) , do: Multi . new
6077
61- @ spec do_handle ( map ) :: { :ok , list ( Comment . t ) } | { :error , :unmatched_repository }
62- defp do_handle ( % { "action" => action } = payload ) when action in ~w( created edited) do
63- multi =
64- Multi . new
65- |> Multi . run ( :repo , fn _ -> RepoFinder . find_repo ( payload ) end )
66- |> Multi . run ( :issue_user , fn _ -> Issues.UserLinker . find_or_create_user ( payload ) end )
67- |> Multi . run ( :comment_user , fn _ -> IssueComment.UserLinker . find_or_create_user ( payload ) end )
68- |> Multi . run ( :tasks , fn % { repo: github_repo , issue_user: user } -> TaskSyncer . sync_all ( github_repo , user , payload ) end )
69- |> Multi . run ( :comments , fn % { tasks: tasks , comment_user: user } -> CommentSyncer . sync_all ( tasks , user , payload ) end )
78+ @ spec marshall_result ( tuple ) :: tuple
79+ defp marshall_result ( { :ok , % { comments: comments } } ) , do: { :ok , comments }
80+ defp marshall_result ( { :error , :payload , :invalid , _steps } ) , do: { :error , :unexpected_payload }
81+ defp marshall_result ( { :error , :action , :unexpected_action , _steps } ) , do: { :error , :unexpected_action }
82+ defp marshall_result ( { :error , :repo , :unmatched_project , _steps } ) , do: { :ok , [ ] }
83+ defp marshall_result ( { :error , :repo , :unmatched_repository , _steps } ) , do: { :error , :repository_not_found }
84+ defp marshall_result ( { :error , :issue_user , % Ecto.Changeset { } , _steps } ) , do: { :error , :validation_error_on_inserting_user_for_task }
85+ defp marshall_result ( { :error , :issue_user , :multiple_users , _steps } ) , do: { :error , :multiple_github_users_matched_same_cc_user_for_task }
86+ defp marshall_result ( { :error , :comment_user , % Ecto.Changeset { } , _steps } ) , do: { :error , :validation_error_on_inserting_user_for_comment }
87+ defp marshall_result ( { :error , :comment_user , :multiple_users , _steps } ) , do: { :error , :multiple_github_users_matched_same_cc_user_for_comment }
88+ defp marshall_result ( { :error , :tasks , { _tasks , _errors } , _steps } ) , do: { :error , :validation_error_on_syncing_tasks }
89+ defp marshall_result ( { :error , :comments , { _comments , _errors } , _steps } ) , do: { :error , :validation_error_on_syncing_comments }
90+ defp marshall_result ( { :error , _errored_step , _error_response , _steps } ) , do: { :error , :unexpected_transaction_outcome }
7091
71- case Repo . transaction ( multi ) do
72- { :ok , % { comments: comments } } -> { :ok , comments }
73- { :error , :repo , :unmatched_project , _steps } -> { :ok , [ ] }
74- { :error , _errored_step , error_response , _steps } -> { :error , error_response }
92+ @ spec validate_payload ( map ) :: { :ok , :valid } | { :error , :invalid }
93+ defp validate_payload ( % { } = payload ) do
94+ case payload |> IssueComment.Validator . valid? do
95+ true -> { :ok , :valid }
96+ false -> { :error , :invalid }
7597 end
7698 end
77- defp do_handle ( % { "action" => "deleted" } = payload ) do
78- CommentDeleter . delete_all ( payload )
79- end
99+
100+ @ implemented_actions ~w( created edited deleted)
101+ @ spec validate_action ( map ) :: { :ok , :implemented } | { :error , :unexpected_action }
102+ defp validate_action ( % { "action" => action } ) when action in @ implemented_actions , do: { :ok , :implemented }
103+ defp validate_action ( % { } ) , do: { :error , :unexpected_action }
80104end
0 commit comments