Discourse Private Replies

Github: GitHub - discoursehosting/discourse-private-replies

This plugin hides topic replies for everyone but the topic starter and the post author.

Use cases

This can be used for for instance homework assignments where the teacher opens a topic and posts an assignment, and all students make a post with their answers. When everyone has submitted their work, the teacher can open up the answers so the students will be able to discuss them.

A second use case can be an auction where something is offered for sale. People can post their bid and when the sale is done, the topic owner can reveal all bids in order to show the community that everything went according to the rules.

Usage

After installing and enabling the plugin you can turn on the functionality of the plugin on a per-topic basis.

If you are the topic-starter then you will see a new button at the bottom of the topic.

button

By pressing this button you have enabled the Private Replies feature for this topic.

A banner will appear above the topic and the following happens:

  • people will only be able to see posts made by themself, by the topic owner, and by staff.
  • only the topic owner will be able to see all posts.

banner

By pressing the button a second time, the topic will revert back to normal.

Caveats

The plugin disables the following ways to retrieve the post contents:

  • topic view (i.e. the regular way of looking at posts)
  • user profile - activity
  • search
  • raw (/raw/topic_id/post_id)

However, you can still find out who posted in the topic:

  • The topic list icons will still show who has posted in the topic.
  • You will also be able to see ‘user xxx is replying’ at the bottom of the topic.
  • The topics are also visible in the user profile - summary.

However, all of these do not reveal the contents of the post.

Background:

So my SO is a teacher (ancient Greek and classical Latin) and has been using Discourse in her classes for a number of years now. She uses it mainly to distribute assignments to her students and discuss them afterwards. However, all the assignments are being handed in on paper during her classes.

Until now - since the schools have been closed because of all the corona panic she is moving to 100% online teaching, including the assignments. So all those students are currently e-mailing their work to her. This is very sub-optimal since it requires her to do quite a lot of administration (and I can tell you she is not very good at those kind of things :wink: ).

We were discussing a few options and she said: “why can’t I have a topic where people can only see my posts (i.e. the posts made by the topic starter) and their own posts? This would allow me to post an assignment as a topic and have every student post their answers below it without being able to see all the other answers. Then when everybody has entered their work and it is time to discuss, I want to be able to press a button to make the veil go away and make all posts in the topic visible to everyone, so they can all see and discuss each others work.”

Yes - brilliant! (that’s why I love her). So I made a plugin for this. Previous discussion here: Topic replies invisible until topic owner decides to reveal them?

33 Likes

New ways schools can adopt with Discourse! Nice job! :grinning:

7 Likes

Very cool and thank you for sharing this @RGJ :beers:

8 Likes

Great to see this!

My one nitpick is that I’d rather have the feature enabled at the category level, then replies can get revealed per-topic when everything’s submitted.

2 Likes

Very cool, I was wondering if there is a way to still access the contents of private replies:

  • by Group (Teachers)
  • Trust Level (all teachers are at least Trust Level 3).
1 Like

Why do you think that’s better?

That’s a pretty good idea. It shouldn’t be too hard to add that to the plugin.
I’ll keep it in mind but right now I don’t have a use case for this myself. PR’s are welcome though!

Prevents random abuse from topic starters enabling the setting on non-intended categories. It’s a “why would you do that?” situation but still something that needs a flag to resolve.

2 Likes

Yeah, that’s useful indeed. So that could be solved by a category setting ‘allow private replies’.
I thought you wanted a ‘topics have private replies by default’ setting, which would be harder to build.

1 Like

Hello,

Thank you for this plugin! :slight_smile: I really liked it. But unfortunately i have to uninstall it. It seems the Discourse update broke this plugin if i install this plugin the search stop working. Internal error message pop up.

This is in the log:

Info
ArgumentError (wrong number of arguments (given 1, expected 0))
app/controllers/search_controller.rb:132:in `query'
app/controllers/application_controller.rb:340:in `block in with_resolved_locale'
app/controllers/application_controller.rb:340:in `with_resolved_locale'
lib/middleware/omniauth_bypass_middleware.rb:68:in `call'
lib/content_security_policy/middleware.rb:12:in `call'
lib/middleware/anonymous_cache.rb:336:in `call'
config/initializers/100-quiet_logger.rb:23:in `call'
config/initializers/100-silence_logger.rb:31:in `call'
lib/middleware/enforce_hostname.rb:22:in `call'
lib/middleware/request_tracker.rb:176:in `call'
Backtrace
plugins/discourse-private-replies/plugin.rb:70:in `execute'

app/controllers/search_controller.rb:132:in `query'

actionpack (6.0.3.3) lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'

actionpack (6.0.3.3) lib/abstract_controller/base.rb:195:in `process_action'

actionpack (6.0.3.3) lib/action_controller/metal/rendering.rb:30:in `process_action'

actionpack (6.0.3.3) lib/abstract_controller/callbacks.rb:42:in `block in process_action'

activesupport (6.0.3.3) lib/active_support/callbacks.rb:112:in `block in run_callbacks'

app/controllers/application_controller.rb:340:in `block in with_resolved_locale'

i18n (1.8.5) lib/i18n.rb:313:in `with_locale'

app/controllers/application_controller.rb:340:in `with_resolved_locale'

activesupport (6.0.3.3) lib/active_support/callbacks.rb:121:in `block in run_callbacks'

