Add /reopen command
This commit is contained in:
@@ -4,6 +4,7 @@ class Prompts::CommandsController < ApplicationController
|
||||
[ "/add_card", "Add a new card", "/add_card " ],
|
||||
[ "/assign", "Assign cards to people", "/assign @" ],
|
||||
[ "/close", "Close cards (with optional reason)", "/close " ],
|
||||
[ "/reopen", "Reopen cards", "/reopen" ],
|
||||
[ "/clear", "Clear all filters", "/clear" ],
|
||||
[ "/consider", "Move cards back to Considering", "/consider" ],
|
||||
[ "/do", "Move cards to Doing", "/do" ],
|
||||
|
||||
@@ -77,7 +77,7 @@ class Command::Ai::Translator
|
||||
Cards have comments and live inside collections.
|
||||
|
||||
Context filters describe card state already true.
|
||||
Commands (/assign, /tag, /close, /search, /clear, /do, /consider, /stage, /visit, /add_card, /user) apply new actions.
|
||||
Commands (/assign, /tag, /close, /reopen, /search, /clear, /do, /consider, /stage, /visit, /add_card, /user) apply new actions.
|
||||
|
||||
Context properties you may use
|
||||
* terms — array of keywords
|
||||
@@ -100,7 +100,7 @@ class Command::Ai::Translator
|
||||
|
||||
* Use terms only if the query explicitly refers to cards; plain-text searches go to /search.
|
||||
* Numbers without the word "card(s)" default to terms **unless the number is the direct object of an
|
||||
action verb that operates on cards (move, assign, tag, close, stage, consider, do, etc.).**
|
||||
action verb that operates on cards (move, assign, tag, close, reopen, stage, consider, do, etc.).**
|
||||
– "123" (with no action verb) → context: { terms: ["123"] }
|
||||
– "card 123" → context: { card_ids: [123] }
|
||||
– "card 1,2" → context: { card_ids: [1, 2] }
|
||||
@@ -127,12 +127,11 @@ class Command::Ai::Translator
|
||||
* "Falling back soon" cards → indexed_by: "falling_back_soon"
|
||||
* **Past-tense** “tagged with #X”, “#X cards” → tag_ids: ["X"] (filter)
|
||||
* **Imperative** “tag …”, “tag with #X”, “add the #X tag”, “apply #X” → command /tag #X (never a filter)
|
||||
* When using past-tense verbs such as "assigned" or "closed", always use the corresponding filter, NEVER a command.
|
||||
* For command that acts on cards, you can reference those by their ID (number). Use the filter "card_ids" when the user passes numbers as command arguments.
|
||||
- User can reference with numbers to a single card or to a group of cards. E.g:
|
||||
- "close 123 and 456" → context: { card_ids: [123, 456] }, commands: [ "/close" ]
|
||||
- "assign 789 to jz" → context: { card_ids: [789] }, commands: [ "/assign jz" ]
|
||||
- "close 789" → context: { card_ids: [789] }, commands: [ "/close" ]
|
||||
- "reopen 789" → context: { card_ids: [789] }, commands: [ "/reopen" ]
|
||||
- "assign 5, 82 and 9 to jz" → context: { card_ids: [5, 82 and 9] }, commands: [ "/assign jz" ]
|
||||
* "Unassigned cards" (or “not assigned”, “with no assignee”) → assignment_status: "unassigned".
|
||||
– IMPORTANT: Only set assignment_status when the user **explicitly** asks for an unassigned state
|
||||
@@ -162,14 +161,15 @@ class Command::Ai::Translator
|
||||
* /visit <url|path> → open any other URL or internal path (cards, settings, etc.).
|
||||
* /do → engage with card and move it to "doing"
|
||||
* /consider → move card back to "considering" (reconsider)
|
||||
* /reopen → reopen closed cards (moves them back to "open")
|
||||
* Unless a clear command applies, fallback to /search with the verbatim text.
|
||||
* When searching for nouns (non-person), prefer /search over terms.
|
||||
* When the person to pass to a command is "me" or "myself", use "#{user.to_gid}"
|
||||
* Respect the spoken order of commands.
|
||||
|
||||
* "close as [reason]" or "close because [reason]" → /close [reason]
|
||||
– Remove "as" or "because" from the actual command
|
||||
* Lone "close" → /close (acts on current context)
|
||||
* Lone "reopen" → /reopen (acts on current context)
|
||||
* /close must **only** be produced if the request explicitly contains the verb “close”.
|
||||
* /reopen must **only** be produced if the request explicitly contains the verb “reopen”.
|
||||
* /stage [workflow stage]→ assign the card to the given stage (never takes card IDs).
|
||||
* “Move <ID(s)> to <Stage>” → context.card_ids = [IDs]; command /stage <Stage>
|
||||
* “Move <ID(s)> to doing” → context.card_ids = [IDs]; command /do
|
||||
@@ -189,7 +189,8 @@ class Command::Ai::Translator
|
||||
|
||||
------------------------- VISIT USER PROFILES ------------------------
|
||||
|
||||
Use **/user <Name>** (not /visit) whenever the request is about viewing a person’s profile or activity:
|
||||
Use **/user <Name>** (not /visit) whenever the request is about viewing a person’s profile, activity
|
||||
or what that person is up to:
|
||||
|
||||
• visit user mike → /user mike*
|
||||
• view user kevin → /user kevin*
|
||||
@@ -197,7 +198,7 @@ class Command::Ai::Translator
|
||||
|
||||
---------------------------- CRUCIAL DON’TS ---------------------------
|
||||
|
||||
* When the query contains active verbs such as "assign" or "close", always use the corresponding command, NEVER a filter.
|
||||
* When the query contains active verbs such as "assign", "close", or "reopen", always use the corresponding command, NEVER a filter.
|
||||
* Don’t output “/visit /users/<name>”. Profile requests must use **/user <name>**.
|
||||
* Never use names, tags, or stage names mentioned **inside commands** (like /assign, /tag, /stage) as filters.
|
||||
* Never duplicate the assignee in both commands and context.
|
||||
@@ -238,7 +239,14 @@ class Command::Ai::Translator
|
||||
User: assign to me
|
||||
Output:
|
||||
{
|
||||
"commands": ["/assign <GID of current user>"]
|
||||
"commands": ["/assign #{user.to_gid}"]
|
||||
}
|
||||
|
||||
User: reopen cards closed this week
|
||||
Output:
|
||||
{
|
||||
"context": { "closure": "thisweek", "indexed_by": "closed" },
|
||||
"commands": ["/reopen"]
|
||||
}
|
||||
|
||||
User: tag with #design*
|
||||
|
||||
@@ -28,8 +28,6 @@ class Command::Parser
|
||||
rich_text_command = as_plain_text_with_attachable_references(string)
|
||||
plain_text_command = as_plain_text(string)
|
||||
|
||||
Rails.logger.info "COMMANDS: #{rich_text_command} AND #{plain_text_command}"
|
||||
|
||||
parse_plain_text_command(plain_text_command) || parse_rich_text_command(rich_text_command)
|
||||
end
|
||||
|
||||
@@ -57,6 +55,8 @@ class Command::Parser
|
||||
Command::ClearFilters.new(params: filter.as_params)
|
||||
when "/close"
|
||||
Command::Close.new(card_ids: cards.ids, reason: combined_arguments)
|
||||
when "/reopen"
|
||||
Command::Reopen.new(card_ids: cards.ids)
|
||||
when "/consider", "/reconsider"
|
||||
Command::Consider.new(card_ids: cards.ids)
|
||||
when "/do"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
class Command::Reopen < Command
|
||||
include Command::Cards
|
||||
|
||||
store_accessor :data, :reopened_card_ids
|
||||
|
||||
def title
|
||||
"Reopen #{cards_description}"
|
||||
end
|
||||
|
||||
def execute
|
||||
reopened_card_ids = []
|
||||
|
||||
transaction do
|
||||
cards.find_each do |card|
|
||||
reopened_card_ids << card.id
|
||||
card.reopen(user: user)
|
||||
end
|
||||
|
||||
update! reopened_card_ids: reopened_card_ids
|
||||
end
|
||||
end
|
||||
|
||||
def undo
|
||||
transaction do
|
||||
reopened_cards.find_each do |card|
|
||||
card.close(user: user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def reopened_cards
|
||||
user.accessible_cards.where(id: reopened_card_ids)
|
||||
end
|
||||
end
|
||||
@@ -48,8 +48,6 @@ class Command::Ai::TranslatorTest < ActionDispatch::IntegrationTest
|
||||
assert_command({ context: { terms: [ "123" ] } }, "123") # Notice existing cards will be intercepted earlier
|
||||
end
|
||||
|
||||
vcr_record!
|
||||
|
||||
test "acts on cards passing their ids" do
|
||||
assert_command({ context: { card_ids: [ 123, 456 ] }, commands: [ "/close" ] }, "close 123 and 456")
|
||||
assert_command({ context: { card_ids: [ 123 ] }, commands: [ "/close" ] }, "close 123")
|
||||
@@ -70,6 +68,16 @@ class Command::Ai::TranslatorTest < ActionDispatch::IntegrationTest
|
||||
assert_command({ commands: [ "/close" ] }, "close", context: :card)
|
||||
end
|
||||
|
||||
test "reopen cards" do
|
||||
# List context
|
||||
assert_command({ commands: [ "/reopen" ] }, "reopen")
|
||||
assert_command({ context: { assignee_ids: [ "jz" ] }, commands: [ "/reopen" ] }, "reopen cards assigned to jz")
|
||||
assert_command({ context: { closure: "thisweek", indexed_by: "closed" }, commands: [ "/reopen" ] }, "reopen cards closed this week")
|
||||
|
||||
# Card context
|
||||
assert_command({ commands: [ "/reopen" ] }, "reopen", context: :card)
|
||||
end
|
||||
|
||||
test "assign cards" do
|
||||
# List context
|
||||
assert_command({ commands: [ "/assign jz" ] }, "assign to jz")
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
require "test_helper"
|
||||
|
||||
class Command::ReopenTest < ActionDispatch::IntegrationTest
|
||||
include CommandTestHelper
|
||||
|
||||
setup do
|
||||
Current.session = sessions(:david)
|
||||
@card = cards(:text)
|
||||
@card.close(user: users(:david))
|
||||
end
|
||||
|
||||
test "reopen card on perma" do
|
||||
assert_changes -> { @card.reload.open? }, from: false, to: true do
|
||||
execute_command "/reopen", context_url: collection_card_url(@card.collection, @card)
|
||||
end
|
||||
|
||||
assert_nil @card.closed_by
|
||||
end
|
||||
|
||||
test "reopen cards on cards' index page" do
|
||||
cards = cards(:logo, :text, :layout)
|
||||
cards.each { |card| card.close }
|
||||
|
||||
execute_command "/reopen", context_url: collection_cards_url(@card.collection, indexed_by: "closed")
|
||||
|
||||
assert cards.map(&:reload).all?(&:open?)
|
||||
end
|
||||
|
||||
test "undo reopen" do
|
||||
cards = cards(:logo, :text, :layout)
|
||||
cards.each { |card| card.close(user: users(:david)) }
|
||||
|
||||
command = parse_command "/reopen", context_url: collection_cards_url(@card.collection, indexed_by: "closed")
|
||||
command.execute
|
||||
|
||||
assert cards.map(&:reload).all?(&:open?)
|
||||
|
||||
command.undo
|
||||
|
||||
assert cards.map(&:reload).all?(&:closed?)
|
||||
end
|
||||
end
|
||||
+2790
File diff suppressed because it is too large
Load Diff
+2808
File diff suppressed because it is too large
Load Diff
+25003
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+7206
File diff suppressed because it is too large
Load Diff
+13592
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+14371
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2786
File diff suppressed because it is too large
Load Diff
+2404
File diff suppressed because it is too large
Load Diff
+2390
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user