223 lines
7.2 KiB
Ruby
223 lines
7.2 KiB
Ruby
|
# name: discourse-data-explorer
|
||
|
# about: Interface for running analysis SQL queries on the live database
|
||
|
# version: 0.1
|
||
|
# authors: Riking
|
||
|
# url: https://github.com/discourse/discourse-data-explorer
|
||
|
|
||
|
enabled_site_setting :data_explorer_enabled
|
||
|
register_asset 'stylesheets/tagging.scss'
|
||
|
|
||
|
after_initialize do
|
||
|
|
||
|
TAGS_FIELD_NAME = "tags"
|
||
|
TAGS_FILTER_REGEXP = /[<\\\/\>\.\#\?\&\s]/
|
||
|
|
||
|
module ::DataExplorer
|
||
|
class Engine < ::Rails::Engine
|
||
|
engine_name "data_explorer"
|
||
|
isolate_namespace DataExplorer
|
||
|
end
|
||
|
|
||
|
def self.clean_tag(tag)
|
||
|
tag.downcase.strip[0...SiteSetting.max_tag_length].gsub(TAGS_FILTER_REGEXP, '')
|
||
|
end
|
||
|
|
||
|
def self.tags_for_saving(tags, guardian)
|
||
|
return unless tags
|
||
|
|
||
|
tags.map! {|t| clean_tag(t) }
|
||
|
tags.delete_if {|t| t.blank? }
|
||
|
tags.uniq!
|
||
|
|
||
|
# If the user can't create tags, remove any tags that don't already exist
|
||
|
unless guardian.can_create_tag?
|
||
|
tag_count = TopicCustomField.where(name: TAGS_FIELD_NAME, value: tags).group(:value).count
|
||
|
tags.delete_if {|t| !tag_count.has_key?(t) }
|
||
|
end
|
||
|
|
||
|
return tags[0...SiteSetting.max_tags_per_topic]
|
||
|
end
|
||
|
|
||
|
def self.notification_key(tag_id)
|
||
|
"tags_notification:#{tag_id}"
|
||
|
end
|
||
|
|
||
|
def self.auto_notify_for(tags, topic)
|
||
|
|
||
|
key_names = tags.map {|t| notification_key(t) }
|
||
|
key_names_sql = ActiveRecord::Base.sql_fragment("(#{tags.map { "'%s'" }.join(', ')})", *key_names)
|
||
|
|
||
|
sql = <<-SQL
|
||
|
INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id)
|
||
|
SELECT ucf.user_id,
|
||
|
#{topic.id.to_i},
|
||
|
CAST(ucf.value AS INTEGER),
|
||
|
#{TopicUser.notification_reasons[:plugin_changed]}
|
||
|
FROM user_custom_fields AS ucf
|
||
|
WHERE ucf.name IN #{key_names_sql}
|
||
|
AND NOT EXISTS(SELECT 1 FROM topic_users WHERE topic_id = #{topic.id.to_i} AND user_id = ucf.user_id)
|
||
|
AND CAST(ucf.value AS INTEGER) <> #{TopicUser.notification_levels[:regular]}
|
||
|
SQL
|
||
|
|
||
|
ActiveRecord::Base.exec_sql(sql)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
require_dependency 'application_controller'
|
||
|
require_dependency 'topic_list_responder'
|
||
|
class DataExplorer::TagsController < ::ApplicationController
|
||
|
include ::TopicListResponder
|
||
|
|
||
|
requires_plugin 'discourse-tagging'
|
||
|
skip_before_filter :check_xhr, only: [:tag_feed, :show]
|
||
|
before_filter :ensure_logged_in, only: [:notifications, :update_notifications]
|
||
|
|
||
|
def cloud
|
||
|
cloud = self.class.tags_by_count(guardian, limit: 300).count
|
||
|
result, max_count, min_count = [], 0, nil
|
||
|
cloud.each do |t, c|
|
||
|
result << { id: t, count: c }
|
||
|
max_count = c if c > max_count
|
||
|
min_count = c if min_count.nil? || c < min_count
|
||
|
end
|
||
|
|
||
|
result.sort_by! {|r| r[:id]}
|
||
|
|
||
|
render json: { cloud: result, max_count: max_count, min_count: min_count }
|
||
|
end
|
||
|
|
||
|
def show
|
||
|
tag_id = ::DiscourseTagging.clean_tag(params[:tag_id])
|
||
|
topics_tagged = TopicCustomField.where(name: TAGS_FIELD_NAME, value: tag_id).pluck(:topic_id)
|
||
|
|
||
|
page = params[:page].to_i
|
||
|
|
||
|
query = TopicQuery.new(current_user, page: page)
|
||
|
latest_results = query.latest_results.where(id: topics_tagged)
|
||
|
@list = query.create_list(:by_tag, {}, latest_results)
|
||
|
@list.more_topics_url = list_by_tag_path(tag_id: tag_id, page: page + 1)
|
||
|
@rss = "tag"
|
||
|
|
||
|
respond_with_list(@list)
|
||
|
end
|
||
|
|
||
|
def tag_feed
|
||
|
discourse_expires_in 1.minute
|
||
|
|
||
|
tag_id = ::DiscourseTagging.clean_tag(params[:tag_id])
|
||
|
@link = "#{Discourse.base_url}/tags/#{tag_id}"
|
||
|
@description = I18n.t("rss_by_tag", tag: tag_id)
|
||
|
@title = "#{SiteSetting.title} - #{@description}"
|
||
|
@atom_link = "#{Discourse.base_url}/tags/#{tag_id}.rss"
|
||
|
|
||
|
query = TopicQuery.new(current_user)
|
||
|
topics_tagged = TopicCustomField.where(name: TAGS_FIELD_NAME, value: tag_id).pluck(:topic_id)
|
||
|
latest_results = query.latest_results.where(id: topics_tagged)
|
||
|
@topic_list = query.create_list(:by_tag, {}, latest_results)
|
||
|
|
||
|
render 'list/list', formats: [:rss]
|
||
|
end
|
||
|
|
||
|
def search
|
||
|
tags = self.class.tags_by_count(guardian)
|
||
|
term = params[:q]
|
||
|
if term.present?
|
||
|
term.gsub!(/[^a-z0-9]*/, '')
|
||
|
tags = tags.where('value like ?', "%#{term}%")
|
||
|
end
|
||
|
|
||
|
tags = tags.count(:value).map {|t, c| { id: t, text: t, count: c } }
|
||
|
|
||
|
render json: { results: tags }
|
||
|
end
|
||
|
|
||
|
def notifications
|
||
|
level = current_user.custom_fields[::DiscourseTagging.notification_key(params[:tag_id])] || 1
|
||
|
render json: { tag_notifications: { id: params[:tag_id], notification_level: level.to_i } }
|
||
|
end
|
||
|
|
||
|
def update_notifications
|
||
|
level = params[:tag_notifications][:notification_level].to_i
|
||
|
|
||
|
current_user.custom_fields[::DiscourseTagging.notification_key(params[:tag_id])] = level
|
||
|
current_user.save_custom_fields
|
||
|
|
||
|
render json: success_json
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
|
||
|
def self.tags_by_count(guardian, opts=nil)
|
||
|
opts = opts || {}
|
||
|
result = TopicCustomField.where(name: TAGS_FIELD_NAME)
|
||
|
.joins(:topic)
|
||
|
.group(:value)
|
||
|
.limit(opts[:limit] || 5)
|
||
|
.order('COUNT(topic_custom_fields.value) DESC')
|
||
|
|
||
|
guardian.filter_allowed_categories(result)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
DataExplorer::Engine.routes.draw do
|
||
|
get '/' => 'tags#cloud'
|
||
|
get '/filter/cloud' => 'tags#cloud'
|
||
|
get '/filter/search' => 'tags#search'
|
||
|
get '/:tag_id.rss' => 'tags#tag_feed'
|
||
|
get '/:tag_id' => 'tags#show', as: 'list_by_tag'
|
||
|
get '/:tag_id/notifications' => 'tags#notifications'
|
||
|
put '/:tag_id/notifications' => 'tags#update_notifications'
|
||
|
end
|
||
|
|
||
|
Discourse::Application.routes.append do
|
||
|
mount ::DataExplorer::Engine, at: "/tags"
|
||
|
end
|
||
|
|
||
|
# Add a `tags` reader to the Topic model for easy reading of tags
|
||
|
add_to_class(:topic, :tags) do
|
||
|
result = custom_fields[TAGS_FIELD_NAME]
|
||
|
return [result].flatten if result
|
||
|
end
|
||
|
|
||
|
# Save the tags when the topic is saved
|
||
|
PostRevisor.track_topic_field(:tags_empty_array) do |tc, val|
|
||
|
if val.present?
|
||
|
tc.record_change(TAGS_FIELD_NAME, tc.topic.custom_fields[TAGS_FIELD_NAME], nil)
|
||
|
tc.topic.custom_fields.delete(TAGS_FIELD_NAME)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
PostRevisor.track_topic_field(:tags) do |tc, tags|
|
||
|
if tags.present?
|
||
|
tags = ::DataExplorer.tags_for_saving(tags, tc.guardian)
|
||
|
|
||
|
new_tags = tags - (tc.topic.tags || [])
|
||
|
tc.record_change(TAGS_FIELD_NAME, tc.topic.custom_fields[TAGS_FIELD_NAME], tags)
|
||
|
tc.topic.custom_fields.update(TAGS_FIELD_NAME => tags)
|
||
|
|
||
|
::DataExplorer.auto_notify_for(new_tags, tc.topic) if new_tags.present?
|
||
|
end
|
||
|
end
|
||
|
|
||
|
on(:topic_created) do |topic, params, user|
|
||
|
tags = ::DataExplorer.tags_for_saving(params[:tags], Guardian.new(user))
|
||
|
if tags.present?
|
||
|
topic.custom_fields.update(TAGS_FIELD_NAME => tags)
|
||
|
topic.save
|
||
|
::DataExplorer.auto_notify_for(tags, topic)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
add_to_class(:guardian, :can_create_tag?) do
|
||
|
user && user.has_trust_level?(SiteSetting.min_trust_to_create_tag.to_i)
|
||
|
end
|
||
|
|
||
|
# Return tag related stuff in JSON output
|
||
|
TopicViewSerializer.attributes_from_topic(:tags)
|
||
|
add_to_serializer(:site, :can_create_tag) { scope.can_create_tag? }
|
||
|
add_to_serializer(:site, :tags_filter_regexp) { TAGS_FILTER_REGEXP.source }
|
||
|
|
||
|
end
|
||
|
|