11defmodule CodeCorps.GitHub.Event.Issues do
22 @ moduledoc ~S"""
3- In charge of dealing with "Issues" GitHub Webhook events
3+ In charge of handling a GitHub Webhook payload for the Issues event type
44
5- https://developer.github.com/v3/activity/events/types/#issuesevent
5+ [ https://developer.github.com/v3/activity/events/types/#issuesevent](https://developer.github.com/v3/activity/events/types/#issuesevent)
66 """
77
8+ @ behaviour CodeCorps.GitHub.Event.Handler
9+
810 alias CodeCorps . {
911 GithubEvent ,
1012 GitHub.Event.Common.RepoFinder ,
@@ -16,63 +18,70 @@ defmodule CodeCorps.GitHub.Event.Issues do
1618 }
1719 alias Ecto.Multi
1820
19- @ typep outcome :: { :ok , list ( Task . t ) } |
20- { :error , :not_fully_implemented } |
21- { :error , :unexpected_payload } |
22- { :error , :unexpected_action } |
23- { :error , :unmatched_repository }
24-
25- @ implemented_actions ~w( opened closed edited reopened)
26- @ unimplemented_actions ~w( assigned unassigned milestoned demilestoned labeled unlabeled)
21+ @ type outcome :: { :ok , list ( Task . t ) } |
22+ { :error , :not_fully_implemented } |
23+ { :error , :unexpected_action } |
24+ { :error , :unexpected_payload } |
25+ { :error , :repository_not_found } |
26+ { :error , :validation_error_on_inserting_user } |
27+ { :error , :multiple_github_users_matched_same_cc_user } |
28+ { :error , :validation_error_on_syncing_tasks } |
29+ { :error , :unexpected_transaction_outcome }
2730
2831 @ doc ~S"""
2932 Handles the "Issues" GitHub webhook
3033
3134 The process is as follows
3235 - validate the payload is structured as expected
33- - try and find the appropriate `GithubRepo` record.
34- - for each `ProjectGithubRepo` belonging to that `Project `
35- - find or initialize a new `Task `
36- - try and find a `User`, associate `Task` with user
37- - commit the change as an insert or update action
36+ - validate the action is properly supported
37+ - match payload with affected `CodeCorps.GithubRepo` record using `CodeCorps.GitHub.Event.Common.RepoFinder `
38+ - match with a `CodeCorps.User` using `CodeCorps.GitHub.Event.Issues.UserLinker `
39+ - for each `CodeCorps.ProjectGithubRepo` belonging to matched repo
40+ - match and update, or create a `CodeCorps.Task` on the associated `CodeCorps.Project`
3841
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
42+ If the process runs all the way through, the function will return an `:ok`
43+ tuple with a list of affected (created or updated) tasks.
4544
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.
45+ If it fails, it will instead return an `:error` tuple, where the second
46+ element is the atom indicating a reason.
5147 """
5248 @ spec handle ( GithubEvent . t , map ) :: outcome
53- def handle ( % GithubEvent { action: action } , payload ) when action in @ implemented_actions do
54- case payload |> Validator . valid? do
55- true -> do_handle ( payload )
56- false -> { :error , :unexpected_payload }
57- end
49+ def handle ( % GithubEvent { } , % { } = payload ) do
50+ Multi . new
51+ |> Multi . run ( :payload , fn _ -> payload |> validate_payload ( ) end )
52+ |> Multi . run ( :action , fn _ -> payload |> validate_action ( ) end )
53+ |> Multi . run ( :repo , fn _ -> RepoFinder . find_repo ( payload ) end )
54+ |> Multi . run ( :user , fn _ -> UserLinker . find_or_create_user ( payload ) end )
55+ |> Multi . run ( :tasks , fn % { repo: github_repo , user: user } -> TaskSyncer . sync_all ( github_repo , user , payload ) end )
56+ |> Repo . transaction
57+ |> marshall_result ( )
5858 end
59- def handle ( % GithubEvent { action: action } , _payload ) when action in @ unimplemented_actions do
60- { :error , :not_fully_implemented }
61- end
62- def handle ( % GithubEvent { action: _action } , _payload ) , do: { :error , :unexpected_action }
6359
64- @ spec do_handle ( map ) :: { :ok , list ( Task . t ) } | { :error , :unmatched_repository }
65- defp do_handle ( % { } = payload ) do
66- multi =
67- Multi . new
68- |> Multi . run ( :repo , fn _ -> RepoFinder . find_repo ( payload ) end )
69- |> Multi . run ( :user , fn _ -> UserLinker . find_or_create_user ( payload ) end )
70- |> Multi . run ( :tasks , fn % { repo: github_repo , user: user } -> TaskSyncer . sync_all ( github_repo , user , payload ) end )
60+ @ spec marshall_result ( tuple ) :: tuple
61+ defp marshall_result ( { :ok , % { tasks: tasks } } ) , do: { :ok , tasks }
62+ defp marshall_result ( { :error , :payload , :invalid , _steps } ) , do: { :error , :unexpected_payload }
63+ defp marshall_result ( { :error , :action , :not_fully_implemented , _steps } ) , do: { :error , :not_fully_implemented }
64+ defp marshall_result ( { :error , :action , :unexpected_action , _steps } ) , do: { :error , :unexpected_action }
65+ defp marshall_result ( { :error , :repo , :unmatched_project , _steps } ) , do: { :ok , [ ] }
66+ defp marshall_result ( { :error , :repo , :unmatched_repository , _steps } ) , do: { :error , :repository_not_found }
67+ defp marshall_result ( { :error , :user , % Ecto.Changeset { } , _steps } ) , do: { :error , :validation_error_on_inserting_user }
68+ defp marshall_result ( { :error , :user , :multiple_users , _steps } ) , do: { :error , :multiple_github_users_matched_same_cc_user }
69+ defp marshall_result ( { :error , :tasks , { _tasks , _errors } , _steps } ) , do: { :error , :validation_error_on_syncing_tasks }
70+ defp marshall_result ( { :error , _errored_step , _error_response , _steps } ) , do: { :error , :unexpected_transaction_outcome }
71+
72+ @ implemented_actions ~w( opened closed edited reopened)
73+ @ unimplemented_actions ~w( assigned unassigned milestoned demilestoned labeled unlabeled)
74+
75+ @ spec validate_action ( map ) :: { :ok , :implemented } | { :error , :not_fully_implemented | :unexpected_action }
76+ defp validate_action ( % { "action" => action } ) when action in @ implemented_actions , do: { :ok , :implemented }
77+ defp validate_action ( % { "action" => action } ) when action in @ unimplemented_actions , do: { :error , :not_fully_implemented }
78+ defp validate_action ( _payload ) , do: { :error , :unexpected_action }
7179
72- case Repo . transaction ( multi ) do
73- { :ok , % { tasks: tasks } } -> { :ok , tasks }
74- { :error , :repo , :unmatched_project , _steps } -> { :ok , [ ] }
75- { :error , _errored_step , error_response , _steps } -> { :error , error_response }
80+ @ spec validate_payload ( map ) :: { :ok , :valid } | { :error , :invalid }
81+ defp validate_payload ( % { } = payload ) do
82+ case payload |> Validator . valid? do
83+ true -> { :ok , :valid }
84+ false -> { :error , :invalid }
7685 end
7786 end
7887end
0 commit comments