<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[12396] sites/trunk/common/includes/slack/props: Props: Find Slack user IDs in bulleted lists</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/12396">12396</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/12396","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>iandunn</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-02-13 23:31:27 +0000 (Mon, 13 Feb 2023)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Props: Find Slack user IDs in bulleted lists

See https://wordpress.slack.com/archives/C02QB8GMM/p1676068080079459</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkcommonincludesslackpropslibphp">sites/trunk/common/includes/slack/props/lib.php</a></li>
<li><a href="#sitestrunkcommonincludesslackpropsteststestlibphp">sites/trunk/common/includes/slack/props/tests/test-lib.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#sitestrunkcommonincludesslackpropsreadmemd">sites/trunk/common/includes/slack/props/readme.md</a></li>
<li><a href="#sitestrunkcommonincludesslackpropstestsmentionsinlistjson">sites/trunk/common/includes/slack/props/tests/mentions-in-list.json</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkcommonincludesslackpropslibphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/common/includes/slack/props/lib.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/lib.php   2023-02-13 23:30:35 UTC (rev 12395)
+++ sites/trunk/common/includes/slack/props/lib.php     2023-02-13 23:31:27 UTC (rev 12396)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -7,7 +7,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Adds props in Slack to w.org profiles.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- * Receives webhook notifications for all new messages in `#props`,
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Receives webhook notifications for all new messages in `#props`. See `dotorg/slack/props.php` for the caller.
</ins><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function handle_props_message( object $request ) : string {
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! is_valid_props( $request->event ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -86,8 +86,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Parse the mentioned Slack user IDs from a message event.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- *
- * This assumes that the app is configured to escape usernames.
</del><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function get_recipient_slack_ids( array $blocks ) : array {
</span><span class="cx" style="display: block; padding: 0 10px">        $ids = array();
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -94,13 +92,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        foreach ( $blocks as $block ) {
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $block->elements as $element ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        foreach ( $element->elements as $inner_element ) {
-                               if ( 'user' !== $inner_element->type ) {
-                                       continue;
-                               }
-
-                               $ids[] = $inner_element->user_id;
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 $ids = array_merge( $ids, get_user_ids_from_element( $element ) );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -108,6 +100,28 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Recursively parse any mentioned Slack user IDs from a message element.
+ *
+ * This assumes that the app is configured to escape usernames.
+ */
+function get_user_ids_from_element( object $element ) : array {
+       $ids = array();
+
+       if ( 'user' === $element->type ) {
+               $ids[] = $element->user_id;
+       }
+
+       if ( isset( $element->elements ) ) {
+               foreach ( $element->elements as $inner_element ) {
+                       $ids = array_merge( $ids, get_user_ids_from_element( $inner_element ) );
+               }
+       }
+
+       return $ids;
+}
+
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Find the w.org users associated with the given slack accounts.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function map_slack_users_to_wporg( array $slack_ids ) : array {
</span></span></pre></div>
<a id="sitestrunkcommonincludesslackpropsreadmemd"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/common/includes/slack/props/readme.md</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/readme.md                         (rev 0)
+++ sites/trunk/common/includes/slack/props/readme.md   2023-02-13 23:31:27 UTC (rev 12396)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,69 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+# Slack #props Handler
+
+Automatically adds an activity item to a user profile when they give or receive props in Slack.
+
+
+## Automated Testing
+
+Run the PHPUnit tests, see the root `README.md` for details.
+
+
+## Manual Testing / Development
+
+### Setup
+
+1. Setup a private testing channel, like `#{yourname}-testing`
+       Don't use a generic name, since we may want that in the future. (e.g., `#testing` would be used by the Test Team).
+1. Add the `propsbot-sandbox` app to our Slack workspace
+       Visit https://api.slack.com/apps/A03AM1FQ7N2 and click `Install to Workspace`
+1. Add the `propsbot-sandbox` app to your private test channel
+       View channel > Integrations > Add an app
+1. Install ngrok, to temporarily proxy part of your sandbox
+       Follow the instructions at https://ngrok.com/ to create an account.
+       Download the tarball and extract it to `~/bin/`.
+1. Configure ngrok
+       Copy/paste the following to `~/.ngrok2/ngrok.yml`, and then edit it to include your ngrok account auth token and a random username/password.
+
+       ```
+       # See https://ngrok.com/docs/ngrok-agent/config
+       authtoken: {your ngrok account auth token}
+
+       tunnels:
+         api:
+           # Use basic auth to prevent hackers from accessing sandbox. HelpScout doesn't support this, but Slack does.
+           # To use, include it in the webhook URL, like: https://{random username}:{random password}@rand-ip-hostname.ngrok.io/
+           auth: "{random username}:{random password}"
+           proto: http
+           addr: 443
+           # hostname - leave this disabled so it'll generate random one each time, for security.
+           host_header: api.wordpress.org
+           bind_tls: true
+       ```
+1. `ngrok start api`
+       `api` corresponds to the tunnel name in the config file.
+       Note the ngrok.io URL that it's forwarding to your sandbox. It won't contain the username/password, you'll need to add that manually when you use it.
+       Every time you start ngrok it will generate a new random subdomain.
+1. You should now be able to make requests like `curl {ngrok url}/events/1.0/?location=Seattle&number=1` from your local machine.
+       Make sure you include the username/password in the URL.
+1. Edit the sandbox app to point it to the ngrok URL.
+       https://api.slack.com/apps/A03AM1FQ7N2/event-subscriptions
+       https://{username}:{password}@{ngrok URL}/dotorg/slack/props.php
+       You'll need to update this every time restart ngrok, because the hostname changes (which is good for security).
+
+### Testing
+
+After running the setup above, you should now be able to test by creating a message in your test channel that mentions someone. You can add `error_log()` statements to `props/lib.php` to get info on the request, etc. Once you identify the problem, it's usually faster to setup a PHPUnit test than to continue manually testing. You should do that anyway to avoid regressions.
+
+You'll probably need to use real Slack usernames in your test messages, but you should make sure that their w.org user accounts aren't impacted. To do that you can modify modifying the data in `add_activity_to_profile()` to hardcode a w.org test account ID. Another way is to use the `wporg_is_valid_activity_request` hook on the profiles.wordpress.org side.
+
+You can view, delete, etc activity entries with `wp bp activity`:
+
+* wp bp activity list --component=slack --count=3 --url=profiles.wordpress.org
+* wp bp activity list --component=slack --user-id={user id} --count=3 --url=profiles.wordpress.org
+* wp bp activity delete {activity id} --url=profiles.wordpress.org
+
+### After testing
+
+1. `control-c` to stop ngrok. Don't leave it running all the time, since that'd increase the attack surface unnecessarily.
+1. Remove `propsbot-sandbox` from our workspace, so it doesn't get used accidentally or confuse anyone.
+1. Delete `#{yourname}-testing` when no longer needed.
</ins></span></pre></div>
<a id="sitestrunkcommonincludesslackpropstestsmentionsinlistjson"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/common/includes/slack/props/tests/mentions-in-list.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/tests/mentions-in-list.json                               (rev 0)
+++ sites/trunk/common/includes/slack/props/tests/mentions-in-list.json 2023-02-13 23:31:27 UTC (rev 12396)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,106 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+[
+       {
+               "type": "rich_text",
+               "block_id": "D8i",
+               "elements": [
+                       {
+                               "type": "rich_text_section",
+                               "elements": [
+                                       {
+                                               "type": "text",
+                                               "text": "Props to:\n"
+                                       }
+                               ]
+                       },
+                       {
+                               "type": "rich_text_list",
+                               "elements": [
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U02RR6SGY"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U02RQHNND"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U3KJ0TK4L"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U4L99HZB6"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U024MFP4L"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U6R2E3Y9Y"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U023GFZJ07L"
+                                                       }
+                                               ]
+                                       },
+                                       {
+                                               "type": "rich_text_section",
+                                               "elements": [
+                                                       {
+                                                               "type": "user",
+                                                               "user_id": "U1E5RLU1L"
+                                                       }
+                                               ]
+                                       }
+                               ],
+                               "style": "bullet",
+                               "indent": 0,
+                               "border": 0
+                       },
+                       {
+                               "type": "rich_text_section",
+                               "elements": [
+                                       {
+                                               "type": "text",
+                                               "text": "For testing WordPress 6.2 Beta 1 release!"
+                                       }
+                               ]
+                       }
+               ]
+       }
+]
</ins></span></pre></div>
<a id="sitestrunkcommonincludesslackpropsteststestlibphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/common/includes/slack/props/tests/test-lib.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/common/includes/slack/props/tests/test-lib.php        2023-02-13 23:30:35 UTC (rev 12395)
+++ sites/trunk/common/includes/slack/props/tests/test-lib.php  2023-02-13 23:31:27 UTC (rev 12396)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -124,6 +124,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'user_id' => 'U1E5RLU1L',
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                $mentions_in_list = json_decode( file_get_contents( __DIR__ . '/mentions-in-list.json' ) );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                 $cases = array(
</span><span class="cx" style="display: block; padding: 0 10px">                        'empty' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'blocks'   => array(),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -135,6 +137,11 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                'expected' => $valid_users,
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'mentions in a list' => array(
+                               'blocks'   => $mentions_in_list,
+                               'expected' => $valid_users,
+                       ),
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         'valid' => array(
</span><span class="cx" style="display: block; padding: 0 10px">                                'blocks'   => $valid_request->event->blocks,
</span><span class="cx" style="display: block; padding: 0 10px">                                'expected' => $valid_users,
</span></span></pre>
</div>
</div>

</body>
</html>