cardinal-ai 0.2.17 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/cardinal.css +8 -0
- data/app/controllers/cards_controller.rb +46 -1
- data/app/services/asana.rb +15 -3
- data/app/views/cards/_summary_panel.html.erb +24 -0
- data/config/routes.rb +1 -0
- data/lib/cardinal/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3382a025a7d0f1ce58e7a8a9f64a6a36705a2e27f2e9e9acd116dfa0c8955e8
|
|
4
|
+
data.tar.gz: 13824a0d4d5680a880151780e33b611e16c2299f9c7465bebbf47af5bf32718b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '098257f74f2a65053e822330315ce564e44fddef11ece7cb830364f8f1f8810a8161ae98a3cf439a35d54918b4bcca4670f97ac5f8ced351ed4e0cd8ae724ae2'
|
|
7
|
+
data.tar.gz: 3051666fee426647550bf6abab8abd8d36f79ff8998ca90e03eb0df85b318044aa31389487270bba5a2340e1b48be846cde8253ef40e49e1cf23d0ba4b79b927
|
|
@@ -168,6 +168,14 @@ a { color: var(--blue); text-decoration: none; }
|
|
|
168
168
|
.asana-steps li { margin: 4px 0; }
|
|
169
169
|
.asana-connected { color: var(--green); }
|
|
170
170
|
|
|
171
|
+
.summary-share { display: flex; align-items: center; margin-top: 8px; flex-shrink: 0; }
|
|
172
|
+
.summary-share button[disabled] { opacity: .4; cursor: not-allowed; pointer-events: none; }
|
|
173
|
+
.share-spacer { flex: 1; text-align: center; }
|
|
174
|
+
.share-status { font-size: 12px; font-weight: 600; color: var(--green);
|
|
175
|
+
animation: share-fade 4s ease forwards; }
|
|
176
|
+
.share-status.share-err { color: var(--red); animation: none; }
|
|
177
|
+
@keyframes share-fade { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
|
|
178
|
+
|
|
171
179
|
.pull-form { display: inline; }
|
|
172
180
|
#repo-pull-status { font-size: 0.85rem; }
|
|
173
181
|
#repo-pull-status .pull-ok { color: var(--green); }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class CardsController < ApplicationController
|
|
2
|
-
before_action :set_card, only: [:show, :update, :move, :approve, :summarize, :compact, :destroy, :archive, :unarchive]
|
|
2
|
+
before_action :set_card, only: [:show, :update, :move, :approve, :summarize, :compact, :destroy, :archive, :unarchive, :share_summary]
|
|
3
3
|
|
|
4
4
|
def new
|
|
5
5
|
@board = Board.first!
|
|
@@ -140,6 +140,37 @@ class CardsController < ApplicationController
|
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
+
# Push the customer summary outward: to the source Asana task as a comment,
|
|
144
|
+
# or to the card's PR. The summary was written for exactly this audience.
|
|
145
|
+
def share_summary
|
|
146
|
+
summary = @card.summary.to_s.strip
|
|
147
|
+
if summary.blank?
|
|
148
|
+
@card.log!("error", text: "Nothing to share — the summary is empty.")
|
|
149
|
+
return redirect_to card_path(@card, zoom: "summary")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
flash_text, flash_error = nil, false
|
|
153
|
+
case params[:to]
|
|
154
|
+
when "asana"
|
|
155
|
+
Asana.comment!(@card.asana_url, "Update from Cardinal:\n\n#{summary}")
|
|
156
|
+
@card.log!("status_change", actor: "user", text: "Summary posted to the Asana task as a comment")
|
|
157
|
+
flash_text = "✓ Posted to Asana"
|
|
158
|
+
when "pr"
|
|
159
|
+
out, status = Open3.capture2e("gh", "pr", "comment", @card.pr_url, "--body", summary)
|
|
160
|
+
if status.success?
|
|
161
|
+
@card.log!("status_change", actor: "user", text: "Summary posted as a PR comment")
|
|
162
|
+
flash_text = "✓ Posted to the PR"
|
|
163
|
+
else
|
|
164
|
+
@card.log!("error", text: "Couldn't comment on the PR: #{out.truncate(160)}")
|
|
165
|
+
flash_text, flash_error = "✗ PR comment failed — see the timeline", true
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
respond_with_share_flash(flash_text, flash_error)
|
|
169
|
+
rescue Asana::Error => e
|
|
170
|
+
@card.log!("error", text: "Couldn't post to Asana: #{e.message}")
|
|
171
|
+
respond_with_share_flash("✗ Asana refused — see the timeline", true)
|
|
172
|
+
end
|
|
173
|
+
|
|
143
174
|
# Archive (card #42): off the board, never gone — /board/archive lists,
|
|
144
175
|
# searches, and restores. Running cards can't be archived out from under
|
|
145
176
|
# their agent.
|
|
@@ -162,6 +193,20 @@ class CardsController < ApplicationController
|
|
|
162
193
|
|
|
163
194
|
private
|
|
164
195
|
|
|
196
|
+
# In-place feedback on the Summary tab: replace the panel with a transient
|
|
197
|
+
# ✓/✗ flash next to the share buttons (falls back to a redirect for plain
|
|
198
|
+
# HTML requests).
|
|
199
|
+
def respond_with_share_flash(text, error)
|
|
200
|
+
respond_to do |format|
|
|
201
|
+
format.turbo_stream do
|
|
202
|
+
render turbo_stream: turbo_stream.replace("card_summary",
|
|
203
|
+
partial: "cards/summary_panel",
|
|
204
|
+
locals: { card: @card.reload, share_status: text, share_error: error })
|
|
205
|
+
end
|
|
206
|
+
format.html { redirect_to card_path(@card, zoom: "summary") }
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
165
210
|
def set_card
|
|
166
211
|
@card = Board.first!.cards.find_by!(number: params[:id])
|
|
167
212
|
end
|
data/app/services/asana.rb
CHANGED
|
@@ -54,14 +54,26 @@ module Asana
|
|
|
54
54
|
card
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
# Post text to the task as a comment (an Asana "story").
|
|
58
|
+
def self.comment!(task_url, text)
|
|
59
|
+
request("/tasks/#{task_gid(task_url)}/stories", token,
|
|
60
|
+
method: :post, body: { data: { text: text } })
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.request(path, auth, method: :get, body: nil)
|
|
58
64
|
uri = URI("#{API}#{path}")
|
|
59
65
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
60
66
|
http.use_ssl = true
|
|
61
67
|
http.open_timeout = 10
|
|
62
68
|
http.read_timeout = 15
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
headers = { "Authorization" => "Bearer #{auth}" }
|
|
70
|
+
response =
|
|
71
|
+
if method == :post
|
|
72
|
+
http.post(uri.request_uri, body.to_json, headers.merge("Content-Type" => "application/json"))
|
|
73
|
+
else
|
|
74
|
+
http.get(uri.request_uri, headers)
|
|
75
|
+
end
|
|
76
|
+
unless [200, 201].include?(response.code.to_i)
|
|
65
77
|
raise Error, "Asana said no (HTTP #{response.code}) — check the token, and that it can see this task"
|
|
66
78
|
end
|
|
67
79
|
JSON.parse(response.body)["data"]
|
|
@@ -27,4 +27,28 @@
|
|
|
27
27
|
disabled: card.summary_working? %>
|
|
28
28
|
<% end %>
|
|
29
29
|
|
|
30
|
+
<% can_asana = Asana.connected? && card.asana_url.present? %>
|
|
31
|
+
<% can_pr = card.pr_url.present? %>
|
|
32
|
+
<% if can_asana || can_pr %>
|
|
33
|
+
<div class="summary-share">
|
|
34
|
+
<% if can_asana %>
|
|
35
|
+
<%= button_to "◯ Post as comment on Asana task", share_summary_card_path(card, to: "asana"),
|
|
36
|
+
class: "theme-toggle", disabled: card.summary.blank?,
|
|
37
|
+
data: { turbo_submits_with: "Posting…" },
|
|
38
|
+
title: "Post this summary as a comment on #{card.asana_url}" %>
|
|
39
|
+
<% end %>
|
|
40
|
+
<span class="share-spacer">
|
|
41
|
+
<% if local_assigns[:share_status].present? %>
|
|
42
|
+
<span class="share-status<%= " share-err" if local_assigns[:share_error] %>"><%= share_status %></span>
|
|
43
|
+
<% end %>
|
|
44
|
+
</span>
|
|
45
|
+
<% if can_pr %>
|
|
46
|
+
<%= button_to "💬 Post as comment on PR", share_summary_card_path(card, to: "pr"),
|
|
47
|
+
class: "theme-toggle", disabled: card.summary.blank?,
|
|
48
|
+
data: { turbo_submits_with: "Posting…" },
|
|
49
|
+
title: "Post this summary as a comment on #{card.pr_url}" %>
|
|
50
|
+
<% end %>
|
|
51
|
+
</div>
|
|
52
|
+
<% end %>
|
|
53
|
+
|
|
30
54
|
</div>
|
data/config/routes.rb
CHANGED
data/lib/cardinal/version.rb
CHANGED