Skip to content

Commit 59e5644

Browse files
committed
Create GithubIssue and modify Task
Add IssueLinker, Issue adapter, incorporate other tweaks Incorporate the GithubRepo into issue linking Update TaskSyncer and ChangesetBuilder Update TaskView and add GithubIssueView Rewrite IssueLinker to take issue params and Task.Service to create issues
1 parent b9cd900 commit 59e5644

34 files changed

+757
-172
lines changed

lib/code_corps/comment/service.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule CodeCorps.Comment.Service do
99

1010
require Logger
1111

12-
@preloads [:user, task: [github_repo: :github_app_installation]]
12+
@preloads [:user, task: [:github_issue, github_repo: :github_app_installation]]
1313

1414
@doc ~S"""
1515
Creates a `Comment` record using the provided parameters
@@ -49,9 +49,9 @@ defmodule CodeCorps.Comment.Service do
4949

5050
@spec connect_to_github(Comment.t) :: {:ok, Comment.t} :: {:error, GitHub.api_error_struct}
5151
defp connect_to_github(
52-
%Comment{task: %Task{github_repo: nil, github_issue_number: nil}} = comment), do: {:ok, comment}
52+
%Comment{task: %Task{github_repo: nil}} = comment), do: {:ok, comment}
5353
defp connect_to_github(
54-
%Comment{task: %Task{github_repo: %GithubRepo{} = _, github_issue_number: _}} = comment) do
54+
%Comment{task: %Task{github_repo: %GithubRepo{}}} = comment) do
5555

5656
with {:ok, github_comment} <- comment |> GitHub.Comment.create do
5757
comment |> link_with_github_changeset(github_comment) |> Repo.update
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
defmodule CodeCorps.GitHub.Adapters.Issue do
2+
@moduledoc """
3+
Used to adapt a GitHub Issue payload into attributes for creating or updating
4+
a `CodeCorps.GithubIssue` and vice-versa.
5+
"""
6+
7+
alias CodeCorps.{
8+
Adapter.MapTransformer,
9+
GithubIssue
10+
}
11+
12+
@mapping [
13+
{:body, ["body"]},
14+
{:closed_at, ["closed_at"]},
15+
{:comments_url, ["comments_url"]},
16+
{:events_url, ["events_url"]},
17+
{:github_created_at, ["created_at"]},
18+
{:github_id, ["id"]},
19+
{:github_updated_at, ["updated_at"]},
20+
{:html_url, ["html_url"]},
21+
{:labels_url, ["labels_url"]},
22+
{:locked, ["locked"]},
23+
{:number, ["number"]},
24+
{:state, ["state"]},
25+
{:title, ["title"]},
26+
{:url, ["url"]}
27+
]
28+
29+
@doc ~S"""
30+
Converts a GitHub Issue payload into a set of attributes used to update or
31+
create a `GithubIssue` record.
32+
"""
33+
@spec from_api(map) :: map
34+
def from_api(%{} = payload) do
35+
payload |> MapTransformer.transform(@mapping)
36+
end
37+
38+
@autogenerated_github_keys ~w(closed_at comments_url created_at events_url html_url id labels_url number updated_at url)
39+
40+
@doc ~S"""
41+
Converts a `GithubIssue` into a set of attributes used to update or create an
42+
associated GitHub Issue.
43+
"""
44+
@spec to_api(GithubIssue.t) :: map
45+
def to_api(%GithubIssue{} = github_issue) do
46+
github_issue
47+
|> Map.from_struct
48+
|> MapTransformer.transform_inverse(@mapping)
49+
|> Map.drop(@autogenerated_github_keys)
50+
end
51+
end

lib/code_corps/github/comment/comment.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule CodeCorps.GitHub.Comment do
33
Handles GitHub API requests for actions on Comments
44
"""
55

6-
alias CodeCorps.{Comment, GitHub, GithubAppInstallation, GithubRepo, Task, User}
6+
alias CodeCorps.{Comment, GitHub, GithubAppInstallation, GithubIssue, GithubRepo, Task, User}
77

