Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions lib/code_corps/github/adapters/issue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule CodeCorps.GitHub.Adapters.Issue do
Task
}

@issue_mapping [
@github_payload_to_github_issue_mapping [
{:body, ["body"]},
{:closed_at, ["closed_at"]},
{:comments_url, ["comments_url"]},
Expand All @@ -34,35 +34,34 @@ defmodule CodeCorps.GitHub.Adapters.Issue do
"""
@spec to_issue(map) :: map
def to_issue(%{} = payload) do
payload |> MapTransformer.transform(@issue_mapping)
payload |> MapTransformer.transform(@github_payload_to_github_issue_mapping)
end

@task_mapping [
@github_payload_to_task_mapping [
{:created_at, ["created_at"]},
{:markdown, ["body"]},
{:modified_at, ["updated_at"]},
{:status, ["state"]},
{:title, ["title"]}
]

@github_issue_to_task_mapping [
{:created_at, [:github_created_at]},
{:markdown, [:body]},
{:modified_at, [:github_updated_at]},
{:status, [:state]},
{:title, [:title]}
]

@doc ~S"""
Converts a GitHub Issue payload into a set of attributes used to create or
update a `Task` record.
"""
@spec to_task(map) :: map
def to_task(%{} = payload) do
payload |> MapTransformer.transform(@task_mapping)
end

@doc ~S"""
Converts a `GithubIssue` record into attributes with the same keys as the
GitHub API's Issue
"""
@spec to_issue_attrs(GithubIssue.t) :: map
def to_issue_attrs(%GithubIssue{} = github_issue) do
@spec to_task(GithubIssue.t) :: map
def to_task(%GithubIssue{} = github_issue) do
github_issue
|> Map.from_struct
|> MapTransformer.transform_inverse(@issue_mapping)
|> MapTransformer.transform(@github_issue_to_task_mapping)
end

@autogenerated_github_keys ~w(closed_at comments_url created_at events_url html_url id labels_url number updated_at url)
Expand All @@ -75,14 +74,14 @@ defmodule CodeCorps.GitHub.Adapters.Issue do
def to_api(%GithubIssue{} = github_issue) do
github_issue
|> Map.from_struct
|> MapTransformer.transform_inverse(@issue_mapping)
|> MapTransformer.transform_inverse(@github_payload_to_github_issue_mapping)
|> Map.drop(@autogenerated_github_keys)
|> BodyDecorator.add_code_corps_header(github_issue)
end
def to_api(%Task{} = task) do
task
|> Map.from_struct
|> MapTransformer.transform_inverse(@task_mapping)
|> MapTransformer.transform_inverse(@github_payload_to_task_mapping)
|> Map.drop(@autogenerated_github_keys)
|> BodyDecorator.add_code_corps_header(task)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/code_corps/github/sync/issue/issue.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ defmodule CodeCorps.GitHub.Sync.Issue do
Multi.new
|> Multi.run(:github_issue, fn _ -> link_issue({github_repo, github_pull_request}, payload) end)
|> Multi.run(:issue_user, fn %{github_issue: github_issue} -> UserRecordLinker.link_to(github_issue, payload) end)
|> Multi.run(:tasks, fn %{github_issue: github_issue, issue_user: user} -> github_issue |> IssueTaskSyncer.sync_all(user, payload) end)
|> Multi.run(:tasks, fn %{github_issue: github_issue, issue_user: user} -> github_issue |> IssueTaskSyncer.sync_all(user) end)
end

@spec link_issue({GithubRepo.t, GithubPullRequest.t}, map) :: {:ok, GithubIssue.t} | {:error, Ecto.Changeset.t}
Expand Down
91 changes: 60 additions & 31 deletions lib/code_corps/github/sync/issue/task/changeset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,77 +23,106 @@ defmodule CodeCorps.GitHub.Sync.Issue.Task.Changeset do

The changeset can be used to create or update a `Task`
"""
@spec build_changeset(Task.t, map, GithubIssue.t, ProjectGithubRepo.t, User.t) :: Changeset.t
def build_changeset(
%Task{id: task_id} = task,
%{} = issue_attrs,
%GithubIssue{} = github_issue,
%ProjectGithubRepo{} = project_github_repo,
%User{} = user) do

case is_nil(task_id) do
true -> create_changeset(task, issue_attrs, github_issue, project_github_repo, user)
false -> update_changeset(task, issue_attrs)
end
end

@spec build_changeset(Task.t, GithubIssue.t, ProjectGithubRepo.t, User.t) :: Changeset.t
def build_changeset(
%Task{id: task_id} = task,
%GithubIssue{} = github_issue,
%ProjectGithubRepo{} = project_github_repo,
%User{} = user) do

issue_attrs = github_issue |> IssueAdapter.to_issue_attrs()
case is_nil(task_id) do
true -> create_changeset(task, issue_attrs, github_issue, project_github_repo, user)
false -> update_changeset(task, issue_attrs)
true -> create_changeset(task, github_issue, project_github_repo, user)
false -> update_changeset(task, github_issue, project_github_repo)
end
end

@create_attrs ~w(created_at markdown modified_at status title)a
@spec create_changeset(Task.t, map, GithubIssue.t, ProjectGithubRepo.t, User.t) :: Changeset.t
@spec create_changeset(Task.t, GithubIssue.t, ProjectGithubRepo.t, User.t) :: Changeset.t
defp create_changeset(
%Task{} = task,
%{} = issue_attrs,
%GithubIssue{id: github_issue_id},
%GithubIssue{id: github_issue_id} = github_issue,
%ProjectGithubRepo{project_id: project_id, github_repo_id: github_repo_id},
%User{id: user_id}) do

%TaskList{id: task_list_id} =
TaskList |> Repo.get_by(project_id: project_id, inbox: true)

task
|> Changeset.cast(IssueAdapter.to_task(issue_attrs), @create_attrs)
|> Changeset.cast(github_issue |> IssueAdapter.to_task, @create_attrs)
|> MarkdownRendererService.render_markdown_to_html(:markdown, :body)
|> Changeset.put_change(:created_from, "github")
|> Changeset.put_change(:modified_from, "github")
|> Changeset.put_change(:github_issue_id, github_issue_id)
|> Changeset.put_change(:github_repo_id, github_repo_id)
|> Changeset.put_change(:project_id, project_id)
|> Changeset.put_change(:task_list_id, task_list_id)
|> assign_task_list(github_issue, project_id)
|> Changeset.put_change(:user_id, user_id)
|> Changeset.validate_required([:project_id, :task_list_id, :title, :user_id])
|> Changeset.assoc_constraint(:github_issue)
|> Changeset.assoc_constraint(:github_repo)
|> Changeset.assoc_constraint(:project)
|> Changeset.assoc_constraint(:task_list)
|> Changeset.assoc_constraint(:user)
|> Task.order_task()
|> maybe_archive()
|> Task.handle_archived()
end

@update_attrs ~w(markdown modified_at status title)a
@spec update_changeset(Task.t, map) :: Changeset.t
defp update_changeset(%Task{} = task, %{} = issue_attrs) do
@spec update_changeset(Task.t, GithubIssue.t, ProjectGithubRepo.t) :: Changeset.t
defp update_changeset(
%Task{} = task,
%GithubIssue{} = github_issue,
%ProjectGithubRepo{project_id: project_id}) do
task
|> Changeset.cast(IssueAdapter.to_task(issue_attrs), @update_attrs)
|> Changeset.cast(github_issue |> IssueAdapter.to_task, @update_attrs)
|> MarkdownRendererService.render_markdown_to_html(:markdown, :body)
|> Changeset.put_change(:modified_from, "github")
|> TimeValidator.validate_time_not_before(:modified_at)
|> assign_task_list(github_issue, project_id)
|> Changeset.validate_required([:project_id, :title, :user_id])
|> Changeset.assoc_constraint(:github_repo)
|> Changeset.assoc_constraint(:project)
|> Changeset.assoc_constraint(:user)
|> Task.order_task()
|> maybe_archive()
|> Task.handle_archived()
end

@spec assign_task_list(Changeset.t, GithubIssue.t, integer) :: Changeset.t
defp assign_task_list(
%Changeset{} = changeset, %GithubIssue{} = github_issue, project_id)
do
%TaskList{id: task_list_id} =
github_issue
|> get_task_list_type()
|> get_task_list(project_id)

changeset
|> Changeset.put_change(:task_list_id, task_list_id)
|> Changeset.assoc_constraint(:task_list)
end

@spec get_task_list_type(GithubIssue.t) :: atom
defp get_task_list_type(%GithubIssue{state: "closed"}), do: :done
defp get_task_list_type(%GithubIssue{state: "open", github_pull_request_id: pr_id})
when not is_nil(pr_id), do: :pull_requests
defp get_task_list_type(%GithubIssue{state: "open"}), do: :inbox

@spec get_task_list(atom, integer) :: TaskList.t
defp get_task_list(:done, project_id) do
TaskList |> Repo.get_by(project_id: project_id, done: true)
end
defp get_task_list(:inbox, project_id) do
TaskList |> Repo.get_by(project_id: project_id, inbox: true)
end
defp get_task_list(:pull_requests, project_id) do
TaskList |> Repo.get_by(project_id: project_id, pull_requests: true)
end

@spec maybe_archive(Changeset.t) :: Changeset.t
defp maybe_archive(%Changeset{} = changeset) do
modified_at = changeset |> Changeset.get_field(:modified_at)
status = changeset |> Changeset.get_field(:status)

case {status, Timex.now |> Timex.diff(modified_at, :days)} do
{"closed", days_since_modified} when days_since_modified > 30 ->
changeset |> Changeset.put_change(:archived, true)
_ -> changeset
end
end
end
29 changes: 10 additions & 19 deletions lib/code_corps/github/sync/issue/task/task.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,18 @@ defmodule CodeCorps.GitHub.Sync.Issue.Task do
{:error, {list(Task.t), list(Changeset.t)}}

@doc """
When provided a `CodeCorps.GithubIssue`, a `CodeCorps.User` and a GitHub API
payload, for each `CodeCorps.Project` associated to that
`CodeCorps.GithubRepo` via a `CodeCorps.ProjectGithubRepo`, it
creates or updates a `CodeCorps.Task`.
When provided a `GithubIssue` and a `User`, for each `Project` associated to
that `GithubRepo` via a `ProjectGithubRepo`, it creates or updates a `Task`.
"""
@spec sync_all(GithubIssue.t, User.t, map) :: {:ok, list(Task.t)}
def sync_all(%GithubIssue{} = github_issue, %User{} = user, %{} = payload) do
@spec sync_all(GithubIssue.t, User.t) :: {:ok, list(Task.t)}
def sync_all(%GithubIssue{} = github_issue, %User{} = user) do

%GithubIssue{
github_repo: %GithubRepo{project_github_repos: project_github_repos}
} = github_issue |> Repo.preload(github_repo: :project_github_repos)

project_github_repos
|> Enum.map(&sync(github_issue, &1, user, payload))
|> Enum.map(&sync(github_issue, &1, user))
|> ResultAggregator.aggregate
end

Expand All @@ -45,17 +43,18 @@ defmodule CodeCorps.GitHub.Sync.Issue.Task do
"""
def sync_project_github_repo(%ProjectGithubRepo{github_repo: %GithubRepo{} = _} = project_github_repo) do
%ProjectGithubRepo{
github_repo: %GithubRepo{
github_issues: github_issues
}
github_repo: %GithubRepo{github_issues: github_issues}
} = project_github_repo |> Repo.preload([:project, github_repo: [github_issues: [github_user: [:user]]]])

github_issues
|> Enum.map(&find_or_create_task(&1, project_github_repo))
|> ResultAggregator.aggregate
end

defp find_or_create_task(%GithubIssue{github_user: %GithubUser{user: %User{} = user}} = github_issue, %ProjectGithubRepo{} = project_github_repo) do
defp find_or_create_task(
%GithubIssue{github_user: %GithubUser{user: %User{} = user}} = github_issue,
%ProjectGithubRepo{} = project_github_repo) do

sync(github_issue, project_github_repo, user)
end

Expand All @@ -67,14 +66,6 @@ defmodule CodeCorps.GitHub.Sync.Issue.Task do
|> Repo.insert_or_update()
end

@spec sync(GithubIssue.t, ProjectGithubRepo.t, User.t, map) :: {:ok, ProjectGithubRepo.t} | {:error, Changeset.t}
defp sync(%GithubIssue{} = github_issue, %ProjectGithubRepo{} = project_github_repo, %User{} = user, %{} = payload) do
project_github_repo
|> find_or_init_task(github_issue)
|> TaskChangeset.build_changeset(payload, github_issue, project_github_repo, user)
|> Repo.insert_or_update()
end

@spec find_or_init_task(ProjectGithubRepo.t, GithubIssue.t) :: Task.t
defp find_or_init_task(
%ProjectGithubRepo{project_id: project_id},
Expand Down
20 changes: 20 additions & 0 deletions priv/repo/migrations/20171106200036_archive_outdated_tasks.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule CodeCorps.Repo.Migrations.ArchiveOutdatedTasks do
use Ecto.Migration

import Ecto.Query

alias CodeCorps.Repo

def up do
from(
t in "tasks",
where: t.status == "closed",
where: date_add(t.modified_at, 30, "day") > ^Date.utc_today,
update: [set: [archived: true, task_list_id: nil]]
) |> Repo.update_all([])
end

def down do
# no-op
end
end
2 changes: 1 addition & 1 deletion priv/repo/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3867,5 +3867,5 @@ ALTER TABLE ONLY users
-- PostgreSQL database dump complete
--

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);
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);

17 changes: 9 additions & 8 deletions test/lib/code_corps/github/adapters/issue_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule CodeCorps.GitHub.Adapters.IssueTest do
use ExUnit.Case, async: true

import CodeCorps.GitHub.TestHelpers
import CodeCorps.Factories

alias CodeCorps.{GitHub.Adapters, GithubIssue, Task}

Expand Down Expand Up @@ -31,15 +32,15 @@ defmodule CodeCorps.GitHub.Adapters.IssueTest do
end

describe "to_task/1" do
test "maps api payload correctly" do
%{"issue" => payload} = load_event_fixture("issues_opened")
test "maps github issue correctly" do
github_issue = build(:github_issue)

assert Adapters.Issue.to_task(payload) == %{
created_at: payload["created_at"],
markdown: payload["body"],
modified_at: payload["updated_at"],
status: payload["state"],
title: payload["title"]
assert github_issue |> Adapters.Issue.to_task == %{
created_at: github_issue.github_created_at,
markdown: github_issue.body,
modified_at: github_issue.github_updated_at,
status: github_issue.state,
title: github_issue.title
}
end
end
Expand Down
2 changes: 2 additions & 0 deletions test/lib/code_corps/github/event/issues/issues_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ defmodule CodeCorps.GitHub.Event.IssuesTest do

github_repo = insert(:github_repo, github_id: repo_github_id)
%{project: project} = insert(:project_github_repo, github_repo: github_repo)
insert(:task_list, project: project, done: true)
insert(:task_list, project: project, inbox: true)
insert(:task_list, project: project, pull_requests: true)

{:ok, tasks} = Issues.handle(@payload)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ defmodule CodeCorps.GitHub.Event.PullRequestTest do

github_repo = insert(:github_repo, github_id: repo_github_id)
%{project: project} = insert(:project_github_repo, github_repo: github_repo)
insert(:task_list, project: project, inbox: true)
insert(:task_list, project: project, pull_requests: true)

{:ok, %{github_pull_request: github_pull_request}} = PullRequest.handle(@payload)

Expand Down
2 changes: 1 addition & 1 deletion test/lib/code_corps/github/sync/issue/issue_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ defmodule CodeCorps.GitHub.Sync.IssueTest do

project_ids |> Enum.each(fn project_id ->
project = Project |> Repo.get_by(id: project_id)
insert(:task_list, project: project, inbox: true)
insert(:task_list, project: project, pull_requests: true)
end)

%{id: existing_task_id} =
Expand Down
Loading