FEATURE: Add support for slack message shortcuts for transcripts (#68)

FEATURE: Add support for slack message shortcuts for transcripts (#68)

Once configured, this adds a new item to the context menu of slack messages. When clicked, the menu item will generate a transcript and present the user with a custom “Post to Discourse” modal. This provides the same functionality as the existing slash-command interface, but is much more user friendly.

diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index a95eb65..0f5083c 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -157,6 +157,10 @@ en:
 
           *Help:* `/discourse help`
         transcript:
+          modal_title: "Create Transcript"
+          modal_description: "All messages in the thread will be assembled into a single forum post. You will be given the opportunity to edit the transcript before posting."
+          transcript_ready: "Transcript ready"
+          continue_on_discourse: "Continue on Discourse"
           error: "Something went wrong when building the transcript, sorry!"
           post_to_discourse: "Click here to draft a post on Discourse with a transcript"
           api_required: "Sorry, this integration isn't setup to support posting transcripts."
diff --git a/lib/discourse_chat/provider/slack/slack_command_controller.rb b/lib/discourse_chat/provider/slack/slack_command_controller.rb
index 053e615..e0f8d88 100644
--- a/lib/discourse_chat/provider/slack/slack_command_controller.rb
+++ b/lib/discourse_chat/provider/slack/slack_command_controller.rb
@@ -22,8 +22,7 @@ module DiscourseChat::Provider::SlackProvider
     def interactive
       json = JSON.parse(params[:payload], symbolize_names: true)
       process_interactive(json)
-
-      render json: { text: I18n.t("chat_integration.provider.slack.transcript.loading") }
+      head :ok
     end
 
     private
@@ -68,8 +67,7 @@ module DiscourseChat::Provider::SlackProvider
 
       Scheduler::Defer.later "Processing slack transcript request" do
         response = build_post_request_response(channel, tokens, slack_channel_id, channel_name, response_url)
-        http = Net::HTTP.new("slack.com", 443)
-        http.use_ssl = true
+        http = DiscourseChat::Provider::SlackProvider.slack_api_http
         req = Net::HTTP::Post.new(URI(response_url), 'Content-Type' => 'application/json')
         req.body = response.to_json
         http.request(req)
@@ -118,30 +116,112 @@ module DiscourseChat::Provider::SlackProvider
     end
 
     def process_interactive(json)
-      action_name = json[:actions][0][:name]
+      Scheduler::Defer.later "Processing slack transcript update" do
+        http = DiscourseChat::Provider::SlackProvider.slack_api_http
+
+        if json[:type] == "block_actions" && json[:actions][0][:action_id] == "null_action"
+          # Do nothing
+        elsif json[:type] == "message_action" && json[:message][:thread_ts]
+          # Context menu used on a threaded message
+          transcript = SlackTranscript.new(
+            channel_name: "##{json[:channel][:name]}",
+            channel_id: json[:channel][:id],
+            requested_thread_ts: json[:message][:thread_ts]
+          )
+
+          # Send a loading modal within 3 seconds:
+          req = Net::HTTP::Post.new(
+            "https://slack.com/api/views.open",
+            'Content-Type' => 'application/json',
+            'Authorization' => "Bearer #{SiteSetting.chat_integration_slack_access_token}"
+          )
+          req.body = {
+            "trigger_id": json[:trigger_id],
+            "view": transcript.build_modal_ui
+          }.to_json
+          response = http.request(req)
+          view_id = JSON.parse(response.body).dig("view", "id")
+
+          # Now load the transcript
+          error_view = generate_error_view("users") unless transcript.load_user_data
+          error_view = generate_error_view("history") unless transcript.load_chat_history
+
+          # Then update the modal with the transcript link:
+          req = Net::HTTP::Post.new(
+            "https://slack.com/api/views.update",
+            'Content-Type' => 'application/json',
+            'Authorization' => "Bearer #{SiteSetting.chat_integration_slack_access_token}"
+          )
+          req.body = {
+            "view_id": view_id,
+            "view": error_view || transcript.build_modal_ui
+          }.to_json
+          response = http.request(req)
+        else
+          # Button clicked in one of our interactive messages
+          req = Net::HTTP::Post.new(URI(json[:response_url]), 'Content-Type' => 'application/json')
+          req.body = build_interactive_response(json).to_json
+          response = http.request(req)
+        end
+      end
+    end
 
-      constant_val = json[:callback_id]
-      changed_val = json[:actions][0][:selected_options][0][:value]
+    def build_interactive_response(json)
+      requested_thread = first_message = last_message = nil
 
-      first_message = (action_name == 'first_message') ? changed_val : constant_val
-      last_message = (action_name == 'first_message') ? constant_val : changed_val
+      if json[:type] == "message_action" # Slack "Shortcut" (for non-threaded messages)
+        first_message = json[:message][:ts]
+      else # Clicking buttons in our transcript UI message
+        action_name = json[:actions][0][:name]
 
-      error_message = { text: I18n.t("chat_integration.provider.slack.transcript.error") }
+        constant_val = json[:callback_id]
+        changed_val = json[:actions][0][:selected_options][0][:value]
 
-      Scheduler::Defer.later "Processing slack transcript update" do
-        break error_message unless transcript = SlackTranscript.new(channel_name: "##{json[:channel][:name]}", channel_id: json[:channel][:id])
-        break error_message unless transcript.load_user_data
-        break error_message unless transcript.load_chat_history
-
-        break error_message unless transcript.set_first_message_by_ts(first_message)
-        break error_message unless transcript.set_last_message_by_ts(last_message)
-
-        http = Net::HTTP.new("slack.com", 443)
-        http.use_ssl = true
-        req = Net::HTTP::Post.new(URI(json[:response_url]), 'Content-Type' => 'application/json')
-        req.body = transcript.build_slack_ui.to_json
-        response = http.request(req)
+        first_message = (action_name == 'first_message') ? changed_val : constant_val
+        last_message = (action_name == 'first_message') ? constant_val : changed_val
+      end
+
+      error_key = "chat_integration.provider.slack.transcript.error"
+
+      return { text: I18n.t(error_key) } unless transcript = SlackTranscript.new(
+        channel_name: "##{json[:channel][:name]}",
+        channel_id: json[:channel][:id],
+        requested_thread_ts: requested_thread
+      )
+      return { text: I18n.t("#{error_key}_users") } unless transcript.load_user_data
+      return { text: I18n.t("#{error_key}_history") } unless transcript.load_chat_history
+
+      if first_message
+        return { text: I18n.t("#{error_key}_ts") } unless transcript.set_first_message_by_ts(first_message)
+      end
+
+      if last_message
+        return { text: I18n.t("#{error_key}_ts") } unless transcript.set_last_message_by_ts(last_message)
       end
+
+      transcript.build_slack_ui
+    end
+
+    def generate_error_view(type = nil)
+      error_key = "chat_integration.provider.slack.transcript.error"
+      error_key += "_#{type}" if type
+
+      {
+        type: "modal",
+        title: {
+          type: "plain_text",
+          text: I18n.t("chat_integration.provider.slack.transcript.modal_title")
+        },
+        blocks: [
+          {
+            type: "section",
+            text: {
+              type: "mrkdwn",
+              text: ":warning: *#{I18n.t(error_key)}*"
+            }
+          }
+        ]
+      }
     end
 
     def slack_token_valid?
diff --git a/lib/discourse_chat/provider/slack/slack_provider.rb b/lib/discourse_chat/provider/slack/slack_provider.rb
index 93bf9fd..8b451fd 100644
--- a/lib/discourse_chat/provider/slack/slack_provider.rb
+++ b/lib/discourse_chat/provider/slack/slack_provider.rb
@@ -91,8 +91,7 @@ module DiscourseChat::Provider::SlackProvider
   end
 
   def self.send_via_api(post, channel, message)
-    http = Net::HTTP.new("slack.com", 443)

[... diff too long, it was truncated ...]

GitHub sha: 610364ff

This commit appears in #68 which was approved by SamSaffron. It was merged by davidtaylorhq.