88
@spec create(Comment.t) :: GitHub.response
99
def create(
@@ -64,7 +64,9 @@ defmodule CodeCorps.GitHub.Comment do
6464
defp create_endpoint_for(
6565
%Comment{
6666
task: %Task{
67-
github_issue_number: number,
67+
github_issue: %GithubIssue{
68+
number: number
69+
},
6870
github_repo: %GithubRepo{
6971
github_account_login: owner, name: repo
7072
},

lib/code_corps/github/event/issue_comment.ex

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
88

99
alias CodeCorps.{
1010
Comment,
11+
GithubRepo,
1112
GitHub.Event.Common.RepoFinder,
1213
GitHub.Event.Issues,
1314
GitHub.Event.Issues.TaskSyncer,
@@ -22,6 +23,7 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
2223
{:error, :unexpected_action} |
2324
{:error, :unexpected_payload} |
2425
{:error, :repository_not_found} |
26+
{:error, :validation_error_on_inserting_issue_for_task} |
2527
{:error, :validation_error_on_inserting_user_for_task} |
2628
{:error, :multiple_github_users_matched_same_cc_user_for_task} |
2729
{:error, :validation_error_on_inserting_user_for_comment} |
@@ -60,12 +62,13 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
6062
end
6163

6264
@spec operational_multi(map) :: Multi.t
63-
defp operational_multi(%{"action" => action} = payload) when action in ~w(created edited) do
65+
defp operational_multi(%{"action" => action, "issue" => issue_payload} = payload) when action in ~w(created edited) do
6466
Multi.new
6567
|> Multi.run(:repo, fn _ -> RepoFinder.find_repo(payload) end)
66-
|> Multi.run(:issue_user, fn _ -> Issues.UserLinker.find_or_create_user(payload) end)
68+
|> Multi.run(:issue, fn %{repo: %GithubRepo{} = github_repo} -> github_repo |> Issues.IssueLinker.create_or_update_issue(issue_payload) end)
69+
|> Multi.run(:issue_user, fn %{issue: github_issue} -> github_issue |> Issues.UserLinker.find_or_create_user(payload) end)
6770
|> 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)
71+
|> Multi.run(:tasks, fn %{issue: github_issue, repo: github_repo, issue_user: user} -> github_issue |> TaskSyncer.sync_all(github_repo, user, payload) end)
6972
|> Multi.run(:comments, fn %{tasks: tasks, comment_user: user} -> CommentSyncer.sync_all(tasks, user, payload) end)
7073
end
7174
defp operational_multi(%{"action" => "deleted"} = payload) do
@@ -80,6 +83,7 @@ defmodule CodeCorps.GitHub.Event.IssueComment do
8083
defp marshall_result({:error, :action, :unexpected_action, _steps}), do: {:error, :unexpected_action}
8184
defp marshall_result({:error, :repo, :unmatched_project, _steps}), do: {:ok, []}
8285
defp marshall_result({:error, :repo, :unmatched_repository, _steps}), do: {:error, :repository_not_found}
86+
defp marshall_result({:error, :issue, %Ecto.Changeset{}, _steps}), do: {:error, :validation_error_on_inserting_issue_for_task}
8387
defp marshall_result({:error, :issue_user, %Ecto.Changeset{}, _steps}), do: {:error, :validation_error_on_inserting_user_for_task}
8488
defp marshall_result({:error, :issue_user, :multiple_users, _steps}), do: {:error, :multiple_github_users_matched_same_cc_user_for_task}
8589
defp marshall_result({:error, :comment_user, %Ecto.Changeset{}, _steps}), do: {:error, :validation_error_on_inserting_user_for_comment}

lib/code_corps/github/event/issues.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule CodeCorps.GitHub.Event.Issues do
99

1010
alias CodeCorps.{
1111
GitHub.Event.Common.RepoFinder,
12+
GitHub.Event.Issues.IssueLinker,
1213
GitHub.Event.Issues.TaskSyncer,
1314
GitHub.Event.Issues.UserLinker,
1415
GitHub.Event.Issues.Validator,
@@ -50,12 +51,18 @@ defmodule CodeCorps.GitHub.Event.Issues do
5051
|> Multi.run(:payload, fn _ -> payload |> validate_payload() end)
5152
|> Multi.run(:action, fn _ -> payload |> validate_action() end)
5253
|> Multi.run(:repo, fn _ -> RepoFinder.find_repo(payload) end)
53-
|> Multi.run(:user, fn _ -> UserLinker.find_or_create_user(payload) end)
54-
|> Multi.run(:tasks, fn %{repo: github_repo, user: user} -> TaskSyncer.sync_all(github_repo, user, payload) end)
54+
|> Multi.run(:issue, fn %{repo: github_repo} -> link_issue(github_repo, payload) end)
55+
|> Multi.run(:user, fn %{issue: github_issue} -> UserLinker.find_or_create_user(github_issue, payload) end)
56+
|> Multi.run(:tasks, fn %{issue: github_issue, repo: github_repo, user: user} -> TaskSyncer.sync_all(github_issue, github_repo, user, payload) end)
5557
|> Repo.transaction
5658
|> marshall_result()
5759
end
5860

61+
@spec link_issue(GithubRepo.t, map) :: {:ok, GithubIssue.t} | {:error, Ecto.Changeset.t}
62+
defp link_issue(github_repo, %{"issue" => attrs}) do
63+
IssueLinker.create_or_update_issue(github_repo, attrs)
64+
end
65+
5966
@spec marshall_result(tuple) :: tuple
6067
defp marshall_result({:ok, %{tasks: tasks}}), do: {:ok, tasks}
6168
defp marshall_result({:error, :payload, :invalid, _steps}), do: {:error, :unexpected_payload}

lib/code_corps/github/event/issues/changeset_builder.ex

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule CodeCorps.GitHub.Event.Issues.ChangesetBuilder do
55
"""
66

77
alias CodeCorps.{
8+
GithubIssue,
89
ProjectGithubRepo,
910
Repo,
1011
Services.MarkdownRendererService,
@@ -22,24 +23,26 @@ defmodule CodeCorps.GitHub.Event.Issues.ChangesetBuilder do
2223
2324
The changeset can be used to create or update a `Task`
2425
"""
25-
@spec build_changeset(Task.t, map, ProjectGithubRepo.t, User.t) :: Changeset.t
26+
@spec build_changeset(Task.t, map, GithubIssue.t, ProjectGithubRepo.t, User.t) :: Changeset.t
2627
def build_changeset(
2728
%Task{id: task_id} = task,
2829
%{"issue" => issue_attrs},
30+
%GithubIssue{} = github_issue,
2931
%ProjectGithubRepo{} = project_github_repo,
3032
%User{} = user) do
3133

3234
case is_nil(task_id) do
33-
true -> create_changeset(task, issue_attrs, project_github_repo, user)
35+
true -> create_changeset(task, issue_attrs, github_issue, project_github_repo, user)
3436
false -> update_changeset(task, issue_attrs)
3537
end
3638
end
3739

38-
@create_attrs ~w(created_at github_issue_number markdown modified_at status title)a
39-
@spec create_changeset(Task.t, map, ProjectGithubRepo.t, User.t) :: Changeset.t
40+
@create_attrs ~w(created_at markdown modified_at status title)a
41+
@spec create_changeset(Task.t, map, GithubIssue.t, ProjectGithubRepo.t, User.t) :: Changeset.t
4042
defp create_changeset(
4143
%Task{} = task,
4244
%{} = issue_attrs,
45+
%GithubIssue{id: github_issue_id},
4346
%ProjectGithubRepo{project_id: project_id, github_repo_id: github_repo_id},
4447
%User{id: user_id}) do
4548

@@ -51,18 +54,20 @@ defmodule CodeCorps.GitHub.Event.Issues.ChangesetBuilder do
5154
|> MarkdownRendererService.render_markdown_to_html(:markdown, :body)
5255
|> Changeset.put_change(:created_from, "github")
5356
|> Changeset.put_change(:modified_from, "github")
57+
|> Changeset.put_change(:github_issue_id, github_issue_id)
5458
|> Changeset.put_change(:github_repo_id, github_repo_id)
5559
|> Changeset.put_change(:project_id, project_id)
5660
|> Changeset.put_change(:task_list_id, task_list_id)
5761
|> Changeset.put_change(:user_id, user_id)
5862
|> Changeset.validate_required([:project_id, :task_list_id, :title, :user_id])
63+
|> Changeset.assoc_constraint(:github_issue)
5964
|> Changeset.assoc_constraint(:github_repo)
6065
|> Changeset.assoc_constraint(:project)
6166
|> Changeset.assoc_constraint(:task_list)
6267
|> Changeset.assoc_constraint(:user)
6368
end
6469

65-
@update_attrs ~w(github_issue_number markdown modified_at status title)a
70+
@update_attrs ~w(markdown modified_at status title)a
6671
@spec update_changeset(Task.t, map) :: Changeset.t
6772
defp update_changeset(%Task{} = task, %{} = issue_attrs) do
6873
task
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
defmodule CodeCorps.GitHub.Event.Issues.IssueLinker do
2+
@moduledoc ~S"""
3+
In charge of finding a issue to link with a Task when processing an Issues
4+
webhook.
5+
6+
The only entry point is `create_or_update_issue/1`.
7+
"""
8+
9+
alias CodeCorps.{
10+
GithubIssue,
11+
GithubRepo,
12+
Repo
13+
}
14+
15+
alias CodeCorps.GitHub.Adapters.Issue, as: IssueAdapter
16+
17+
@typep linking_result :: {:ok, GithubIssue.t} |
18+
{:error, Ecto.Changeset.t}
19+
20+
@doc ~S"""
21+
Finds or creates a `GithubIssue` using the data in a GitHub Issue payload.
22+
23+
The process is as follows:
24+
25+
- Search for the issue in our database with the payload data.
26+
- If we return a single `GithubIssue`, then the `GithubIssue` should be
27+
updated.
28+
- If there are no matching `GithubIssue` records, then a `GithubIssue`
29+
should be created.
30+
"""
31+
@spec create_or_update_issue(GithubRepo.t, map) :: linking_result
32+
def create_or_update_issue(%GithubRepo{} = github_repo, %{"id" => github_issue_id} = attrs) do
33+
params = IssueAdapter.from_api(attrs)
34+
35+
case Repo.get_by(GithubIssue, github_id: github_issue_id) do
36+
nil -> create_issue(github_repo, params)
37+
%GithubIssue{} = issue -> update_issue(issue, params)
38+
end
39+
end
40+
41+
defp create_issue(%GithubRepo{id: github_repo_id}, params) do
42+
params = Map.put(params, :github_repo_id, github_repo_id)
43+
44+
%GithubIssue{}
45+
|> GithubIssue.create_changeset(params)
46+
|> Repo.insert
47+
end
48+
49+
defp update_issue(%GithubIssue{} = github_issue, params) do
50+
github_issue
51+
|> GithubIssue.update_changeset(params)
52+
|> Repo.update
53+
end
54+
end

lib/code_corps/github/event/issues/task_syncer.ex

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule CodeCorps.GitHub.Event.Issues.TaskSyncer do
22
alias CodeCorps.{
3+
GithubIssue,
34
GithubRepo,
45
GitHub.Event.Common.ResultAggregator,
56
GitHub.Event.Issues.ChangesetBuilder,
@@ -19,30 +20,31 @@ defmodule CodeCorps.GitHub.Event.Issues.TaskSyncer do
1920
`Project` associated to that `GithubRepo` via a `ProjectGithubRepo`, it
2021
creates or updates a `Task` associated to the specified `User`.
2122
"""
22-
@spec sync_all(GithubRepo.t, User.t, map) :: {:ok, list(Task.t)}
23-
def sync_all(%GithubRepo{project_github_repos: project_github_repos}, %User{} = user, %{} = payload) do
23+
@spec sync_all(GithubIssue.t, GithubRepo.t, User.t, map) :: {:ok, list(Task.t)}
24+
def sync_all(%GithubIssue{} = github_issue, %GithubRepo{project_github_repos: project_github_repos}, %User{} = user, %{} = payload) do
2425
project_github_repos
25-
|> Enum.map(&sync(&1, user, payload))
26+
|> Enum.map(&sync(github_issue, &1, user, payload))
2627
|> ResultAggregator.aggregate
2728
end
2829

29-
@spec sync(ProjectGithubRepo.t, User.t, map) :: {:ok, ProjectGithubRepo.t} | {:error, Changeset.t}
30-
defp sync(%ProjectGithubRepo{} = project_github_repo, %User{} = user, %{} = payload) do
30+
@spec sync(GithubIssue.t, ProjectGithubRepo.t, User.t, map) :: {:ok, ProjectGithubRepo.t} | {:error, Changeset.t}
31+
defp sync(%GithubIssue{} = github_issue, %ProjectGithubRepo{} = project_github_repo, %User{} = user, %{} = payload) do
3132
project_github_repo
32-
|> find_or_init_task(payload)
33-
|> ChangesetBuilder.build_changeset(payload, project_github_repo, user)
33+
|> find_or_init_task(github_issue)
34+
|> ChangesetBuilder.build_changeset(payload, github_issue, project_github_repo, user)
3435
|> commit()
3536
end
3637

37-
@spec find_or_init_task(ProjectGithubRepo.t, map) :: Task.t
38+
@spec find_or_init_task(ProjectGithubRepo.t, GithubIssue.t) :: Task.t
3839
defp find_or_init_task(
3940
%ProjectGithubRepo{project_id: project_id, github_repo_id: github_repo_id},
40-
%{"issue" => %{"number" => github_issue_number}}) do
41+
%GithubIssue{id: github_issue_id}
42+
) do
4143

4244
query_params = [
43-
github_issue_number: github_issue_number,
44-
project_id: project_id,
45-
github_repo_id: github_repo_id
45+
github_issue_id: github_issue_id,
46+
github_repo_id: github_repo_id,
47+
project_id: project_id
4648
]
4749

4850
case Task |> Repo.get_by(query_params) do

lib/code_corps/github/event/issues/user_linker.ex

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule CodeCorps.GitHub.Event.Issues.UserLinker do
1010

1111
alias CodeCorps.{
1212
Accounts,
13-
GithubRepo,
13+
GithubIssue,
1414
Repo,
1515
Task,
1616
User
@@ -21,8 +21,8 @@ defmodule CodeCorps.GitHub.Event.Issues.UserLinker do
2121
{:error, :multiple_users}
2222

2323
@doc ~S"""
24-
Finds or creates a user using information contained in a GitHub Issue
25-
webhook payload.
24+
Finds or creates a user using information in a `GithubIssue` record and a
25+
Github Issue payload.
2626
2727
The process is as follows:
2828
- Find all affected tasks and extract their user data.
@@ -32,28 +32,22 @@ defmodule CodeCorps.GitHub.Event.Issues.UserLinker do
3232
someone who does not have a matching GitHub-connected Code Corps account.
3333
We create a placeholder user account until that GitHub user is claimed by
3434
a Code Corps user.
35-
created.
3635
- If there are multiple matching users, this is an unexpected scenario and
3736
should error out.
3837
"""
39-
@spec find_or_create_user(map) :: linking_result
40-
def find_or_create_user(%{"issue" => %{"user" => user_attrs}} = attrs) do
41-
attrs
38+
@spec find_or_create_user(GithubIssue.t, map) :: linking_result
39+
def find_or_create_user(%GithubIssue{} = issue, %{"issue" => %{"user" => user_attrs}}) do
40+
issue
4241
|> match_users
4342
|> marshall_response(user_attrs)
4443
end
4544

46-
@spec match_users(map) :: list(User.t)
47-
defp match_users(
48-
%{
49-
"issue" => %{"number" => github_issue_number},
50-
"repository" =>%{"id" => github_repo_id}
51-
}) do
45+
@spec match_users(GithubIssue.t) :: list(User.t)
46+
defp match_users(%GithubIssue{id: github_issue_id}) do
5247

5348
query = from u in User,
5449
distinct: u.id,
55-
join: t in Task, on: u.id == t.user_id, where: t.github_issue_number == ^github_issue_number,
56-
join: r in GithubRepo, on: r.id == t.github_repo_id, where: r.github_id == ^github_repo_id
50+
join: t in Task, on: u.id == t.user_id, where: t.github_issue_id == ^github_issue_id
5751

5852
query |> Repo.all
5953
end

0 commit comments

Comments
 (0)