Skip to content

Commit ff7ce09

Browse files
committed
feat: Workspace.CommandResponse
1 parent 6fb95e0 commit ff7ce09

File tree

6 files changed

+160
-24
lines changed

6 files changed

+160
-24
lines changed

lib/algora/bounties/bounties.ex

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ defmodule Algora.Bounties do
7373
amount: Money.t(),
7474
ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()}
7575
},
76-
opts :: [installation_id: integer()]
76+
opts :: [installation_id: integer(), command_id: integer(), command_source: :ticket | :comment]
7777
) ::
7878
{:ok, Bounty.t()} | {:error, atom()}
7979
def create_bounty(
@@ -86,6 +86,7 @@ defmodule Algora.Bounties do
8686
opts \\ []
8787
) do
8888
installation_id = opts[:installation_id]
89+
command_id = opts[:command_id]
8990

9091
token_res =
9192
if installation_id,
@@ -97,7 +98,11 @@ defmodule Algora.Bounties do
9798
{:ok, ticket} <- Workspace.ensure_ticket(token, repo_owner, repo_name, number),
9899
{:ok, bounty} <- do_create_bounty(%{creator: creator, owner: owner, amount: amount, ticket: ticket}),
99100
{:ok, _job} <-
100-
notify_bounty(%{owner: owner, bounty: bounty, ticket_ref: ticket_ref}, installation_id: installation_id) do
101+
notify_bounty(%{owner: owner, bounty: bounty, ticket_ref: ticket_ref},
102+
installation_id: installation_id,
103+
command_id: command_id,
104+
command_source: opts[:command_source]
105+
) do
101106
broadcast()
102107
{:ok, bounty}
103108
else
@@ -112,15 +117,17 @@ defmodule Algora.Bounties do
112117
bounty: Bounty.t(),
113118
ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()}
114119
},
115-
opts :: [installation_id: integer()]
120+
opts :: [installation_id: integer(), command_id: integer(), command_source: :ticket | :comment]
116121
) ::
117122
{:ok, Oban.Job.t()} | {:error, atom()}
118123
def notify_bounty(%{owner: owner, bounty: bounty, ticket_ref: ticket_ref}, opts \\ []) do
119124
%{
120125
owner_login: owner.provider_login,
121126
amount: Money.to_string!(bounty.amount, no_fraction_if_integer: true),
122127
ticket_ref: %{owner: ticket_ref.owner, repo: ticket_ref.repo, number: ticket_ref.number},
123-
installation_id: opts[:installation_id]
128+
installation_id: opts[:installation_id],
129+
command_id: opts[:command_id],
130+
command_source: opts[:command_source]
124131
}
125132
|> Jobs.NotifyBounty.new()
126133
|> Oban.insert()
Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
defmodule Algora.Bounties.Jobs.NotifyBounty do
22
@moduledoc false
3-
use Oban.Worker, queue: :notify_bounty
3+
use Oban.Worker,
4+
queue: :notify_bounty,
5+
max_attempts: 1
46

57
alias Algora.Accounts
68
alias Algora.Accounts.User
79
alias Algora.Github
10+
alias Algora.Repo
11+
alias Algora.Util
812
alias Algora.Workspace
13+
alias Algora.Workspace.CommandResponse
914

1015
require Logger
1116

1217
@impl Oban.Worker
1318
def perform(%Oban.Job{
14-
args: %{"owner_login" => owner_login, "amount" => amount, "ticket_ref" => ticket_ref, "installation_id" => nil}
19+
args: %{
20+
"owner_login" => owner_login,
21+
"amount" => amount,
22+
"ticket_ref" => ticket_ref,
23+
"installation_id" => nil,
24+
"command_id" => command_id,
25+
"command_source" => command_source
26+
}
1527
}) do
1628
body = """
1729
💎 **#{owner_login}** is offering a **#{amount}** bounty for this issue
@@ -20,13 +32,28 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
2032
"""
2133

