Discourse Mingle

Mingle 是一个破冰插件 :mountain_snow: :hammer:,专为 Discourse 设计,灵感来源于 Donut Slackbot,最初在 此处 进行了讨论。[你好 @debryc!]

工作原理

指定一个时间段(例如 2 周)、一组用户以及一条可自定义的消息。

每隔一个时间段,我们会随机将组内成员配对,并向他们发送一条友好的私信,邀请他们彼此进一步了解。

如何操作

要设置 Mingle,请遵循以下四个步骤:

  • 按照 常规插件安装说明 安装插件

  • 访问 Mingle 设置面板:\u003cyoursite.com\u003e/admin/site_settings/category/all_results?filter=mingle。选择一个用户组,设置时间段,并选择发送消息的用户。Mingle 默认处于禁用状态,以确保您能按自己的意愿进行配置,因此请在准备好后启用它。

  • 访问 Staff 分类;您应该在那里看到一个新主题,标题中包含“Mingle”。这是您的消息模板!点击进入,阅读说明,并自定义要发送给用户的消息。

  • 完成! 第一条消息将在指定的时间段内发出,发送后会自动安排下一条消息。

其他功能

  • 请注意,您可以使用以下格式为 mingle_group_name 指定多个组进行混合:

    group_1|group_2|group_3
    
  • 您还可以通过更改“Mingle 组大小”选项来调整用户被分组的规模(默认为两人一组)。

  • 如果您想设定下一次匹配的具体时间,可以点击下次计划运行时间旁边的“更改”按钮;如果您修改了间隔类型或间隔数量(例如,从 3 周改为 4 周,或从 3 天改为 3 周),Mingle 将自动根据指定的时间间隔重新安排现有任务。

如果出现问题

  • 您可以在 \u003cyoursite.com\u003e/sidekiq/scheduled 的 Sidekiq 队列中查看下一次计划的 Mingle 任务;它的名称是 Jobs::Mingle。将其添加到队列中将立即执行,并安排下一次任务。
  • 如果该队列中意外没有计划任务,只需禁用然后重新启用 Mingle 插件,它应该会恢复。
  • 若要停止安排 Mingle 任务,只需禁用 mingle_enabled 设置(这将同时终止当前已计划的 Mingle 任务)。
  • 如果您的模板损坏无法修复 :fonzie:,可以通过运行以下命令恢复原始模板:
    /var/discourse/launcher enter app
    rails c
    Mingle::Initializer.new.reinitialize!
    

贡献

错误报告

目前这是一个 Beta 版插件(我昨天才写的 :partying_face:),因此请尝试使用,并在本主题中提及我(@gdpelican)或在 问题列表 中报告任何问题。

