Skip to content

Commit ff2eec3

Browse files
authored
refactor: create command response helpers (#45)
1 parent d6695b3 commit ff2eec3

File tree

3 files changed

+170
-84
lines changed

3 files changed

+170
-84
lines changed

lib/algora/bounties/bounties.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,27 @@ defmodule Algora.Bounties do
136136
end)
137137
end
138138

139+
@spec get_response_body(
140+
bounties :: list(Bounty.t()),
141+
ticket_ref :: %{owner: String.t(), repo: String.t(), number: integer()}
142+
) :: String.t()
143+
def get_response_body(bounties, ticket_ref) do
144+
header =
145+
Enum.map_join(bounties, "\n", fn bounty ->
146+
"## 💎 #{bounty.amount} bounty [• #{bounty.owner.name}](#{User.url(bounty.owner)})"
147+
end)
148+
149+
"""
150+
#{header}
151+
### Steps to solve:
152+
1. **Start working**: Comment `/attempt ##{ticket_ref["number"]}` with your implementation plan
153+
2. **Submit work**: Create a pull request including `/claim ##{ticket_ref["number"]}` in the PR body to claim the bounty
154+
3. **Receive payment**: 100% of the bounty is received 2-5 days post-reward. [Make sure you are eligible for payouts](https://docs.algora.io/bounties/payments#supported-countries-regions)
155+
156+
Thank you for contributing to #{ticket_ref["owner"]}/#{ticket_ref["repo"]}!
157+
"""
158+
end
159+
139160
@spec notify_bounty(
140161
%{
141162
owner: User.t(),

lib/algora/bounties/jobs/notify_bounty.ex

Lines changed: 16 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
44
queue: :notify_bounty,
55
max_attempts: 1
66

7-
alias Algora.Accounts.User
87
alias Algora.Bounties
98
alias Algora.Github
10-
alias Algora.Repo
11-
alias Algora.Util
129
alias Algora.Workspace
13-
alias Algora.Workspace.CommandResponse
1410

1511
require Logger
1612

@@ -32,7 +28,7 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
3228
"""
3329

3430
if Github.pat_enabled() do
35-
with {:ok, response} <-
31+
with {:ok, comment} <-
3632
Github.create_issue_comment(
3733
Github.pat(),
3834
ticket_ref["owner"],
@@ -43,7 +39,12 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
4339
{:ok, ticket} <-
4440
Workspace.ensure_ticket(Github.pat(), ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"]) do
4541
# TODO: update existing command response if it exists
46-
create_command_response(response, command_source, command_id, ticket.id)
42+
Workspace.create_command_response(%{
43+
comment: comment,
44+
command_source: command_source,
45+
command_id: command_id,
46+
ticket_id: ticket.id
47+
})
4748
end
4849
else
4950
Logger.info("""
@@ -69,84 +70,15 @@ defmodule Algora.Bounties.Jobs.NotifyBounty do
6970
{:ok, ticket} <- Workspace.ensure_ticket(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"]),
7071
bounties when bounties != [] <- Bounties.list_bounties(ticket_id: ticket.id),
7172
{:ok, _} <- Github.add_labels(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"], ["💎 Bounty"]) do
72-
header =
73-
Enum.map_join(bounties, "\n", fn bounty ->
74-
"## 💎 #{bounty.amount} bounty [• #{bounty.owner.name}](#{User.url(bounty.owner)})"
75-
end)
76-
77-
body = """
78-
#{header}
79-
### Steps to solve:
80-
1. **Start working**: Comment `/attempt ##{ticket_ref["number"]}` with your implementation plan
81-
2. **Submit work**: Create a pull request including `/claim ##{ticket_ref["number"]}` in the PR body to claim the bounty
82-
3. **Receive payment**: 100% of the bounty is received 2-5 days post-reward. [Make sure you are eligible for payouts](https://docs.algora.io/bounties/payments#supported-countries-regions)
83-
84-
Thank you for contributing to #{ticket_ref["owner"]}/#{ticket_ref["repo"]}!
85-
"""
86-
87-
ensure_command_response(token, ticket_ref, command_id, command_source, ticket, body)
88-
end
89-
end
90-
91-
defp ensure_command_response(token, ticket_ref, command_id, command_source, ticket, body) do
92-
case Workspace.fetch_command_response(ticket.id, :bounty) do
93-
{:ok, response} ->
94-
case Github.update_issue_comment(
95-
token,
96-
ticket_ref["owner"],
97-
ticket_ref["repo"],
98-
response.provider_response_id,
99-
body
100-
) do
101-
{:ok, comment} ->
102-
try_update_command_response(response, comment)
103-
104-
{:error, "404 Not Found"} ->
105-
with {:ok, _} <- Workspace.delete_command_response(response.id) do
106-
post_response(token, ticket_ref, command_id, command_source, ticket, body)
107-
end
108-
109-
{:error, reason} ->
110-
Logger.error("Failed to update command response #{response.id}: #{inspect(reason)}")
111-
{:error, reason}
112-
end
113-
114-
{:error, _reason} ->
115-
post_response(token, ticket_ref, command_id, command_source, ticket, body)
116-
end
117-
end
118-
119-
defp post_response(token, ticket_ref, command_id, command_source, ticket, body) do
120-
with {:ok, comment} <-
121-
Github.create_issue_comment(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"], body) do
122-
create_command_response(comment, command_source, command_id, ticket.id)
123-
end
124-
end
125-
126-
defp create_command_response(comment, command_source, command_id, ticket_id) do
127-
%CommandResponse{}
128-
|> CommandResponse.changeset(%{
129-
provider: "github",
130-
provider_meta: Util.normalize_struct(comment),
131-
provider_command_id: to_string(command_id),
132-
provider_response_id: to_string(comment["id"]),
133-
command_source: command_source,
134-
command_type: :bounty,
135-
ticket_id: ticket_id
136-
})
137-
|> Repo.insert()
138-
end
139-
140-
defp try_update_command_response(command_response, body) do
141-
case command_response
142-
|> CommandResponse.changeset(%{provider_meta: Util.normalize_struct(body)})
143-
|> Repo.update() do
144-
{:ok, command_response} ->
145-
{:ok, command_response}
146-
147-
{:error, reason} ->
148-
Logger.error("Failed to update command response #{command_response.id}: #{inspect(reason)}")
149-
{:ok, command_response}
73+
Workspace.ensure_command_response(%{
74+
token: token,
75+
ticket_ref: ticket_ref,
76+
command_id: command_id,
77+
command_type: :bounty,
78+
command_source: command_source,
79+
ticket: ticket,
80+
body: Bounties.get_response_body(bounties, ticket_ref)
81+
})
15082
end
15183
end
15284
end

lib/algora/workspace/workspace.ex

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Algora.Workspace do
55
alias Algora.Accounts.User
66
alias Algora.Github
77
alias Algora.Repo
8+
alias Algora.Util
89
alias Algora.Workspace.CommandResponse
910
alias Algora.Workspace.Installation
1011
alias Algora.Workspace.Jobs
@@ -164,4 +165,136 @@ defmodule Algora.Workspace do
164165
end
165166

166167
def delete_command_response(id), do: Repo.delete(Repo.get(CommandResponse, id))
168+
169+
@spec ensure_command_response(%{
170+
token: String.t(),
171+
ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()},
172+
command_id: integer(),
173+
command_type: :bounty | :attempt | :claim,
174+
command_source: :ticket | :comment,
175+
ticket: Ticket.t(),
176+
body: String.t()
177+
}) :: {:ok, CommandResponse.t()} | {:error, any()}
178+
def ensure_command_response(%{
179+
token: token,
180+
ticket_ref: ticket_ref,
181+
command_id: command_id,
182+
command_type: command_type,
183+
command_source: command_source,
184+
ticket: ticket,
185+
body: body
186+
}) do
187+
case refresh_command_response(%{
188+
token: token,
189+
ticket_ref: ticket_ref,
190+
ticket: ticket,
191+
body: body,
192+
command_type: command_type
193+
}) do
194+
{:ok, response} ->
195+
{:ok, response}
196+
197+
{:error, :command_response_not_found} ->
198+
post_response(token, ticket_ref, command_id, command_source, ticket, body)
199+
200+
{:error, {:comment_not_found, response_id}} ->
201+
with {:ok, _} <- delete_command_response(response_id) do
202+
post_response(token, ticket_ref, command_id, command_source, ticket, body)
203+
end
204+
205+
{:error, reason} ->
206+
{:error, reason}
207+
end
208+
end
209+
210+
@spec refresh_command_response(%{
211+
token: String.t(),
212+
command_type: :bounty | :attempt | :claim,
213+
ticket_ref: %{owner: String.t(), repo: String.t(), number: integer()},
214+
ticket: Ticket.t(),
215+
body: String.t()
216+
}) :: {:ok, CommandResponse.t()} | {:error, any()}
217+
defp refresh_command_response(%{
218+
token: token,
219+
command_type: command_type,
220+
ticket_ref: ticket_ref,
221+
ticket: ticket,
222+
body: body
223+
}) do
224+
case fetch_command_response(ticket.id, command_type) do
225+
{:ok, response} ->
226+
case Github.update_issue_comment(
227+
token,
228+
ticket_ref["owner"],
229+
ticket_ref["repo"],
230+
response.provider_response_id,
231+
body
232+
) do
233+
{:ok, comment} ->
234+
try_update_command_response(response, comment)
235+
236+
# TODO: don't rely on string matching
237+
{:error, "404 Not Found"} ->
238+
Logger.error("Command response #{response.id} not found")
239+
{:error, {:comment_not_found, response.id}}
240+
241+
{:error, reason} ->
242+
Logger.error("Failed to update command response #{response.id}: #{inspect(reason)}")
243+
{:error, reason}
244+
end
245+
246+
{:error, _reason} ->
247+
{:error, :command_response_not_found}
248+
end
249+
end
250+
251+
defp post_response(token, ticket_ref, command_id, command_source, ticket, body) do
252+
with {:ok, comment} <-
253+
Github.create_issue_comment(token, ticket_ref["owner"], ticket_ref["repo"], ticket_ref["number"], body) do
254+
create_command_response(%{
255+
comment: comment,
256+
command_source: command_source,
257+
command_id: command_id,
258+
ticket_id: ticket.id
259+
})
260+
end
261+
end
262+
263+
@spec create_command_response(%{
264+
comment: map(),
265+
command_source: :ticket | :comment,
266+
command_id: integer(),
267+
ticket_id: integer()
268+
}) :: {:ok, CommandResponse.t()} | {:error, any()}
269+
def create_command_response(%{
270+
comment: comment,
271+
command_source: command_source,
272+
command_id: command_id,
273+
ticket_id: ticket_id
274+
}) do
275+
%CommandResponse{}
276+
|> CommandResponse.changeset(%{
277+
provider: "github",
278+
provider_meta: Util.normalize_struct(comment),
279+
provider_command_id: to_string(command_id),
280+
provider_response_id: to_string(comment["id"]),
281+
command_source: command_source,
282+
command_type: :bounty,
283+
ticket_id: ticket_id
284+
})
285+
|> Repo.insert()
286+
end
287+
288+
defp try_update_command_response(command_response, body) do
289+
case command_response
290+
|> CommandResponse.changeset(%{provider_meta: Util.normalize_struct(body)})
291+
|> Repo.update() do
292+
{:ok, command_response} ->
293+
{:ok, command_response}
294+
295+
{:error, reason} ->
296+
Logger.error("Failed to update command response #{command_response.id}: #{inspect(reason)}")
297+
{:ok, command_response}
298+
end
299+
end
167300
end

0 commit comments

Comments
 (0)