2234
if Github.pat_enabled() do
23-
Github.create_issue_comment(
24-
Github.pat(),
25-
ticket_ref["owner"],
26-
ticket_ref["repo"],
27-
ticket_ref["number"],
28-
body
29-
)
35+
with {:ok, response} <-
36+
Github.create_issue_comment(
37+
Github.pat(),
38+
ticket_ref["owner"],
39+
ticket_ref["repo"],
40+
ticket_ref["number"],
41+
body
42+
),
43+
{:ok, ticket} <-
44+
Workspace.ensure_ticket(Github.pat(), ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"]) do
45+
%CommandResponse{}
46+
|> CommandResponse.changeset(%{
47+
provider: "github",
48+
provider_meta: Util.normalize_struct(response),
49+
provider_command_id: to_string(command_id),
50+
provider_response_id: to_string(response["id"]),
51+
command_source: command_source,
52+
command_type: :bounty,
53+
ticket_id: ticket.id
54+
})
55+
|> Repo.insert()
56+
end
3057
else
3158
Logger.info("""
3259
Github.create_issue_comment(Github.pat(), "#{ticket_ref["owner"]}", "#{ticket_ref["repo"]}", #{ticket_ref["number"]},
@@ -38,12 +65,22 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
3865
end
3966

4067
@impl Oban.Worker
41-
def perform(%Oban.Job{args: %{"amount" => amount, "ticket_ref" => ticket_ref, "installation_id" => installation_id}}) do
68+
def perform(%Oban.Job{
69+
args: %{
70+
"amount" => amount,
71+
"ticket_ref" => ticket_ref,
72+
"installation_id" => installation_id,
73+
"command_id" => command_id,
74+
"command_source" => command_source
75+
}
76+
}) do
4277
with {:ok, token} <- Github.get_installation_token(installation_id),
4378
{:ok, installation} <-
4479
Workspace.fetch_installation_by(provider: "github", provider_id: to_string(installation_id)),
4580
{:ok, owner} <- Accounts.fetch_user_by(id: installation.connected_user_id),
46-
{:ok, _} <- Github.add_labels(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"], ["💎 Bounty"]) do
81+
{:ok, _} <-
82+
Github.add_labels(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"], ["💎 Bounty"]),
83+
{:ok, ticket} <- Workspace.ensure_ticket(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"]) do
4784
body = """
4885
## 💎 #{amount} bounty [• #{owner.name}](#{User.url(owner)})
4986
### Steps to solve:
@@ -54,13 +91,26 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
5491
Thank you for contributing to #{ticket_ref["owner"]}/#{ticket_ref["repo"]}!
5592
"""
5693

57-
Github.create_issue_comment(
58-
token,
59-
ticket_ref["owner"],
60-
ticket_ref["repo"],
61-
ticket_ref["number"],
62-
body
63-
)
94+
with {:ok, response} <-
95+
Github.create_issue_comment(
96+
token,
97+
ticket_ref["owner"],
98+
ticket_ref["repo"],
99+
ticket_ref["number"],
100+
body
101+
) do
102+
%CommandResponse{}
103+
|> CommandResponse.changeset(%{
104+
provider: "github",
105+
provider_meta: Util.normalize_struct(response),
106+
provider_command_id: to_string(command_id),
107+
provider_response_id: to_string(response["id"]),
108+
command_source: command_source,
109+
command_type: :bounty,
110+
ticket_id: ticket.id
111+
})
112+
|> Repo.insert()
113+
end
64114
end
65115
end
66116
end

lib/algora/integrations/github/poller/comment_consumer.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ defmodule Algora.Github.Poller.CommentConsumer do
3838
creator: user,
3939
owner: user,
4040
amount: args[:amount],
41-
ticket_ref: %{owner: ticket_ref[:owner], repo: ticket_ref[:repo], number: ticket_ref[:number]}
41+
ticket_ref: %{owner: ticket_ref[:owner], repo: ticket_ref[:repo], number: ticket_ref[:number]},
42+
command_id: comment["id"]
4243
})
4344

4445
{:error, _reason} = error ->
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
defmodule Algora.Workspace.CommandResponse do
2+
@moduledoc """
3+
Schema for tracking command comments and their corresponding bot responses.
4+
This allows updating existing bot responses instead of creating new ones.
5+
"""
6+
use Algora.Schema
7+
8+
typed_schema "command_responses" do
9+
field :provider, :string, null: false
10+
field :provider_meta, :map, null: false
11+
field :provider_command_id, :string
12+
field :provider_response_id, :string, null: false
13+
field :command_source, Ecto.Enum, values: [:ticket, :comment], null: false
14+
field :command_type, Ecto.Enum, values: [:bounty, :attempt, :claim], null: false
15+
16+
belongs_to :ticket, Algora.Workspace.Ticket, null: false
17+
18+
timestamps()
19+
end
20+
21+
def changeset(command_response, attrs) do
22+
command_response
23+
|> cast(attrs, [
24+
:provider,
25+
:provider_meta,
26+
:provider_command_id,
27+
:provider_response_id,
28+
:command_source,
29+
:command_type,
30+
:ticket_id
31+
])
32+
|> validate_required([
33+
:provider,
34+
:provider_meta,
35+
:provider_response_id,
36+
:command_source,
37+
:command_type,
38+
:ticket_id
39+
])
40+
|> generate_id()
41+
|> foreign_key_constraint(:ticket_id)
42+
|> unique_constraint([:provider, :provider_command_id])
43+
end
44+
end

lib/algora_web/controllers/webhooks/github_controller.ex

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,23 @@ defmodule AlgoraWeb.Webhooks.GithubController do
5252

5353
defp execute_command(event_action, {:bounty, args}, author, params)
5454
when event_action in ["issues.opened", "issues.edited", "issue_comment.created", "issue_comment.edited"] do
55+
[event, _action] = String.split(event_action, ".")
5556
amount = args[:amount]
5657
repo = params["repository"]
5758
issue = params["issue"]
5859
installation_id = params["installation"]["id"]
5960

61+
{command_source, command_id} =
62+
case event do
63+
"issue_comment" ->
64+
{:comment, params["comment"]["id"]}
65+
66+
_ ->
67+
{:ticket, issue["id"]}
68+
end
69+
70+
dbg({command_source, command_id})
71+
6072
# TODO: community bounties?
6173
with {:ok, "admin"} <- get_permissions(author, params),
6274
{:ok, token} <- Github.get_installation_token(installation_id),
@@ -71,7 +83,9 @@ defmodule AlgoraWeb.Webhooks.GithubController do
7183
amount: amount,
7284
ticket_ref: %{owner: repo["owner"]["login"], repo: repo["name"], number: issue["number"]}
7385
},
74-
installation_id: installation_id
86+
installation_id: installation_id,
87+
command_id: command_id,
88+
command_source: command_source
7589
)
7690
else
7791
{:ok, _permission} ->
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
defmodule Algora.Repo.Migrations.CreateCommandResponses do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:command_responses) do
6+
add :provider, :string, null: false
7+
add :provider_meta, :map, null: false
8+
add :provider_command_id, :string
9+
add :provider_response_id, :string, null: false
10+
add :command_source, :string, null: false
11+
add :command_type, :string, null: false
12+
add :ticket_id, references(:tickets), null: false
13+
14+
timestamps()
15+
end
16+
17+
create unique_index(:command_responses, [:provider, :provider_command_id, :command_source])
18+
create index(:command_responses, [:ticket_id])
19+
end
20+
end

0 commit comments

Comments
 (0)