代码

  • Fork 项目(https://github.com/[my-github-username]/mingle/fork
  • 创建您的功能分支(git checkout -b my-new-feature
  • 提交您的更改(git commit -am 'Add some feature'
  • 推送到分支(git push origin my-new-feature
  • 创建新的拉取请求
  • 成为一名出色的开源贡献者!

未来可能的改进

  • 目前匹配是完全随机的,但也许有更好的方法,例如考虑某对用户是否曾经匹配过、他们是资深用户还是新手等。
  • 目前如果组内人数为奇数,每次匹配会有一个人“被落下”。:crying_cat_face: 如果能将这个人加入三人组消息,或者采取其他措施而不是忽略他们,那就太好了。
  • 我认为我们需要支持在模板中设置“默认”内容,例如当用户未设置某个自定义字段时(目前只会填入空字符串,这可能导致奇怪的句子,如“你知道吗,Flynn ?”)。
41 个赞

Very cool!
I think the ability to set the day of week and time of day to do the matchups would be on my immediate wishlist, so people would get the message when they are likely to be around and ready to reply.

And I think multiple text templates would be even better, to keep the bot from repeating itself each time it sends out the scheduled messages, and allow varying the number of people in each message. So you could mingle with just one person one month, then get a group message with 3-5 people the next and so on accompanied by a message template that matches the number of participants.

3 个赞

Nice work! Very cool to see the discussion / proposal for this and then a working v1 literally within days :smiley:

This is good but the introduction Pm needs more context. Why is this running? What is it for? Why are people being matched?

4 个赞

Yeah, I agree, but copy isn’t my super-strong suit, and of course I would think that every community would want some level of customization there; online communities being a quite different message than in-person work communities, for example.

Maybe you or @erlend_sh or @HAWK or some of the other more wordsy-minded folks could help me get to a better default message there? PRs are, of course, welcome. (I also need to shuffle it around so that that default text there is translatable)

EDIT: if you try this out in your community, I’d love to see what you put for your templates; feel free to send em to me and I’ll cobble together a better default at some point.

4 个赞

Very unique and useful plugin! :heart:

I have an idea: to make this viable for new or inactive forums, what about being able to match users that have last signed in the last 7 days (or any set time) so that mingles can be expected to be active. Because if random users get paired, there can be a good chance of pairing with an inactive user. Just a thought :slight_smile:

4 个赞

This is so awesome that it makes me want to break my rule of not installing new plugins until they’ve been tested for a while! :+1:

One quick suggestion regarding wording: I think it would sound much more inviting if the message title said “You’ve been invited to a mingle” instead of “You’ve been matched for a mingle”.

And some ideas for further development:

  1. It would be very useful to match pairs from different groups. (e.g. on one of my sites users are members of groups to reflect their background (from what perspective they are interested in the forums subject matter) and while it could be useful to match people from the same background (strengthen bonding social capital) it would also be great to match people from different backgrounds (strengthen bridging social capital).
  2. It may be useful to be able to specify the size of the mingle groups. I’m not sure about this because you may need to do it in diads in order to maximize likelihood that a conversation actually takes off (if you know, it’s only you and one other person, you may feel more inclined to respond than if you know there are others who can respond instead of you). But, given that the vast majority of any forum is passive, it may also be so that neither of the two matched users responds at all, so that nothing happens at all, wheras if you have, say, four people in a group, you have double the chance that someone makes the first step. And with three instead of one potential respondent, chances are that at least one of them responds. And once that has happened, chances are that the other two also chime in. It’s a matter of trying out, I guess, but my prediction would be that diads will only work if one or both users are rather active/committed users (which gives us yet another reason for suggestion 1 above).
6 个赞

Mingle is normally a verb in English so “You’ve been invited to mingle” would be even better.

5 个赞

Okay, I’ve done a little bit of stuff here. Here is a list of stuff I did.

  • There’s now a ‘mingle group size’ option which will allow you to create group sizes of your choosing (2 or more, of course)
  • I made the default messaging translatable in client.en.yml. If you have an idea for a better default message (title or message body), please PR it.
  • I’ve added a toggle to the mingle admin panel, which will allow you to specify the exact date and time of the next event at any time.


    (NB: this is waiting on a PR to core to add the plugin outlet, so it won’t show up today.)

So you can now go “I want the events to happen every 4 weeks”, and then change the time of the next event to be, say, Tuesday at 2pm or whatever you fancy.

  • I’ve made it so that the group input can accept multiple group names delimited by |. So you can put
    marketers|engineers
    
    And get matches from both groups (note that there will still be inter-group matches, this simply takes users from both groups and mixes them up)
  • I merged a PR to translate it into Russian, thanks @Stranik!

Some thoughts on other feedback:

  • Re having multiple templates. Yes? But I’m not totally convinced of the right method here yet. As an admin, I think I’d much rather update my single template and know what’s going out, rather than having three templates that could go out. Also, since it’s a topic with an edit history, if I want to go back to a previous version, it’s really easy to do so, meaning you don’t gain a whole ton from having multiple templates; a single topic can currently hold multiple templates through the edit history.
  • I’m not certain about a recency threshold just yet, but I’m considering it.

Also, could you make this a Wiki topic plz @codinghorror? [Side note, I wonder if there’s appetite for a setting which allows all topics in a category to be wiki topics automatically]

8 个赞

That already is part of Discourse :slight_smile:

In this case, since this is your plugin, it makes sense for people to suggest content and for you to own the edit, but this is a good idea

Enjoying the development overall

2 个赞

I am so excited for this! I’m now wondering what’s the easiest way for people to move in and out of being paired for a mingle. For example, is there a way to pre-message people in a group and say something like:

Get ready to mingle! If you want to be paired up with another member of the community on August 27th, click here to sign up. If you want to wait until next time, no action needed.

Note: Not interested in mingling anymore? Click here to opt-out of these emails

This would also address the issue @nexo brought up re: inactive users.

Or, maybe a workaround is for the group admin to clear out the group each time and have people add themselves back in? That sounds like a lot of work, though…

5 个赞

I think you can totally handle this manually for the time being.

  • Create a new group, minglers for example, and set it to be public and anyone can join and leave
  • Send out a message to trust_level_1 (or everyone, or people you care about), introducing the program and explaining that it’s opt-in. Include a link to yoursite.com/groups/minglers, and instruct them to click ‘Join’ if they want to be a part.

One-click opt-out seems like a thing we could consider, but for now you could customize your PM template to have something like

Not interested in mingling in the future? Click [here](yoursite.com/groups/minglers) and select ‘Leave’ to opt-out.

2 个赞

Mingle is continually adding admin themes when I rebuild Discourse:

This also seems to be related to “oops” page crashes, in that mingle and the Iconified Header Links theme somehow conflict. When both are active my rebuild always fails. To fix it I have to:

  1. enter safe-mode
  2. disable Iconified Header Links
  3. load index page
  4. re-enable Iconified Header Links

This only happens when Mingle is installed and active.

1 个赞

@FoohonPie Do you know the error that is being thrown? (should be able to see it by visiting /logs)

This is pretty neat - thanks for creating this plugin and sharing it with the community! Is anyone using it with success? How is it going?

I was just talking with some colleagues in my community about bringing people together around themes of shared interest. E.g. people we know to have shared interest who we think should meet. We already do it on an ad hoc basis, but this plugin came to mind as a means to systematize it and not create more opportunities for connecting and engaging.

I wonder if there might be scope and interest to combine this with the Events Plugin 📆 by @angus, to allow for the creation of scheduled mingle events. The events modal when creating an event topic could have a “this is a mingle!” option, which when selected opens up options to set it up including the message template.

Other ideas that spring to mind:

  • ability to customize the “host” user who sends the mingle messages, e.g. it could be a moderator in charge of the mingle event.
  • ability to include a group in the message and add tags for coordinating any followup, which would work well with the Tickets Plugin 🎟 by @angus
  • ability to specify user fields for matching for a specific mingle event, e.g. to connect up people from same country or who share some datapoint in user custom fields

Separately from this it’s occurring to me that it would be interesting to be able to grab a list of users in a particular topic or message, and add them to a discourse group to use for various purposes like this. I suspect there’s already a data explorer query for this (is there?) but some UI method for admins would be neat. Being able to take the people who have contributed to an active topic and obviously have shared interest and starting a mingle event for them would be super interesting.

And finally, I’d love to see the Voice recording plugin by @pawel get some love, which would really make mingling a heck of a lot more dynamic and fun… letting people save recorded messages to each other.

3 个赞

Had a quick glance at this, in the context of this topic

@gdpelican I think the issue is here:

This snippet will return the ID of the theme_field, not the ID of the theme.

6 个赞

Here are the /logs errors from hitting it again earlier today:

ArgumentError (comparison of Integer with nil failed) /var/www/discourse/app/models/theme.rb:132:in `sort!'
3:24 pm
Failed to handle exception in exception app middleware : comparison of Integer with nil failed
3:24 pm

backtrace
/var/www/discourse/app/models/theme.rb:132:in `sort!'
/var/www/discourse/app/models/theme.rb:132:in `block in transform_ids'
/var/www/discourse/app/models/theme.rb:88:in `get_set_cache'
/var/www/discourse/app/models/theme.rb:123:in `transform_ids'
/var/www/discourse/app/models/theme.rb:206:in `lookup_field'
/var/www/discourse/app/controllers/application_controller.rb:544:in `custom_html_json'
/var/www/discourse/app/controllers/application_controller.rb:525:in `preload_anonymous_data'
/var/www/discourse/app/controllers/application_controller.rb:350:in `preload_json'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:426:in `block in make_lambda'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:198:in `block (2 levels) in halting'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rb:34:in `block (2 levels) in <module:Callbacks>'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:199:in `block in halting'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:513:in `block in invoke_before'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:513:in `each'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:513:in `invoke_before'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:131:in `run_callbacks'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/callbacks.rb:41:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/rescue.rb:22:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications.rb:168:in `block in instrument'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/notifications.rb:168:in `instrument'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.2/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/abstract_controller/base.rb:134:in `process'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.2/lib/action_view/rendering.rb:32:in `process'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-mini-profiler-1.0.2/lib/mini_profiler/profiling_methods.rb:104:in `block in profile_method'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal.rb:191:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_controller/metal.rb:252:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:34:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/mapper.rb:18:in `block in <class:Constraints>'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/mapper.rb:48:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:52:in `block in serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:35:in `each'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/journey/router.rb:35:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/routing/route_set.rb:840:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-protection-2.0.5/lib/rack/protection/frame_options.rb:31:in `call'
/var/www/discourse/lib/middleware/omniauth_bypass_middleware.rb:32:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/tempfile_reaper.rb:15:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/conditional_get.rb:25:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/head.rb:12:in `call'
/var/www/discourse/lib/content_security_policy/middleware.rb:12:in `call'
/var/www/discourse/lib/middleware/anonymous_cache.rb:216:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:232:in `context'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:226:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/cookies.rb:670:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.2/lib/active_support/callbacks.rb:98:in `run_callbacks'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/logster-2.0.1/lib/logster/middleware/reporter.rb:30:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:38:in `call_app'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.2/lib/rails/rack/logger.rb:28:in `call'
/var/www/discourse/config/initializers/100-quiet_logger.rb:16:in `call'
/var/www/discourse/config/initializers/100-silence_logger.rb:29:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/var/www/discourse/lib/middleware/enforce_hostname.rb:17:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/method_override.rb:22:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.2/lib/action_dispatch/middleware/executor.rb:14:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/sendfile.rb:111:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-mini-profiler-1.0.2/lib/mini_profiler/profiler.rb:281:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/message_bus-2.2.0/lib/message_bus/rack/middleware.rb:57:in `call'
/var/www/discourse/lib/middleware/request_tracker.rb:182:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.2/lib/rails/engine.rb:524:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.2/lib/rails/railtie.rb:190:in `public_send'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.2/lib/rails/railtie.rb:190:in `method_missing'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:68:in `block in call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `each'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/http_server.rb:606:in `process_client'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/http_server.rb:701:in `worker_loop'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/http_server.rb:549:in `spawn_missing_workers'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/lib/unicorn/http_server.rb:142:in `start'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.1/bin/unicorn:126:in `<top (required)>'
/var/www/discourse/vendor/bundle/ruby/2.5.0/bin/unicorn:23:in `load'
/var/www/discourse/vendor/bundle/ruby/2.5.0/bin/unicorn:23:in `<main>'

The weird thing is, even after removing mingle entirely, including the themes it adds, my site remains broken until I disable Iconified Header Links. Yet, it only ever happens when mingle starts off in the mix. I’m not sure what to make of that, but it seems worth noting.

Thanks, I’ll patch that up over the weekend. :slight_smile:

EDIT: Alright, that issue with the theme id should now be patched up.

6 个赞

Any chance you will further develop this plugin to ensure that mingle matches are not from within the same group, when mingling groups? I’m creating a discourse community that will have several user types, all related with one another in the same industry of engineering, and I’d like to mingle them in ways that will be mutually beneficial. It would be less beneficial to mingle those of the same group, at least for my use case. Thoughts?

2 个赞

Circling-back on this. My forum has a focus on humanitarian engineering, and I will have groups of mentors and proteges, as well as those seeking and providing help. It would be great to connect these folks in a targeted way. Has there been any development in ensuring that mingle will not match folks within the same group?