activesupport (6.0.3.3) lib/active_support/callbacks.rb:139:in `run_callbacks'

actionpack (6.0.3.3) lib/abstract_controller/callbacks.rb:41:in `process_action'

actionpack (6.0.3.3) lib/action_controller/metal/rescue.rb:22:in `process_action'

actionpack (6.0.3.3) lib/action_controller/metal/instrumentation.rb:33:in `block in process_action'

activesupport (6.0.3.3) lib/active_support/notifications.rb:180:in `block in instrument'

activesupport (6.0.3.3) lib/active_support/notifications/instrumenter.rb:24:in `instrument'

activesupport (6.0.3.3) lib/active_support/notifications.rb:180:in `instrument'

actionpack (6.0.3.3) lib/action_controller/metal/instrumentation.rb:32:in `process_action'

actionpack (6.0.3.3) lib/action_controller/metal/params_wrapper.rb:245:in `process_action'

activerecord (6.0.3.3) lib/active_record/railties/controller_runtime.rb:27:in `process_action'

actionpack (6.0.3.3) lib/abstract_controller/base.rb:136:in `process'

actionview (6.0.3.3) lib/action_view/rendering.rb:39:in `process'

rack-mini-profiler (2.0.4) lib/mini_profiler/profiling_methods.rb:104:in `block in profile_method'

actionpack (6.0.3.3) lib/action_controller/metal.rb:190:in `dispatch'

actionpack (6.0.3.3) lib/action_controller/metal.rb:254:in `dispatch'

actionpack (6.0.3.3) lib/action_dispatch/routing/route_set.rb:50:in `dispatch'

actionpack (6.0.3.3) lib/action_dispatch/routing/route_set.rb:33:in `serve'

actionpack (6.0.3.3) lib/action_dispatch/journey/router.rb:49:in `block in serve'

actionpack (6.0.3.3) lib/action_dispatch/journey/router.rb:32:in `each'

actionpack (6.0.3.3) lib/action_dispatch/journey/router.rb:32:in `serve'

actionpack (6.0.3.3) lib/action_dispatch/routing/route_set.rb:834:in `call'

lib/middleware/omniauth_bypass_middleware.rb:68:in `call'

rack (2.2.3) lib/rack/tempfile_reaper.rb:15:in `call'

rack (2.2.3) lib/rack/conditional_get.rb:27:in `call'

rack (2.2.3) lib/rack/head.rb:12:in `call'

lib/content_security_policy/middleware.rb:12:in `call'

lib/middleware/anonymous_cache.rb:336:in `call'

rack (2.2.3) lib/rack/session/abstract/id.rb:266:in `context'

rack (2.2.3) lib/rack/session/abstract/id.rb:260:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/cookies.rb:648:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'

activesupport (6.0.3.3) lib/active_support/callbacks.rb:101:in `run_callbacks'

actionpack (6.0.3.3) lib/action_dispatch/middleware/callbacks.rb:26:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/debug_exceptions.rb:32:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'

logster (2.9.4) lib/logster/middleware/reporter.rb:43:in `call'

railties (6.0.3.3) lib/rails/rack/logger.rb:37:in `call_app'

railties (6.0.3.3) lib/rails/rack/logger.rb:28:in `call'

config/initializers/100-quiet_logger.rb:23:in `call'

config/initializers/100-silence_logger.rb:31:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/request_id.rb:27:in `call'

lib/middleware/enforce_hostname.rb:22:in `call'

rack (2.2.3) lib/rack/method_override.rb:24:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/executor.rb:14:in `call'

rack (2.2.3) lib/rack/sendfile.rb:110:in `call'

actionpack (6.0.3.3) lib/action_dispatch/middleware/host_authorization.rb:76:in `call'

rack-mini-profiler (2.0.4) lib/mini_profiler/profiler.rb:321:in `call'

message_bus (3.3.1) lib/message_bus/rack/middleware.rb:61:in `call'

lib/middleware/request_tracker.rb:176:in `call'

railties (6.0.3.3) lib/rails/engine.rb:527:in `call'

railties (6.0.3.3) lib/rails/railtie.rb:190:in `public_send'

railties (6.0.3.3) lib/rails/railtie.rb:190:in `method_missing'

rack (2.2.3) lib/rack/urlmap.rb:74:in `block in call'

rack (2.2.3) lib/rack/urlmap.rb:58:in `each'

rack (2.2.3) lib/rack/urlmap.rb:58:in `call'

unicorn (5.7.0) lib/unicorn/http_server.rb:632:in `process_client'

unicorn (5.7.0) lib/unicorn/http_server.rb:728:in `worker_loop'

unicorn (5.7.0) lib/unicorn/http_server.rb:548:in `spawn_missing_workers'

unicorn (5.7.0) lib/unicorn/http_server.rb:144:in `start'

unicorn (5.7.0) bin/unicorn:128:in `<top (required)>'

vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `load'

vendor/bundle/ruby/2.6.0/bin/unicorn:23:in `<main>'
1 Like

I have pushed a fix.

2 Likes

Thank you for this super fast fix! :slight_smile: :heart: :clap: I will check this soon and inform you! :slight_smile:

Edit: Thank you it works great again! :slightly_smiling_face:

1 Like