DEV: Introduce syntax_tree for ruby formatting (#208)

This commit is contained in:
David Taylor 2022-12-29 12:31:29 +00:00 committed by GitHub
parent ac6b0467a1
commit 148d6c32a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1064 additions and 897 deletions

View File

@ -55,3 +55,12 @@ jobs:
- name: Rubocop - name: Rubocop
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
run: bundle exec rubocop . run: bundle exec rubocop .
- name: Syntax Tree
if: ${{ !cancelled() }}
run: |
if test -f .streerc; then
bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake')
else
echo "Stree config not detected for this repository. Skipping."
fi

View File

@ -80,7 +80,7 @@ jobs:
- name: Get yarn cache directory - name: Get yarn cache directory
id: yarn-cache-dir id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
- name: Yarn cache - name: Yarn cache
uses: actions/cache@v3 uses: actions/cache@v3
@ -130,7 +130,7 @@ jobs:
shell: bash shell: bash
run: | run: |
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then
echo "::set-output name=files_exist::true" echo "files_exist=true" >> $GITHUB_OUTPUT
fi fi
- name: Plugin RSpec - name: Plugin RSpec
@ -142,7 +142,7 @@ jobs:
shell: bash shell: bash
run: | run: |
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
echo "::set-output name=files_exist::true" echo "files_exist=true" >> $GITHUB_OUTPUT
fi fi
- name: Plugin QUnit - name: Plugin QUnit

View File

@ -1,2 +1,2 @@
inherit_gem: inherit_gem:
rubocop-discourse: default.yml rubocop-discourse: stree-compat.yml

2
.streerc Normal file
View File

@ -0,0 +1,2 @@
--print-width=100
--plugins=plugin/trailing_comma

View File

@ -1,7 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source "https://rubygems.org"
group :development do group :development do
gem 'rubocop-discourse' gem "rubocop-discourse"
gem "syntax_tree"
end end

View File

@ -6,6 +6,7 @@ GEM
parallel (1.22.1) parallel (1.22.1)
parser (3.1.2.1) parser (3.1.2.1)
ast (~> 2.4.1) ast (~> 2.4.1)
prettier_print (1.2.0)
rainbow (3.1.1) rainbow (3.1.1)
regexp_parser (2.6.0) regexp_parser (2.6.0)
rexml (3.2.5) rexml (3.2.5)
@ -27,6 +28,8 @@ GEM
rubocop-rspec (2.13.2) rubocop-rspec (2.13.2)
rubocop (~> 1.33) rubocop (~> 1.33)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
syntax_tree (5.1.0)
prettier_print (>= 1.2.0)
unicode-display_width (2.3.0) unicode-display_width (2.3.0)
PLATFORMS PLATFORMS
@ -39,6 +42,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
rubocop-discourse rubocop-discourse
syntax_tree
BUNDLED WITH BUNDLED WITH
2.3.10 2.3.10

View File

@ -3,19 +3,17 @@
class DataExplorer::QueryController < ::ApplicationController class DataExplorer::QueryController < ::ApplicationController
requires_plugin DataExplorer.plugin_name requires_plugin DataExplorer.plugin_name
before_action :set_group, only: %i(group_reports_index group_reports_show group_reports_run) before_action :set_group, only: %i[group_reports_index group_reports_show group_reports_run]
before_action :set_query, only: %i(group_reports_show group_reports_run show update) before_action :set_query, only: %i[group_reports_show group_reports_run show update]
before_action :ensure_admin before_action :ensure_admin
skip_before_action :check_xhr, only: %i(show group_reports_run run) skip_before_action :check_xhr, only: %i[show group_reports_run run]
skip_before_action :ensure_admin, only: %i( skip_before_action :ensure_admin,
group_reports_index only: %i[group_reports_index group_reports_show group_reports_run]
group_reports_show
group_reports_run
)
def index def index
queries = DataExplorer::Query.where(hidden: false).order(:last_run_at, :name).includes(:groups).to_a queries =
DataExplorer::Query.where(hidden: false).order(:last_run_at, :name).includes(:groups).to_a
database_queries_ids = DataExplorer::Query.pluck(:id) database_queries_ids = DataExplorer::Query.pluck(:id)
Queries.default.each do |params| Queries.default.each do |params|
@ -30,19 +28,19 @@ class DataExplorer::QueryController < ::ApplicationController
queries << query queries << query
end end
render_serialized queries, DataExplorer::QuerySerializer, root: 'queries' render_serialized queries, DataExplorer::QuerySerializer, root: "queries"
end end
def show def show
check_xhr unless params[:export] check_xhr unless params[:export]
if params[:export] if params[:export]
response.headers['Content-Disposition'] = "attachment; filename=#{@query.slug}.dcquery.json" response.headers["Content-Disposition"] = "attachment; filename=#{@query.slug}.dcquery.json"
response.sending_file = true response.sending_file = true
end end
return raise Discourse::NotFound if !guardian.user_can_access_query?(@query) || @query.hidden return raise Discourse::NotFound if !guardian.user_can_access_query?(@query) || @query.hidden
render_serialized @query, DataExplorer::QuerySerializer, root: 'query' render_serialized @query, DataExplorer::QuerySerializer, root: "query"
end end
def groups def groups
@ -55,40 +53,48 @@ class DataExplorer::QueryController < ::ApplicationController
respond_to do |format| respond_to do |format|
format.json do format.json do
queries = DataExplorer::Query.for_group(@group) queries = DataExplorer::Query.for_group(@group)
render_serialized(queries, DataExplorer::QuerySerializer, root: 'queries') render_serialized(queries, DataExplorer::QuerySerializer, root: "queries")
end end
end end
end end
def group_reports_show def group_reports_show
return raise Discourse::NotFound if !guardian.group_and_user_can_access_query?(@group, @query) || @query.hidden if !guardian.group_and_user_can_access_query?(@group, @query) || @query.hidden
return raise Discourse::NotFound
end
respond_to do |format| respond_to do |format|
format.json do format.json do
query_group = DataExplorer::QueryGroup.find_by(query_id: @query.id, group_id: @group.id) query_group = DataExplorer::QueryGroup.find_by(query_id: @query.id, group_id: @group.id)
render json: { render json: {
query: serialize_data(@query, DataExplorer::QuerySerializer, root: nil), query: serialize_data(@query, DataExplorer::QuerySerializer, root: nil),
query_group: serialize_data(query_group, DataExplorer::QueryGroupSerializer, root: nil), query_group:
} serialize_data(query_group, DataExplorer::QueryGroupSerializer, root: nil),
}
end end
end end
end end
def group_reports_run def group_reports_run
return raise Discourse::NotFound if !guardian.group_and_user_can_access_query?(@group, @query) || @query.hidden if !guardian.group_and_user_can_access_query?(@group, @query) || @query.hidden
return raise Discourse::NotFound
end
run run
end end
def create def create
query = DataExplorer::Query.create!(params.require(:query).permit(:name, :description, :sql).merge(user_id: current_user.id, last_run_at: Time.now)) query =
DataExplorer::Query.create!(
params
.require(:query)
.permit(:name, :description, :sql)
.merge(user_id: current_user.id, last_run_at: Time.now),
)
group_ids = params.require(:query)[:group_ids] group_ids = params.require(:query)[:group_ids]
group_ids&.each do |group_id| group_ids&.each { |group_id| query.query_groups.find_or_create_by!(group_id: group_id) }
query.query_groups.find_or_create_by!(group_id: group_id) render_serialized query, DataExplorer::QuerySerializer, root: "query"
end
render_serialized query, DataExplorer::QuerySerializer, root: 'query'
end end
def update def update
@ -97,12 +103,10 @@ class DataExplorer::QueryController < ::ApplicationController
group_ids = params.require(:query)[:group_ids] group_ids = params.require(:query)[:group_ids]
DataExplorer::QueryGroup.where.not(group_id: group_ids).where(query_id: @query.id).delete_all DataExplorer::QueryGroup.where.not(group_id: group_ids).where(query_id: @query.id).delete_all
group_ids&.each do |group_id| group_ids&.each { |group_id| @query.query_groups.find_or_create_by!(group_id: group_id) }
@query.query_groups.find_or_create_by!(group_id: group_id)
end
end end
render_serialized @query, DataExplorer::QuerySerializer, root: 'query' render_serialized @query, DataExplorer::QuerySerializer, root: "query"
rescue DataExplorer::ValidationError => e rescue DataExplorer::ValidationError => e
render_json_error e.message render_json_error e.message
end end
@ -116,9 +120,7 @@ class DataExplorer::QueryController < ::ApplicationController
def schema def schema
schema_version = DB.query_single("SELECT max(version) AS tag FROM schema_migrations").first schema_version = DB.query_single("SELECT max(version) AS tag FROM schema_migrations").first
if stale?(public: true, etag: schema_version, template: false) render json: DataExplorer.schema if stale?(public: true, etag: schema_version, template: false)
render json: DataExplorer.schema
end
end end
# Return value: # Return value:
@ -135,9 +137,7 @@ class DataExplorer::QueryController < ::ApplicationController
query = DataExplorer::Query.find(params[:id].to_i) query = DataExplorer::Query.find(params[:id].to_i)
query.update!(last_run_at: Time.now) query.update!(last_run_at: Time.now)
if params[:download] response.sending_file = true if params[:download]
response.sending_file = true
end
query_params = {} query_params = {}
query_params = MultiJson.load(params[:params]) if params[:params] query_params = MultiJson.load(params[:params]) if params[:params]
@ -145,18 +145,17 @@ class DataExplorer::QueryController < ::ApplicationController
opts = { current_user: current_user.username } opts = { current_user: current_user.username }
opts[:explain] = true if params[:explain] == "true" opts[:explain] = true if params[:explain] == "true"
opts[:limit] = opts[:limit] = if params[:format] == "csv"
if params[:format] == "csv" if params[:limit].present?
if params[:limit].present? limit = params[:limit].to_i
limit = params[:limit].to_i limit = DataExplorer::QUERY_RESULT_MAX_LIMIT if limit > DataExplorer::QUERY_RESULT_MAX_LIMIT
limit = DataExplorer::QUERY_RESULT_MAX_LIMIT if limit > DataExplorer::QUERY_RESULT_MAX_LIMIT limit
limit else
else DataExplorer::QUERY_RESULT_MAX_LIMIT
DataExplorer::QUERY_RESULT_MAX_LIMIT
end
elsif params[:limit].present?
params[:limit] == "ALL" ? "ALL" : params[:limit].to_i
end end
elsif params[:limit].present?
params[:limit] == "ALL" ? "ALL" : params[:limit].to_i
end
result = DataExplorer.run_query(query, query_params, opts) result = DataExplorer.run_query(query, query_params, opts)
@ -168,23 +167,21 @@ class DataExplorer::QueryController < ::ApplicationController
err_msg = err.message err_msg = err.message
if err.is_a? ActiveRecord::StatementInvalid if err.is_a? ActiveRecord::StatementInvalid
err_class = err.original_exception.class err_class = err.original_exception.class
err_msg.gsub!("#{err_class}:", '') err_msg.gsub!("#{err_class}:", "")
else else
err_msg = "#{err_class}: #{err_msg}" err_msg = "#{err_class}: #{err_msg}"
end end
render json: { render json: { success: false, errors: [err_msg] }, status: 422
success: false,
errors: [err_msg]
}, status: 422
else else
pg_result = result[:pg_result] pg_result = result[:pg_result]
cols = pg_result.fields cols = pg_result.fields
respond_to do |format| respond_to do |format|
format.json do format.json do
if params[:download] if params[:download]
response.headers['Content-Disposition'] = response.headers[
"attachment; filename=#{query.slug}@#{Slug.for(Discourse.current_hostname, 'discourse')}-#{Date.today}.dcqresult.json" "Content-Disposition"
] = "attachment; filename=#{query.slug}@#{Slug.for(Discourse.current_hostname, "discourse")}-#{Date.today}.dcqresult.json"
end end
json = { json = {
success: true, success: true,
@ -193,7 +190,7 @@ class DataExplorer::QueryController < ::ApplicationController
result_count: pg_result.values.length || 0, result_count: pg_result.values.length || 0,
params: query_params, params: query_params,
columns: cols, columns: cols,
default_limit: SiteSetting.data_explorer_query_result_limit default_limit: SiteSetting.data_explorer_query_result_limit,
} }
json[:explain] = result[:explain] if opts[:explain] json[:explain] = result[:explain] if opts[:explain]
@ -208,16 +205,16 @@ class DataExplorer::QueryController < ::ApplicationController
render json: json render json: json
end end
format.csv do format.csv do
response.headers['Content-Disposition'] = response.headers[
"attachment; filename=#{query.slug}@#{Slug.for(Discourse.current_hostname, 'discourse')}-#{Date.today}.dcqresult.csv" "Content-Disposition"
] = "attachment; filename=#{query.slug}@#{Slug.for(Discourse.current_hostname, "discourse")}-#{Date.today}.dcqresult.csv"
require 'csv' require "csv"
text = CSV.generate do |csv| text =
csv << cols CSV.generate do |csv|
pg_result.values.each do |row| csv << cols
csv << row pg_result.values.each { |row| csv << row }
end end
end
render plain: text render plain: text
end end

View File

@ -7,9 +7,13 @@ module Jobs
def execute(args) def execute(args)
return unless SiteSetting.data_explorer_enabled return unless SiteSetting.data_explorer_enabled
DataExplorer::Query.where("id > 0") DataExplorer::Query
.where("id > 0")
.where(hidden: true) .where(hidden: true)
.where("(last_run_at IS NULL OR last_run_at < :days_ago) AND updated_at < :days_ago", days_ago: 7.days.ago) .where(
"(last_run_at IS NULL OR last_run_at < :days_ago) AND updated_at < :days_ago",
days_ago: 7.days.ago,
)
.delete_all .delete_all
end end
end end

View File

@ -2,18 +2,20 @@
module DataExplorer module DataExplorer
class Query < ActiveRecord::Base class Query < ActiveRecord::Base
self.table_name = 'data_explorer_queries' self.table_name = "data_explorer_queries"
has_many :query_groups has_many :query_groups
has_many :groups, through: :query_groups has_many :groups, through: :query_groups
belongs_to :user belongs_to :user
validates :name, presence: true validates :name, presence: true
scope :for_group, ->(group) do scope :for_group,
where(hidden: false) ->(group) {
.joins("INNER JOIN data_explorer_query_groups where(hidden: false).joins(
"INNER JOIN data_explorer_query_groups
ON data_explorer_query_groups.query_id = data_explorer_queries.id ON data_explorer_query_groups.query_id = data_explorer_queries.id
AND data_explorer_query_groups.group_id = #{group.id}") AND data_explorer_query_groups.group_id = #{group.id}",
end )
}
def params def params
@params ||= DataExplorer::Parameter.create_from_sql(sql) @params ||= DataExplorer::Parameter.create_from_sql(sql)

View File

@ -2,7 +2,7 @@
module DataExplorer module DataExplorer
class QueryGroup < ActiveRecord::Base class QueryGroup < ActiveRecord::Base
self.table_name = 'data_explorer_query_groups' self.table_name = "data_explorer_query_groups"
belongs_to :query belongs_to :query
belongs_to :group belongs_to :group

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class DataExplorer::QueryGroupSerializer < ActiveModel::Serializer class DataExplorer::QueryGroupSerializer < ActiveModel::Serializer
attributes :id, :group_id, :query_id, :bookmark, attributes :id,
:group_id,
def query_group_bookmark :query_id,
@query_group_bookmark ||= Bookmark.find_by(user: scope.user, bookmarkable: object) :bookmark,
end def query_group_bookmark
@query_group_bookmark ||= Bookmark.find_by(user: scope.user, bookmarkable: object)
end
def include_bookmark? def include_bookmark?
query_group_bookmark.present? query_group_bookmark.present?
@ -18,8 +20,7 @@ class DataExplorer::QueryGroupSerializer < ActiveModel::Serializer
name: query_group_bookmark.name, name: query_group_bookmark.name,
auto_delete_preference: query_group_bookmark.auto_delete_preference, auto_delete_preference: query_group_bookmark.auto_delete_preference,
bookmarkable_id: query_group_bookmark.bookmarkable_id, bookmarkable_id: query_group_bookmark.bookmarkable_id,
bookmarkable_type: query_group_bookmark.bookmarkable_type bookmarkable_type: query_group_bookmark.bookmarkable_type,
} }
end end
end end

View File

@ -1,7 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
class DataExplorer::QuerySerializer < ActiveModel::Serializer class DataExplorer::QuerySerializer < ActiveModel::Serializer
attributes :id, :sql, :name, :description, :param_info, :created_at, :username, :group_ids, :last_run_at, :hidden, :user_id attributes :id,
:sql,
:name,
:description,
:param_info,
:created_at,
:username,
:group_ids,
:last_run_at,
:hidden,
:user_id
def param_info def param_info
object&.params&.map(&:to_hash) object&.params&.map(&:to_hash)

View File

@ -18,7 +18,7 @@ class CreateDataExplorerQueries < ActiveRecord::Migration[6.0]
t.index :query_id t.index :query_id
t.index :group_id t.index :group_id
end end
add_index(:data_explorer_query_groups, [:query_id, :group_id], unique: true) add_index(:data_explorer_query_groups, %i[query_id group_id], unique: true)
DB.exec <<~SQL, now: Time.zone.now DB.exec <<~SQL, now: Time.zone.now
INSERT INTO data_explorer_queries(id, name, description, sql, user_id, last_run_at, hidden, created_at, updated_at) INSERT INTO data_explorer_queries(id, name, description, sql, user_id, last_run_at, hidden, created_at, updated_at)
@ -56,20 +56,32 @@ class CreateDataExplorerQueries < ActiveRecord::Migration[6.0]
WHERE plugin_name = 'discourse-data-explorer' AND type_name = 'JSON' WHERE plugin_name = 'discourse-data-explorer' AND type_name = 'JSON'
SQL SQL
DB.query("SELECT * FROM plugin_store_rows WHERE plugin_name = 'discourse-data-explorer' AND type_name = 'JSON'").each do |row| DB
json = JSON.parse(row.value) .query(
next if json['group_ids'].blank? "SELECT * FROM plugin_store_rows WHERE plugin_name = 'discourse-data-explorer' AND type_name = 'JSON'",
query_id = DB.query("SELECT id FROM data_explorer_queries WHERE )
name = ? AND sql = ?", json['name'], json['sql']).first.id .each do |row|
json = JSON.parse(row.value)
next if json["group_ids"].blank?
query_id =
DB
.query(
"SELECT id FROM data_explorer_queries WHERE
name = ? AND sql = ?",
json["name"],
json["sql"],
)
.first
.id
json['group_ids'].each do |group_id| json["group_ids"].each do |group_id|
next if group_id.blank? || query_id.blank? next if group_id.blank? || query_id.blank?
DB.exec <<~SQL DB.exec <<~SQL
INSERT INTO data_explorer_query_groups(query_id, group_id) INSERT INTO data_explorer_query_groups(query_id, group_id)
VALUES(#{query_id}, #{group_id}) VALUES(#{query_id}, #{group_id})
SQL SQL
end
end end
end
DB.exec <<~SQL DB.exec <<~SQL
SELECT SELECT

View File

@ -2,7 +2,7 @@
class FixQueryIds < ActiveRecord::Migration[6.0] class FixQueryIds < ActiveRecord::Migration[6.0]
def up def up
Rake::Task['data_explorer:fix_query_ids'].invoke Rake::Task["data_explorer:fix_query_ids"].invoke
end end
def down def down

View File

@ -10,7 +10,7 @@ class DataExplorerQueryGroupBookmarkable < BaseBookmarkable
end end
def self.preload_associations def self.preload_associations
[:data_explorer_queries, :groups] %i[data_explorer_queries groups]
end end
def self.list_query(user, guardian) def self.list_query(user, guardian)
@ -20,17 +20,22 @@ class DataExplorerQueryGroupBookmarkable < BaseBookmarkable
return if group_ids.empty? return if group_ids.empty?
end end
query = user.bookmarks_of_type("DataExplorer::QueryGroup") query =
.joins("INNER JOIN data_explorer_query_groups ON data_explorer_query_groups.id = bookmarks.bookmarkable_id") user
.joins("LEFT JOIN data_explorer_queries ON data_explorer_queries.id = data_explorer_query_groups.query_id") .bookmarks_of_type("DataExplorer::QueryGroup")
.joins(
"INNER JOIN data_explorer_query_groups ON data_explorer_query_groups.id = bookmarks.bookmarkable_id",
)
.joins(
"LEFT JOIN data_explorer_queries ON data_explorer_queries.id = data_explorer_query_groups.query_id",
)
query = query.where("data_explorer_query_groups.group_id IN (?)", group_ids) if !user.admin? query = query.where("data_explorer_query_groups.group_id IN (?)", group_ids) if !user.admin?
query query
end end
# Searchable only by data_explorer_queries name # Searchable only by data_explorer_queries name
def self.search_query(bookmarks, query, ts_query, &bookmarkable_search) def self.search_query(bookmarks, query, ts_query, &bookmarkable_search)
bookmarkable_search.call(bookmarks, bookmarkable_search.call(bookmarks, "data_explorer_queries.name ILIKE :q")
"data_explorer_queries.name ILIKE :q")
end end
def self.reminder_handler(bookmark) def self.reminder_handler(bookmark)
@ -38,8 +43,9 @@ class DataExplorerQueryGroupBookmarkable < BaseBookmarkable
bookmark, bookmark,
data: { data: {
title: bookmark.bookmarkable.query.name, title: bookmark.bookmarkable.query.name,
bookmarkable_url: "/g/#{bookmark.bookmarkable.group.name}/reports/#{bookmark.bookmarkable.query.id}" bookmarkable_url:
} "/g/#{bookmark.bookmarkable.group.name}/reports/#{bookmark.bookmarkable.query.id}",
},
) )
end end
@ -51,5 +57,4 @@ class DataExplorerQueryGroupBookmarkable < BaseBookmarkable
return false if !bookmark.bookmarkable.group return false if !bookmark.bookmarkable.group
guardian.user_is_a_member_of_group?(bookmark.bookmarkable.group) guardian.user_is_a_member_of_group?(bookmark.bookmarkable.group)
end end
end end

View File

@ -11,96 +11,104 @@ class Queries
# you must run the query with id=-1 on the site again to update these changes in the site db # you must run the query with id=-1 on the site again to update these changes in the site db
queries = { queries = {
"most-common-likers": { "most-common-likers": {
"id": -1, id: -1,
"name": "Most Common Likers", name: "Most Common Likers",
"description": "Which users like particular other users the most?" description: "Which users like particular other users the most?",
}, },
"most-messages": { "most-messages": {
"id": -2, id: -2,
"name": "Who has been sending the most messages in the last week?", name: "Who has been sending the most messages in the last week?",
"description": "tracking down suspicious PM activity" description: "tracking down suspicious PM activity",
}, },
"edited-post-spam": { "edited-post-spam": {
"id": -3, id: -3,
"name": "Last 500 posts that were edited by TL0/TL1 users", name: "Last 500 posts that were edited by TL0/TL1 users",
"description": "fighting human-driven copy-paste spam" description: "fighting human-driven copy-paste spam",
}, },
"new-topics": { "new-topics": {
"id": -4, id: -4,
"name": "New Topics by Category", name: "New Topics by Category",
"description": "Lists all new topics ordered by category and creation_date. The query accepts a months_ago parameter. It defaults to 0 to give you the stats for the current month." description:
}, "Lists all new topics ordered by category and creation_date. The query accepts a months_ago parameter. It defaults to 0 to give you the stats for the current month.",
"active-topics": { },
"id": -5, "active-topics": {
"name": "Top 100 Active Topics", id: -5,
"description": "based on the number of replies, it accepts a months_ago parameter, defaults to 1 to give results for the last calendar month." name: "Top 100 Active Topics",
}, description:
"top-likers": { "based on the number of replies, it accepts a months_ago parameter, defaults to 1 to give results for the last calendar month.",
"id": -6, },
"name": "Top 100 Likers", "top-likers": {
"description": "returns the top 100 likers for a given monthly period ordered by like_count. It accepts a months_ago parameter, defaults to 1 to give results for the last calendar month." id: -6,
}, name: "Top 100 Likers",
"quality-users": { description:
"id": -7, "returns the top 100 likers for a given monthly period ordered by like_count. It accepts a months_ago parameter, defaults to 1 to give results for the last calendar month.",
"name": "Top 50 Quality Users", },
"description": "based on post score calculated using reply count, likes, incoming links, bookmarks, time spent and read count." "quality-users": {
}, id: -7,
"user-participation": { name: "Top 50 Quality Users",
"id": -8, description:
"name": "User Participation Statistics", "based on post score calculated using reply count, likes, incoming links, bookmarks, time spent and read count.",
"description": "Detailed statistics for the most active users." },
}, "user-participation": {
"largest-uploads": { id: -8,
"id": -9, name: "User Participation Statistics",
"name": "Top 50 Largest Uploads", description: "Detailed statistics for the most active users.",
"description": "sorted by file size." },
}, "largest-uploads": {
"inactive-users": { id: -9,
"id": -10, name: "Top 50 Largest Uploads",
"name": "Inactive Users with no posts", description: "sorted by file size.",
"description": "analyze pre-Discourse signups." },
}, "inactive-users": {
"active-lurkers": { id: -10,
"id": -11, name: "Inactive Users with no posts",
"name": "Most Active Lurkers", description: "analyze pre-Discourse signups.",
"description": "active users without posts and excessive read times, it accepts a post_read_count parameter that sets the threshold for posts read." },
}, "active-lurkers": {
"topic-user-notification-level": { id: -11,
"id": -12, name: "Most Active Lurkers",
"name": "List of topics a user is watching/tracking/muted", description:
"description": "The query requires a notification_level parameter. Use 0 for muted, 1 for regular, 2 for tracked and 3 for watched topics." "active users without posts and excessive read times, it accepts a post_read_count parameter that sets the threshold for posts read.",
}, },
"assigned-topics-report": { "topic-user-notification-level": {
"id": -13, id: -12,
"name": "List of assigned topics by user", name: "List of topics a user is watching/tracking/muted",
"description": "This report requires the assign plugin, it will find all assigned topics" description:
}, "The query requires a notification_level parameter. Use 0 for muted, 1 for regular, 2 for tracked and 3 for watched topics.",
"group-members-reply-count": { },
"id": -14, "assigned-topics-report": {
"name": "Group Members Reply Count", id: -13,
"description": "Number of replies by members of a group over a given time period. Requires 'group_name', 'start_date', and 'end_date' parameters. Dates need to be in the form 'yyyy-mm-dd'. Accepts an 'include_pms' parameter." name: "List of assigned topics by user",
}, description: "This report requires the assign plugin, it will find all assigned topics",
"total-assigned-topics-report": { },
"id": -15, "group-members-reply-count": {
"name": "Total topics assigned per user", id: -14,
"description": "Count of assigned topis per user linking to assign list" name: "Group Members Reply Count",
}, description:
"poll-results": { "Number of replies by members of a group over a given time period. Requires 'group_name', 'start_date', and 'end_date' parameters. Dates need to be in the form 'yyyy-mm-dd'. Accepts an 'include_pms' parameter.",
"id": -16, },
"name": "Poll results report", "total-assigned-topics-report": {
"description": "Details of a poll result, including details about each vote and voter, useful for analyzing results in external software." id: -15,
}, name: "Total topics assigned per user",
"top-tags-per-year": { description: "Count of assigned topis per user linking to assign list",
"id": -17, },
"name": "Top tags per year", "poll-results": {
"description": "List the top tags per year." id: -16,
}, name: "Poll results report",
"number_of_replies_by_category": { description:
"id": -18, "Details of a poll result, including details about each vote and voter, useful for analyzing results in external software.",
"name": "Number of replies by category", },
"description": "List the number of replies by category." "top-tags-per-year": {
} id: -17,
name: "Top tags per year",
description: "List the top tags per year.",
},
number_of_replies_by_category: {
id: -18,
name: "Number of replies by category",
description: "List the number of replies by category.",
},
}.with_indifferent_access }.with_indifferent_access
queries["most-common-likers"]["sql"] = <<~SQL queries["most-common-likers"]["sql"] = <<~SQL
@ -546,8 +554,8 @@ class Queries
ORDER BY p.year DESC, qt DESC ORDER BY p.year DESC, qt DESC
SQL SQL
# convert query ids from "mostcommonlikers" to "-1", "mostmessages" to "-2" etc. # convert query ids from "mostcommonlikers" to "-1", "mostmessages" to "-2" etc.
queries.transform_keys!.with_index { |key, idx| "-#{idx + 1}" } queries.transform_keys!.with_index { |key, idx| "-#{idx + 1}" }
queries queries
end end
end end

View File

@ -2,8 +2,8 @@
# rake data_explorer:list_hidden_queries # rake data_explorer:list_hidden_queries
desc "Shows a list of hidden queries" desc "Shows a list of hidden queries"
task('data_explorer:list_hidden_queries').clear task("data_explorer:list_hidden_queries").clear
task 'data_explorer:list_hidden_queries' => :environment do |t| task "data_explorer:list_hidden_queries" => :environment do |t|
puts "\nHidden Queries\n\n" puts "\nHidden Queries\n\n"
hidden_queries = DataExplorer::Query.where(hidden: false) hidden_queries = DataExplorer::Query.where(hidden: false)
@ -18,8 +18,8 @@ end
# rake data_explorer[-1] # rake data_explorer[-1]
# rake data_explorer[1,-2,3,-4,5] # rake data_explorer[1,-2,3,-4,5]
desc "Hides one or multiple queries by ID" desc "Hides one or multiple queries by ID"
task('data_explorer').clear task("data_explorer").clear
task 'data_explorer' => :environment do |t, args| task "data_explorer" => :environment do |t, args|
args.extras.each do |arg| args.extras.each do |arg|
id = arg.to_i id = arg.to_i
query = DataExplorer::Query.find_by(id: id) query = DataExplorer::Query.find_by(id: id)
@ -37,8 +37,8 @@ end
# rake data_explorer:unhide_query[-1] # rake data_explorer:unhide_query[-1]
# rake data_explorer:unhide_query[1,-2,3,-4,5] # rake data_explorer:unhide_query[1,-2,3,-4,5]
desc "Unhides one or multiple queries by ID" desc "Unhides one or multiple queries by ID"
task('data_explorer:unhide_query').clear task("data_explorer:unhide_query").clear
task 'data_explorer:unhide_query' => :environment do |t, args| task "data_explorer:unhide_query" => :environment do |t, args|
args.extras.each do |arg| args.extras.each do |arg|
id = arg.to_i id = arg.to_i
query = DataExplorer::Query.find_by(id: id) query = DataExplorer::Query.find_by(id: id)
@ -56,8 +56,8 @@ end
# rake data_explorer:hard_delete[-1] # rake data_explorer:hard_delete[-1]
# rake data_explorer:hard_delete[1,-2,3,-4,5] # rake data_explorer:hard_delete[1,-2,3,-4,5]
desc "Hard deletes one or multiple queries by ID" desc "Hard deletes one or multiple queries by ID"
task('data_explorer:hard_delete').clear task("data_explorer:hard_delete").clear
task 'data_explorer:hard_delete' => :environment do |t, args| task "data_explorer:hard_delete" => :environment do |t, args|
args.extras.each do |arg| args.extras.each do |arg|
id = arg.to_i id = arg.to_i
query = DataExplorer::Query.find_by(id: id) query = DataExplorer::Query.find_by(id: id)

View File

@ -1,9 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
desc 'Fix query IDs to match the old ones used in the plugin store (q:id)' desc "Fix query IDs to match the old ones used in the plugin store (q:id)"
task('data_explorer:fix_query_ids').clear task("data_explorer:fix_query_ids").clear
task 'data_explorer:fix_query_ids' => :environment do task "data_explorer:fix_query_ids" => :environment do
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
# Only queries with unique title can be fixed # Only queries with unique title can be fixed
movements = DB.query <<~SQL movements = DB.query <<~SQL
@ -20,7 +20,8 @@ task 'data_explorer:fix_query_ids' => :environment do
if movements.present? if movements.present?
# If there are new queries, they still may have conflict # If there are new queries, they still may have conflict
# We just want to move their ids to safe space and we will not move them back # We just want to move their ids to safe space and we will not move them back
additional_conflicts = DB.query(<<~SQL, from: movements.map { |m| m.from }, to: movements.map { |m| m.to }) additional_conflicts =
DB.query(<<~SQL, from: movements.map { |m| m.from }, to: movements.map { |m| m.to })
SELECT id FROM data_explorer_queries SELECT id FROM data_explorer_queries
WHERE id IN (:to) WHERE id IN (:to)
AND id NOT IN (:from) AND id NOT IN (:from)
@ -84,7 +85,8 @@ task 'data_explorer:fix_query_ids' => :environment do
SQL SQL
# insert additional_conflicts to temporary tables # insert additional_conflicts to temporary tables
new_id = DB.query("select greatest(max(id), 1) from tmp_data_explorer_queries").first.greatest + 1 new_id =
DB.query("select greatest(max(id), 1) from tmp_data_explorer_queries").first.greatest + 1
additional_conflicts.each do |conflict_id| additional_conflicts.each do |conflict_id|
DB.exec <<-SQL DB.exec <<-SQL
INSERT INTO tmp_data_explorer_queries(id, name, description, sql, user_id, last_run_at, hidden, created_at, updated_at) INSERT INTO tmp_data_explorer_queries(id, name, description, sql, user_id, last_run_at, hidden, created_at, updated_at)

814
plugin.rb

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
describe DataExplorer do describe DataExplorer do
describe '.run_query' do describe ".run_query" do
fab!(:topic) { Fabricate(:topic) } fab!(:topic) { Fabricate(:topic) }
it 'should run a query that includes PG template patterns' do it "should run a query that includes PG template patterns" do
sql = <<~SQL sql = <<~SQL
WITH query AS ( WITH query AS (
SELECT TO_CHAR(created_at, 'yyyy:mm:dd') AS date FROM topics SELECT TO_CHAR(created_at, 'yyyy:mm:dd') AS date FROM topics
@ -19,7 +19,7 @@ describe DataExplorer do
expect(result[:pg_result][0]["date"]).to eq(topic.created_at.strftime("%Y:%m:%d")) expect(result[:pg_result][0]["date"]).to eq(topic.created_at.strftime("%Y:%m:%d"))
end end
it 'should run a query containing a question mark in the comment' do it "should run a query containing a question mark in the comment" do
sql = <<~SQL sql = <<~SQL
WITH query AS ( WITH query AS (
SELECT id FROM topics -- some SQL ? comment ? SELECT id FROM topics -- some SQL ? comment ?
@ -34,7 +34,7 @@ describe DataExplorer do
expect(result[:pg_result][0]["id"]).to eq(topic.id) expect(result[:pg_result][0]["id"]).to eq(topic.id)
end end
it 'can run a query with params interpolation' do it "can run a query with params interpolation" do
topic2 = Fabricate(:topic) topic2 = Fabricate(:topic)
sql = <<~SQL sql = <<~SQL

View File

@ -1,16 +1,18 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe Guardian do describe Guardian do
before { SiteSetting.data_explorer_enabled = true } before { SiteSetting.data_explorer_enabled = true }
def make_query(group_ids = []) def make_query(group_ids = [])
query = DataExplorer::Query.create!(name: "Query number #{Fabrication::Sequencer.sequence("query-id", 1)}", sql: "SELECT 1") query =
DataExplorer::Query.create!(
name: "Query number #{Fabrication::Sequencer.sequence("query-id", 1)}",
sql: "SELECT 1",
)
group_ids.each do |group_id| group_ids.each { |group_id| query.query_groups.create!(group_id: group_id) }
query.query_groups.create!(group_id: group_id)
end
query query
end end

View File

@ -1,11 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe 'API keys scoped to query#run' do describe "API keys scoped to query#run" do
before do before { SiteSetting.data_explorer_enabled = true }
SiteSetting.data_explorer_enabled = true
end
fab!(:query1) { DataExplorer::Query.create!(name: "Query 1", sql: "SELECT 1 AS query1_res") } fab!(:query1) { DataExplorer::Query.create!(name: "Query 1", sql: "SELECT 1 AS query1_res") }
fab!(:query2) { DataExplorer::Query.create!(name: "Query 2", sql: "SELECT 1 AS query2_res") } fab!(:query2) { DataExplorer::Query.create!(name: "Query 2", sql: "SELECT 1 AS query2_res") }
@ -13,11 +11,7 @@ describe 'API keys scoped to query#run' do
let(:all_queries_api_key) do let(:all_queries_api_key) do
key = ApiKey.create! key = ApiKey.create!
ApiKeyScope.create!( ApiKeyScope.create!(resource: "data_explorer", action: "run_queries", api_key_id: key.id)
resource: "data_explorer",
action: "run_queries",
api_key_id: key.id
)
key key
end end
@ -27,73 +21,83 @@ describe 'API keys scoped to query#run' do
resource: "data_explorer", resource: "data_explorer",
action: "run_queries", action: "run_queries",
api_key_id: key.id, api_key_id: key.id,
allowed_parameters: { "id" => [query1.id.to_s] } allowed_parameters: {
"id" => [query1.id.to_s],
},
) )
key key
end end
it 'cannot hit any other endpoints' do it "cannot hit any other endpoints" do
get "/latest.json", headers: { get "/latest.json",
"Api-Key" => all_queries_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => all_queries_api_key.key,
} "Api-Username" => admin.username,
}
expect(response.status).to eq(403) expect(response.status).to eq(403)
get "/latest.json", headers: { get "/latest.json",
"Api-Key" => single_query_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => single_query_api_key.key,
} "Api-Username" => admin.username,
}
expect(response.status).to eq(403) expect(response.status).to eq(403)
get "/u/#{admin.username}.json", headers: { get "/u/#{admin.username}.json",
"Api-Key" => all_queries_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => all_queries_api_key.key,
} "Api-Username" => admin.username,
}
expect(response.status).to eq(403) expect(response.status).to eq(403)
get "/u/#{admin.username}.json", headers: { get "/u/#{admin.username}.json",
"Api-Key" => single_query_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => single_query_api_key.key,
} "Api-Username" => admin.username,
}
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
it "can only run the queries they're allowed to run" do it "can only run the queries they're allowed to run" do
expect { expect {
post "/admin/plugins/explorer/queries/#{query1.id}/run.json", headers: { post "/admin/plugins/explorer/queries/#{query1.id}/run.json",
"Api-Key" => single_query_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => single_query_api_key.key,
} "Api-Username" => admin.username,
}
}.to change { query1.reload.last_run_at } }.to change { query1.reload.last_run_at }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body["success"]).to eq(true) expect(response.parsed_body["success"]).to eq(true)
expect(response.parsed_body["columns"]).to eq(["query1_res"]) expect(response.parsed_body["columns"]).to eq(["query1_res"])
expect { expect {
post "/admin/plugins/explorer/queries/#{query2.id}/run.json", headers: { post "/admin/plugins/explorer/queries/#{query2.id}/run.json",
"Api-Key" => single_query_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => single_query_api_key.key,
} "Api-Username" => admin.username,
}
}.not_to change { query2.reload.last_run_at } }.not_to change { query2.reload.last_run_at }
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
it "can run all queries if they're not restricted to any queries" do it "can run all queries if they're not restricted to any queries" do
expect { expect {
post "/admin/plugins/explorer/queries/#{query1.id}/run.json", headers: { post "/admin/plugins/explorer/queries/#{query1.id}/run.json",
"Api-Key" => all_queries_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => all_queries_api_key.key,
} "Api-Username" => admin.username,
}
}.to change { query1.reload.last_run_at } }.to change { query1.reload.last_run_at }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body["success"]).to eq(true) expect(response.parsed_body["success"]).to eq(true)
expect(response.parsed_body["columns"]).to eq(["query1_res"]) expect(response.parsed_body["columns"]).to eq(["query1_res"])
expect { expect {
post "/admin/plugins/explorer/queries/#{query2.id}/run.json", headers: { post "/admin/plugins/explorer/queries/#{query2.id}/run.json",
"Api-Key" => all_queries_api_key.key, headers: {
"Api-Username" => admin.username "Api-Key" => all_queries_api_key.key,
} "Api-Username" => admin.username,
}
}.to change { query2.reload.last_run_at } }.to change { query2.reload.last_run_at }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.parsed_body["success"]).to eq(true) expect(response.parsed_body["success"]).to eq(true)

View File

@ -9,12 +9,60 @@ describe Jobs::DeleteHiddenQueries do
end end
it "will correctly destroy old hidden queries" do it "will correctly destroy old hidden queries" do
DataExplorer::Query.create!(id: 1, name: "A", description: "A description for A", sql: "SELECT 1 as value", hidden: false, last_run_at: 2.days.ago, updated_at: 2.days.ago) DataExplorer::Query.create!(
DataExplorer::Query.create!(id: 2, name: "B", description: "A description for B", sql: "SELECT 1 as value", hidden: true, last_run_at: 8.days.ago, updated_at: 8.days.ago) id: 1,
DataExplorer::Query.create!(id: 3, name: "C", description: "A description for C", sql: "SELECT 1 as value", hidden: true, last_run_at: 4.days.ago, updated_at: 4.days.ago) name: "A",
DataExplorer::Query.create!(id: 4, name: "D", description: "A description for D", sql: "SELECT 1 as value", hidden: true, last_run_at: nil, updated_at: 10.days.ago) description: "A description for A",
DataExplorer::Query.create!(id: 5, name: "E", description: "A description for E", sql: "SELECT 1 as value", hidden: true, last_run_at: 5.days.ago, updated_at: 10.days.ago) sql: "SELECT 1 as value",
DataExplorer::Query.create!(id: 6, name: "F", description: "A description for F", sql: "SELECT 1 as value", hidden: true, last_run_at: 10.days.ago, updated_at: 5.days.ago) hidden: false,
last_run_at: 2.days.ago,
updated_at: 2.days.ago,
)
DataExplorer::Query.create!(
id: 2,
name: "B",
description: "A description for B",
sql: "SELECT 1 as value",
hidden: true,
last_run_at: 8.days.ago,
updated_at: 8.days.ago,
)
DataExplorer::Query.create!(
id: 3,
name: "C",
description: "A description for C",
sql: "SELECT 1 as value",
hidden: true,
last_run_at: 4.days.ago,
updated_at: 4.days.ago,
)
DataExplorer::Query.create!(
id: 4,
name: "D",
description: "A description for D",
sql: "SELECT 1 as value",
hidden: true,
last_run_at: nil,
updated_at: 10.days.ago,
)
DataExplorer::Query.create!(
id: 5,
name: "E",
description: "A description for E",
sql: "SELECT 1 as value",
hidden: true,
last_run_at: 5.days.ago,
updated_at: 10.days.ago,
)
DataExplorer::Query.create!(
id: 6,
name: "F",
description: "A description for F",
sql: "SELECT 1 as value",
hidden: true,
last_run_at: 10.days.ago,
updated_at: 5.days.ago,
)
subject.execute(nil) subject.execute(nil)
expect(DataExplorer::Query.all.length).to eq(4) expect(DataExplorer::Query.all.length).to eq(4)

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe DataExplorerQueryGroupBookmarkable do describe DataExplorerQueryGroupBookmarkable do
fab!(:admin_user) { Fabricate(:admin) } fab!(:admin_user) { Fabricate(:admin) }
@ -10,14 +10,24 @@ describe DataExplorerQueryGroupBookmarkable do
fab!(:group1) { Fabricate(:group) } fab!(:group1) { Fabricate(:group) }
fab!(:group2) { Fabricate(:group) } fab!(:group2) { Fabricate(:group) }
fab!(:group3) { Fabricate(:group) } fab!(:group3) { Fabricate(:group) }
fab!(:query1) { Fabricate(:query, name: "My First Query", fab!(:query1) do
description: "This is the description of my 1st query.", Fabricate(
sql: "Not really important", :query,
user: admin_user) } name: "My First Query",
fab!(:query2) { Fabricate(:query, name: "My Second Query", description: "This is the description of my 1st query.",
description: "This is my 2nd query's description.", sql: "Not really important",
sql: "Not really important", user: admin_user,
user: admin_user) } )
end
fab!(:query2) do
Fabricate(
:query,
name: "My Second Query",
description: "This is my 2nd query's description.",
sql: "Not really important",
user: admin_user,
)
end
before do before do
SiteSetting.data_explorer_enabled = true SiteSetting.data_explorer_enabled = true
@ -41,28 +51,40 @@ describe DataExplorerQueryGroupBookmarkable do
let!(:group_user3) { Fabricate(:group_user, user: user, group: group3) } let!(:group_user3) { Fabricate(:group_user, user: user, group: group3) }
# User bookmarked the same Query 1 twice, from different Groups (0 and 1) # User bookmarked the same Query 1 twice, from different Groups (0 and 1)
let!(:bookmark1) { Fabricate(:bookmark, user: user, let!(:bookmark1) do
bookmarkable: query_group1, Fabricate(:bookmark, user: user, bookmarkable: query_group1, name: "something i gotta do")
name: "something i gotta do") } end
let!(:bookmark2) { Fabricate(:bookmark, user: user, let!(:bookmark2) do
bookmarkable: query_group2, Fabricate(
name: "something else i have to do") } :bookmark,
user: user,
bookmarkable: query_group2,
name: "something else i have to do",
)
end
# User also bookmarked Query 2 from Group 1. # User also bookmarked Query 2 from Group 1.
let!(:bookmark3) { Fabricate(:bookmark, user: user, let!(:bookmark3) do
bookmarkable: query_group3, Fabricate(
name: "this is the other query I needed.") } :bookmark,
user: user,
bookmarkable: query_group3,
name: "this is the other query I needed.",
)
end
# User previously bookmarked Query 1 from Group 2, of which she is no longer a member. # User previously bookmarked Query 1 from Group 2, of which she is no longer a member.
let!(:bookmark4) { Fabricate(:bookmark, user: user, let!(:bookmark4) do
bookmarkable: query_group4, Fabricate(:bookmark, user: user, bookmarkable: query_group4, name: "something i gotta do also")
name: "something i gotta do also") } end
subject { RegisteredBookmarkable.new(DataExplorerQueryGroupBookmarkable) } subject { RegisteredBookmarkable.new(DataExplorerQueryGroupBookmarkable) }
describe "#perform_list_query" do describe "#perform_list_query" do
it "returns all the user's bookmarks" do it "returns all the user's bookmarks" do
expect(subject.perform_list_query(user, guardian).map(&:id)).to match_array([bookmark1.id, bookmark2.id, bookmark3.id]) expect(subject.perform_list_query(user, guardian).map(&:id)).to match_array(
[bookmark1.id, bookmark2.id, bookmark3.id],
)
end end
it "does not return bookmarks made from groups that the user is no longer a member of" do it "does not return bookmarks made from groups that the user is no longer a member of" do
@ -75,24 +97,32 @@ describe DataExplorerQueryGroupBookmarkable do
# bookmarks is now empty, because user is not a member of any Groups with permission to see the query # bookmarks is now empty, because user is not a member of any Groups with permission to see the query
expect(subject.perform_list_query(user, guardian)).to be_empty expect(subject.perform_list_query(user, guardian)).to be_empty
end end
end end
describe "#perform_search_query" do describe "#perform_search_query" do
before do before { SearchIndexer.enable }
SearchIndexer.enable
end
it "returns bookmarks that match by name" do it "returns bookmarks that match by name" do
ts_query = Search.ts_query(term: "gotta", ts_config: "simple") ts_query = Search.ts_query(term: "gotta", ts_config: "simple")
expect(subject.perform_search_query(subject.perform_list_query(user, guardian), "%gotta%", ts_query).map(&:id)).to match_array([bookmark1.id]) expect(
subject.perform_search_query(
subject.perform_list_query(user, guardian),
"%gotta%",
ts_query,
).map(&:id),
).to match_array([bookmark1.id])
end end
it "returns bookmarks that match by Query name" do it "returns bookmarks that match by Query name" do
ts_query = Search.ts_query(term: "First", ts_config: "simple") ts_query = Search.ts_query(term: "First", ts_config: "simple")
expect(subject.perform_search_query(subject.perform_list_query(user, guardian), "%First%", ts_query).map(&:id)).to match_array([bookmark1.id, bookmark2.id]) expect(
subject.perform_search_query(
subject.perform_list_query(user, guardian),
"%First%",
ts_query,
).map(&:id),
).to match_array([bookmark1.id, bookmark2.id])
end end
end end
describe "#can_send_reminder?" do describe "#can_send_reminder?" do
@ -106,17 +136,20 @@ describe DataExplorerQueryGroupBookmarkable do
describe "#reminder_handler" do describe "#reminder_handler" do
it "creates a notification for the user with the correct details" do it "creates a notification for the user with the correct details" do
expect { subject.send_reminder_notification(bookmark1) }.to change { Notification.count }.by(1) expect { subject.send_reminder_notification(bookmark1) }.to change { Notification.count }.by(
1,
)
notif = user.notifications.last notif = user.notifications.last
expect(notif.notification_type).to eq(Notification.types[:bookmark_reminder]) expect(notif.notification_type).to eq(Notification.types[:bookmark_reminder])
expect(notif.data).to eq( expect(notif.data).to eq(
{ {
title: bookmark1.bookmarkable.query.name, title: bookmark1.bookmarkable.query.name,
bookmarkable_url: "/g/#{bookmark1.bookmarkable.group.name}/reports/#{bookmark1.bookmarkable.query.id}", bookmarkable_url:
"/g/#{bookmark1.bookmarkable.group.name}/reports/#{bookmark1.bookmarkable.query.id}",
display_username: bookmark1.user.username, display_username: bookmark1.user.username,
bookmark_name: bookmark1.name, bookmark_name: bookmark1.name,
bookmark_id: bookmark1.id bookmark_id: bookmark1.id,
}.to_json }.to_json,
) )
end end
end end
@ -144,6 +177,5 @@ describe DataExplorerQueryGroupBookmarkable do
expect(subject.can_see?(guardian, bookmark4)).to eq(true) expect(subject.can_see?(guardian, bookmark4)).to eq(true)
end end
end end
end end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe "Data explorer group serializer additions" do describe "Data explorer group serializer additions" do
fab!(:group_user) { Fabricate(:user) } fab!(:group_user) { Fabricate(:user) }

View File

@ -1,37 +1,35 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe DataExplorer::QueryController do describe DataExplorer::QueryController do
def response_json def response_json
response.parsed_body response.parsed_body
end end
before do before { SiteSetting.data_explorer_enabled = true }
SiteSetting.data_explorer_enabled = true
end
def make_query(sql, opts = {}, group_ids = []) def make_query(sql, opts = {}, group_ids = [])
query = DataExplorer::Query.create!(name: opts[:name] || "Query number", description: "A description for query number", sql: sql, hidden: opts[:hidden] || false) query =
group_ids.each do |group_id| DataExplorer::Query.create!(
query.query_groups.create!(group_id: group_id) name: opts[:name] || "Query number",
end description: "A description for query number",
sql: sql,
hidden: opts[:hidden] || false,
)
group_ids.each { |group_id| query.query_groups.create!(group_id: group_id) }
query query
end end
describe "Admin" do describe "Admin" do
fab!(:admin) { Fabricate(:admin) } fab!(:admin) { Fabricate(:admin) }
before do before { sign_in(admin) }
sign_in(admin)
end
describe "when disabled" do describe "when disabled" do
before do before { SiteSetting.data_explorer_enabled = false }
SiteSetting.data_explorer_enabled = false
end
it 'denies every request' do it "denies every request" do
get "/admin/plugins/explorer/queries.json" get "/admin/plugins/explorer/queries.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
@ -41,9 +39,7 @@ describe DataExplorer::QueryController do
get "/admin/plugins/explorer/queries/3.json" get "/admin/plugins/explorer/queries/3.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
post "/admin/plugins/explorer/queries.json", params: { post "/admin/plugins/explorer/queries.json", params: { id: 3 }
id: 3
}
expect(response.status).to eq(404) expect(response.status).to eq(404)
post "/admin/plugins/explorer/queries/3/run.json" post "/admin/plugins/explorer/queries/3/run.json"
@ -62,28 +58,28 @@ describe DataExplorer::QueryController do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
get "/admin/plugins/explorer/queries.json" get "/admin/plugins/explorer/queries.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['queries'].count).to eq(Queries.default.count) expect(response_json["queries"].count).to eq(Queries.default.count)
end end
it "shows all available queries in alphabetical order" do it "shows all available queries in alphabetical order" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', name: 'B') make_query("SELECT 1 as value", name: "B")
make_query('SELECT 1 as value', name: 'A') make_query("SELECT 1 as value", name: "A")
get "/admin/plugins/explorer/queries.json" get "/admin/plugins/explorer/queries.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['queries'].length).to eq(Queries.default.count + 2) expect(response_json["queries"].length).to eq(Queries.default.count + 2)
expect(response_json['queries'][0]['name']).to eq('A') expect(response_json["queries"][0]["name"]).to eq("A")
expect(response_json['queries'][1]['name']).to eq('B') expect(response_json["queries"][1]["name"]).to eq("B")
end end
it "doesn't show hidden/deleted queries" do it "doesn't show hidden/deleted queries" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', name: 'A', hidden: false) make_query("SELECT 1 as value", name: "A", hidden: false)
make_query('SELECT 1 as value', name: 'B', hidden: true) make_query("SELECT 1 as value", name: "B", hidden: true)
make_query('SELECT 1 as value', name: 'C', hidden: true) make_query("SELECT 1 as value", name: "C", hidden: true)
get "/admin/plugins/explorer/queries.json" get "/admin/plugins/explorer/queries.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['queries'].length).to eq(Queries.default.count + 1) expect(response_json["queries"].length).to eq(Queries.default.count + 1)
end end
end end
@ -93,29 +89,32 @@ describe DataExplorer::QueryController do
it "allows group to access system query" do it "allows group to access system query" do
query = DataExplorer::Query.find(-4) query = DataExplorer::Query.find(-4)
put "/admin/plugins/explorer/queries/#{query.id}.json", params: { put "/admin/plugins/explorer/queries/#{query.id}.json",
"query" => { params: {
"name" => query.name, "query" => {
"description" => query.description, "name" => query.name,
"sql" => query.sql, "description" => query.description,
"user_id" => query.user_id, "sql" => query.sql,
"created_at" => query.created_at, "user_id" => query.user_id,
"group_ids" => [group2.id], "created_at" => query.created_at,
"last_run_at" => query.last_run_at "group_ids" => [group2.id],
}, "last_run_at" => query.last_run_at,
"id" => query.id } },
"id" => query.id,
}
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
it "returns a proper json error for invalid updates" do it "returns a proper json error for invalid updates" do
query = DataExplorer::Query.find(-4) query = DataExplorer::Query.find(-4)
put "/admin/plugins/explorer/queries/#{query.id}", params: { put "/admin/plugins/explorer/queries/#{query.id}",
"query" => { params: {
"name" => "", "query" => {
}, "name" => "",
"id" => query.id } },
"id" => query.id,
}
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to eq(["Name can't be blank"]) expect(response.parsed_body["errors"]).to eq(["Name can't be blank"])
@ -129,13 +128,13 @@ describe DataExplorer::QueryController do
end end
it "can run queries" do it "can run queries" do
query = make_query('SELECT 23 as my_value') query = make_query("SELECT 23 as my_value")
run_query query.id run_query query.id
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['success']).to eq(true) expect(response_json["success"]).to eq(true)
expect(response_json['errors']).to eq([]) expect(response_json["errors"]).to eq([])
expect(response_json['columns']).to eq(['my_value']) expect(response_json["columns"]).to eq(["my_value"])
expect(response_json['rows']).to eq([[23]]) expect(response_json["rows"]).to eq([[23]])
end end
it "can process parameters" do it "can process parameters" do
@ -147,24 +146,24 @@ describe DataExplorer::QueryController do
run_query query.id, foo: 23 run_query query.id, foo: 23
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['errors']).to eq([]) expect(response_json["errors"]).to eq([])
expect(response_json['success']).to eq(true) expect(response_json["success"]).to eq(true)
expect(response_json['columns']).to eq(['my_value']) expect(response_json["columns"]).to eq(["my_value"])
expect(response_json['rows']).to eq([[23]]) expect(response_json["rows"]).to eq([[23]])
run_query query.id run_query query.id
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['errors']).to eq([]) expect(response_json["errors"]).to eq([])
expect(response_json['success']).to eq(true) expect(response_json["success"]).to eq(true)
expect(response_json['columns']).to eq(['my_value']) expect(response_json["columns"]).to eq(["my_value"])
expect(response_json['rows']).to eq([[34]]) expect(response_json["rows"]).to eq([[34]])
# 2.3 is not an integer # 2.3 is not an integer
run_query query.id, foo: '2.3' run_query query.id, foo: "2.3"
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/ValidationError/) expect(response_json["errors"].first).to match(/ValidationError/)
end end
it "doesn't allow you to modify the database #1" do it "doesn't allow you to modify the database #1" do
@ -184,9 +183,9 @@ describe DataExplorer::QueryController do
# This test should fail on the below check. # This test should fail on the below check.
expect(p.cooked).to_not match(/winner/) expect(p.cooked).to_not match(/winner/)
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/read-only transaction/) expect(response_json["errors"].first).to match(/read-only transaction/)
end end
it "doesn't allow you to modify the database #2" do it "doesn't allow you to modify the database #2" do
@ -221,9 +220,9 @@ describe DataExplorer::QueryController do
# Afterwards, this test should fail on the below check. # Afterwards, this test should fail on the below check.
expect(p.cooked).to_not match(/winner/) expect(p.cooked).to_not match(/winner/)
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/semicolon/) expect(response_json["errors"].first).to match(/semicolon/)
end end
it "doesn't allow you to lock rows" do it "doesn't allow you to lock rows" do
@ -233,9 +232,9 @@ describe DataExplorer::QueryController do
run_query query.id run_query query.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/read-only transaction/) expect(response_json["errors"].first).to match(/read-only transaction/)
end end
it "doesn't allow you to create a table" do it "doesn't allow you to create a table" do
@ -245,9 +244,9 @@ describe DataExplorer::QueryController do
run_query query.id run_query query.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/read-only transaction|syntax error/) expect(response_json["errors"].first).to match(/read-only transaction|syntax error/)
end end
it "doesn't allow you to break the transaction" do it "doesn't allow you to break the transaction" do
@ -257,9 +256,9 @@ describe DataExplorer::QueryController do
run_query query.id run_query query.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/syntax error/) expect(response_json["errors"].first).to match(/syntax error/)
query.sql = <<~SQL query.sql = <<~SQL
) )
@ -267,9 +266,9 @@ describe DataExplorer::QueryController do
run_query query.id run_query query.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/syntax error/) expect(response_json["errors"].first).to match(/syntax error/)
query.sql = <<~SQL query.sql = <<~SQL
RELEASE SAVEPOINT active_record_1 RELEASE SAVEPOINT active_record_1
@ -277,13 +276,13 @@ describe DataExplorer::QueryController do
run_query query.id run_query query.id
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response_json['errors']).to_not eq([]) expect(response_json["errors"]).to_not eq([])
expect(response_json['success']).to eq(false) expect(response_json["success"]).to eq(false)
expect(response_json['errors'].first).to match(/syntax error/) expect(response_json["errors"].first).to match(/syntax error/)
end end
it "can export data in CSV format" do it "can export data in CSV format" do
query = make_query('SELECT 23 as my_value') query = make_query("SELECT 23 as my_value")
post "/admin/plugins/explorer/queries/#{query.id}/run.json", params: { download: 1 } post "/admin/plugins/explorer/queries/#{query.id}/run.json", params: { download: 1 }
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
@ -302,13 +301,13 @@ describe DataExplorer::QueryController do
SQL SQL
run_query query.id run_query query.id
expect(response_json['rows'].count).to eq(2) expect(response_json["rows"].count).to eq(2)
post "/admin/plugins/explorer/queries/#{query.id}/run.json", params: { limit: 1 } post "/admin/plugins/explorer/queries/#{query.id}/run.json", params: { limit: 1 }
expect(response_json['rows'].count).to eq(1) expect(response_json["rows"].count).to eq(1)
post "/admin/plugins/explorer/queries/#{query.id}/run.json", params: { limit: "ALL" } post "/admin/plugins/explorer/queries/#{query.id}/run.json", params: { limit: "ALL" }
expect(response_json['rows'].count).to eq(3) expect(response_json["rows"].count).to eq(3)
end end
it "should limit the results in CSV download" do it "should limit the results in CSV download" do
@ -324,11 +323,19 @@ describe DataExplorer::QueryController do
post "/admin/plugins/explorer/queries/#{query.id}/run.csv", params: { download: 1 } post "/admin/plugins/explorer/queries/#{query.id}/run.csv", params: { download: 1 }
expect(response.body.split("\n").count).to eq(3) expect(response.body.split("\n").count).to eq(3)
post "/admin/plugins/explorer/queries/#{query.id}/run.csv", params: { download: 1, limit: 1 } post "/admin/plugins/explorer/queries/#{query.id}/run.csv",
params: {
download: 1,
limit: 1,
}
expect(response.body.split("\n").count).to eq(2) expect(response.body.split("\n").count).to eq(2)
# The value `ALL` is not supported in csv exports. # The value `ALL` is not supported in csv exports.
post "/admin/plugins/explorer/queries/#{query.id}/run.csv", params: { download: 1, limit: "ALL" } post "/admin/plugins/explorer/queries/#{query.id}/run.csv",
params: {
download: 1,
limit: "ALL",
}
expect(response.body.split("\n").count).to eq(1) expect(response.body.split("\n").count).to eq(1)
ensure ensure
DataExplorer.send(:remove_const, "QUERY_RESULT_MAX_LIMIT") DataExplorer.send(:remove_const, "QUERY_RESULT_MAX_LIMIT")
@ -343,16 +350,12 @@ describe DataExplorer::QueryController do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
fab!(:group) { Fabricate(:group, users: [user]) } fab!(:group) { Fabricate(:group, users: [user]) }
before do before { sign_in(user) }
sign_in(user)
end
describe "when disabled" do describe "when disabled" do
before do before { SiteSetting.data_explorer_enabled = false }
SiteSetting.data_explorer_enabled = false
end
it 'denies every request' do it "denies every request" do
get "/g/1/reports.json" get "/g/1/reports.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
@ -365,7 +368,7 @@ describe DataExplorer::QueryController do
end end
it "cannot access admin endpoints" do it "cannot access admin endpoints" do
query = make_query('SELECT 1 as value') query = make_query("SELECT 1 as value")
post "/admin/plugins/explorer/queries/#{query.id}/run.json" post "/admin/plugins/explorer/queries/#{query.id}/run.json"
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
@ -373,12 +376,12 @@ describe DataExplorer::QueryController do
describe "#group_reports_index" do describe "#group_reports_index" do
it "only returns queries that the group has access to" do it "only returns queries that the group has access to" do
group.add(user) group.add(user)
make_query('SELECT 1 as value', { name: 'A' }, ["#{group.id}"]) make_query("SELECT 1 as value", { name: "A" }, ["#{group.id}"])
get "/g/#{group.name}/reports.json" get "/g/#{group.name}/reports.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['queries'].length).to eq(1) expect(response_json["queries"].length).to eq(1)
expect(response_json['queries'][0]['name']).to eq('A') expect(response_json["queries"][0]["name"]).to eq("A")
end end
it "returns a 404 when the user should not have access to the query " do it "returns a 404 when the user should not have access to the query " do
@ -398,19 +401,19 @@ describe DataExplorer::QueryController do
it "does not return hidden queries" do it "does not return hidden queries" do
group.add(user) group.add(user)
make_query('SELECT 1 as value', { name: 'A', hidden: true }, ["#{group.id}"]) make_query("SELECT 1 as value", { name: "A", hidden: true }, ["#{group.id}"])
make_query('SELECT 1 as value', { name: 'B' }, ["#{group.id}"]) make_query("SELECT 1 as value", { name: "B" }, ["#{group.id}"])
get "/g/#{group.name}/reports.json" get "/g/#{group.name}/reports.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response_json['queries'].length).to eq(1) expect(response_json["queries"].length).to eq(1)
expect(response_json['queries'][0]['name']).to eq('B') expect(response_json["queries"][0]["name"]).to eq("B")
end end
end end
describe "#group_reports_run" do describe "#group_reports_run" do
it "runs the query" do it "runs the query" do
query = make_query('SELECT 1828 as value', { name: 'B' }, ["#{group.id}"]) query = make_query("SELECT 1828 as value", { name: "B" }, ["#{group.id}"])
post "/g/#{group.name}/reports/#{query.id}/run.json" post "/g/#{group.name}/reports/#{query.id}/run.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
@ -421,7 +424,7 @@ describe DataExplorer::QueryController do
it "returns a 404 when the user should not have access to the query " do it "returns a 404 when the user should not have access to the query " do
group.add(user) group.add(user)
query = make_query('SELECT 1 as value', {}, []) query = make_query("SELECT 1 as value", {}, [])
post "/g/#{group.name}/reports/#{query.id}/run.json" post "/g/#{group.name}/reports/#{query.id}/run.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
@ -429,7 +432,7 @@ describe DataExplorer::QueryController do
it "return a 200 when the user has access the the query" do it "return a 200 when the user has access the the query" do
group.add(user) group.add(user)
query = make_query('SELECT 1 as value', {}, [group.id.to_s]) query = make_query("SELECT 1 as value", {}, [group.id.to_s])
post "/g/#{group.name}/reports/#{query.id}/run.json" post "/g/#{group.name}/reports/#{query.id}/run.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
@ -437,7 +440,7 @@ describe DataExplorer::QueryController do
it "return a 404 when the query is hidden" do it "return a 404 when the query is hidden" do
group.add(user) group.add(user)
query = make_query('SELECT 1 as value', { hidden: true }, [group.id.to_s]) query = make_query("SELECT 1 as value", { hidden: true }, [group.id.to_s])
post "/g/#{group.name}/reports/#{query.id}/run.json" post "/g/#{group.name}/reports/#{query.id}/run.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
@ -446,21 +449,21 @@ describe DataExplorer::QueryController do
describe "#group_reports_show" do describe "#group_reports_show" do
it "returns a 404 when the user should not have access to the query " do it "returns a 404 when the user should not have access to the query " do
query = make_query('SELECT 1 as value', {}, []) query = make_query("SELECT 1 as value", {}, [])
get "/g/#{group.name}/reports/#{query.id}.json" get "/g/#{group.name}/reports/#{query.id}.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "return a 200 when the user has access the the query" do it "return a 200 when the user has access the the query" do
query = make_query('SELECT 1 as value', {}, [group.id.to_s]) query = make_query("SELECT 1 as value", {}, [group.id.to_s])
get "/g/#{group.name}/reports/#{query.id}.json" get "/g/#{group.name}/reports/#{query.id}.json"
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
it "return a 404 when the query is hidden" do it "return a 404 when the query is hidden" do
query = make_query('SELECT 1 as value', { hidden: true }, [group.id.to_s]) query = make_query("SELECT 1 as value", { hidden: true }, [group.id.to_s])
get "/g/#{group.name}/reports/#{query.id}.json" get "/g/#{group.name}/reports/#{query.id}.json"
expect(response.status).to eq(404) expect(response.status).to eq(404)

View File

@ -1,18 +1,23 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe 'Data Explorer rake tasks' do describe "Data Explorer rake tasks" do
before do before do
Rake::Task.clear Rake::Task.clear
Discourse::Application.load_tasks Discourse::Application.load_tasks
end end
def make_query(sql, opts = {}, group_ids = []) def make_query(sql, opts = {}, group_ids = [])
query = DataExplorer::Query.create!(id: opts[:id], name: opts[:name] || "Query number", description: "A description for query number", sql: sql, hidden: opts[:hidden] || false) query =
group_ids.each do |group_id| DataExplorer::Query.create!(
query.query_groups.create!(group_id: group_id) id: opts[:id],
end name: opts[:name] || "Query number",
description: "A description for query number",
sql: sql,
hidden: opts[:hidden] || false,
)
group_ids.each { |group_id| query.query_groups.create!(group_id: group_id) }
query query
end end
@ -20,13 +25,13 @@ describe 'Data Explorer rake tasks' do
DataExplorer::Query.where(hidden: true).order(:id) DataExplorer::Query.where(hidden: true).order(:id)
end end
describe 'data_explorer' do describe "data_explorer" do
it 'hides a single query' do it "hides a single query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A') make_query("SELECT 1 as value", id: 1, name: "A")
make_query('SELECT 1 as value', id: 2, name: 'B') make_query("SELECT 1 as value", id: 2, name: "B")
# rake data_explorer[1] => hide query with ID 1 # rake data_explorer[1] => hide query with ID 1
silence_stdout { Rake::Task['data_explorer'].invoke(1) } silence_stdout { Rake::Task["data_explorer"].invoke(1) }
# Soft deletion: PluginStoreRow should not be modified # Soft deletion: PluginStoreRow should not be modified
expect(DataExplorer::Query.all.length).to eq(2) expect(DataExplorer::Query.all.length).to eq(2)
@ -36,14 +41,14 @@ describe 'Data Explorer rake tasks' do
expect(hidden_queries[0].id).to eq(1) expect(hidden_queries[0].id).to eq(1)
end end
it 'hides multiple queries' do it "hides multiple queries" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A') make_query("SELECT 1 as value", id: 1, name: "A")
make_query('SELECT 1 as value', id: 2, name: 'B') make_query("SELECT 1 as value", id: 2, name: "B")
make_query('SELECT 1 as value', id: 3, name: 'C') make_query("SELECT 1 as value", id: 3, name: "C")
make_query('SELECT 1 as value', id: 4, name: 'D') make_query("SELECT 1 as value", id: 4, name: "D")
# rake data_explorer[1,2,4] => hide queries with IDs 1, 2 and 4 # rake data_explorer[1,2,4] => hide queries with IDs 1, 2 and 4
silence_stdout { Rake::Task['data_explorer'].invoke(1, 2, 4) } silence_stdout { Rake::Task["data_explorer"].invoke(1, 2, 4) }
# Soft deletion: PluginStoreRow should not be modified # Soft deletion: PluginStoreRow should not be modified
expect(DataExplorer::Query.all.length).to eq(4) expect(DataExplorer::Query.all.length).to eq(4)
@ -55,15 +60,15 @@ describe 'Data Explorer rake tasks' do
expect(hidden_queries[2].id).to eq(4) expect(hidden_queries[2].id).to eq(4)
end end
context 'when query does not exist in PluginStore' do context "when query does not exist in PluginStore" do
it 'should not hide the query' do it "should not hide the query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A') make_query("SELECT 1 as value", id: 1, name: "A")
make_query('SELECT 1 as value', id: 2, name: 'B') make_query("SELECT 1 as value", id: 2, name: "B")
# rake data_explorer[3] => try to hide query with ID 3 # rake data_explorer[3] => try to hide query with ID 3
silence_stdout { Rake::Task['data_explorer'].invoke(3) } silence_stdout { Rake::Task["data_explorer"].invoke(3) }
# rake data_explorer[3,4,5] => try to hide queries with IDs 3, 4 and 5 # rake data_explorer[3,4,5] => try to hide queries with IDs 3, 4 and 5
silence_stdout { Rake::Task['data_explorer'].invoke(3, 4, 5) } silence_stdout { Rake::Task["data_explorer"].invoke(3, 4, 5) }
# Array of hidden queries should be empty # Array of hidden queries should be empty
expect(hidden_queries.length).to eq(0) expect(hidden_queries.length).to eq(0)
@ -71,13 +76,13 @@ describe 'Data Explorer rake tasks' do
end end
end end
describe '#unhide_query' do describe "#unhide_query" do
it 'unhides a single query' do it "unhides a single query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A', hidden: true) make_query("SELECT 1 as value", id: 1, name: "A", hidden: true)
make_query('SELECT 1 as value', id: 2, name: 'B', hidden: true) make_query("SELECT 1 as value", id: 2, name: "B", hidden: true)
# rake data_explorer:unhide_query[1] => unhide query with ID 1 # rake data_explorer:unhide_query[1] => unhide query with ID 1
silence_stdout { Rake::Task['data_explorer:unhide_query'].invoke(1) } silence_stdout { Rake::Task["data_explorer:unhide_query"].invoke(1) }
# Soft deletion: PluginStoreRow should not be modified # Soft deletion: PluginStoreRow should not be modified
expect(DataExplorer::Query.all.length).to eq(2) expect(DataExplorer::Query.all.length).to eq(2)
@ -87,14 +92,14 @@ describe 'Data Explorer rake tasks' do
expect(hidden_queries[0].id).to eq(2) expect(hidden_queries[0].id).to eq(2)
end end
it 'unhides multiple queries' do it "unhides multiple queries" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A', hidden: true) make_query("SELECT 1 as value", id: 1, name: "A", hidden: true)
make_query('SELECT 1 as value', id: 2, name: 'B', hidden: true) make_query("SELECT 1 as value", id: 2, name: "B", hidden: true)
make_query('SELECT 1 as value', id: 3, name: 'C', hidden: true) make_query("SELECT 1 as value", id: 3, name: "C", hidden: true)
make_query('SELECT 1 as value', id: 4, name: 'D', hidden: true) make_query("SELECT 1 as value", id: 4, name: "D", hidden: true)
# rake data_explorer:unhide_query[1,2,4] => unhide queries with IDs 1, 2 and 4 # rake data_explorer:unhide_query[1,2,4] => unhide queries with IDs 1, 2 and 4
silence_stdout { Rake::Task['data_explorer:unhide_query'].invoke(1, 2, 4) } silence_stdout { Rake::Task["data_explorer:unhide_query"].invoke(1, 2, 4) }
# Soft deletion: PluginStoreRow should not be modified # Soft deletion: PluginStoreRow should not be modified
expect(DataExplorer::Query.all.length).to eq(4) expect(DataExplorer::Query.all.length).to eq(4)
@ -104,15 +109,15 @@ describe 'Data Explorer rake tasks' do
expect(hidden_queries[0].id).to eq(3) expect(hidden_queries[0].id).to eq(3)
end end
context 'when query does not exist in PluginStore' do context "when query does not exist in PluginStore" do
it 'should not unhide the query' do it "should not unhide the query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A', hidden: true) make_query("SELECT 1 as value", id: 1, name: "A", hidden: true)
make_query('SELECT 1 as value', id: 2, name: 'B', hidden: true) make_query("SELECT 1 as value", id: 2, name: "B", hidden: true)
# rake data_explorer:unhide_query[3] => try to unhide query with ID 3 # rake data_explorer:unhide_query[3] => try to unhide query with ID 3
silence_stdout { Rake::Task['data_explorer:unhide_query'].invoke(3) } silence_stdout { Rake::Task["data_explorer:unhide_query"].invoke(3) }
# rake data_explorer:unhide_query[3,4,5] => try to unhide queries with IDs 3, 4 and 5 # rake data_explorer:unhide_query[3,4,5] => try to unhide queries with IDs 3, 4 and 5
silence_stdout { Rake::Task['data_explorer:unhide_query'].invoke(3, 4, 5) } silence_stdout { Rake::Task["data_explorer:unhide_query"].invoke(3, 4, 5) }
# Array of hidden queries shouldn't change # Array of hidden queries shouldn't change
expect(hidden_queries.length).to eq(2) expect(hidden_queries.length).to eq(2)
@ -120,13 +125,13 @@ describe 'Data Explorer rake tasks' do
end end
end end
describe '#hard_delete' do describe "#hard_delete" do
it 'hard deletes a single query' do it "hard deletes a single query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A', hidden: true) make_query("SELECT 1 as value", id: 1, name: "A", hidden: true)
make_query('SELECT 1 as value', id: 2, name: 'B', hidden: true) make_query("SELECT 1 as value", id: 2, name: "B", hidden: true)
# rake data_explorer:hard_delete[1] => hard delete query with ID 1 # rake data_explorer:hard_delete[1] => hard delete query with ID 1
silence_stdout { Rake::Task['data_explorer:hard_delete'].invoke(1) } silence_stdout { Rake::Task["data_explorer:hard_delete"].invoke(1) }
# Hard deletion: query list should be shorter by 1 # Hard deletion: query list should be shorter by 1
expect(DataExplorer::Query.all.length).to eq(1) expect(DataExplorer::Query.all.length).to eq(1)
@ -136,14 +141,14 @@ describe 'Data Explorer rake tasks' do
expect(hidden_queries[0].id).to eq(2) expect(hidden_queries[0].id).to eq(2)
end end
it 'hard deletes multiple queries' do it "hard deletes multiple queries" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A', hidden: true) make_query("SELECT 1 as value", id: 1, name: "A", hidden: true)
make_query('SELECT 1 as value', id: 2, name: 'B', hidden: true) make_query("SELECT 1 as value", id: 2, name: "B", hidden: true)
make_query('SELECT 1 as value', id: 3, name: 'C', hidden: true) make_query("SELECT 1 as value", id: 3, name: "C", hidden: true)
make_query('SELECT 1 as value', id: 4, name: 'D', hidden: true) make_query("SELECT 1 as value", id: 4, name: "D", hidden: true)
# rake data_explorer:hard_delete[1,2,4] => hard delete queries with IDs 1, 2 and 4 # rake data_explorer:hard_delete[1,2,4] => hard delete queries with IDs 1, 2 and 4
silence_stdout { Rake::Task['data_explorer:hard_delete'].invoke(1, 2, 4) } silence_stdout { Rake::Task["data_explorer:hard_delete"].invoke(1, 2, 4) }
# Hard deletion: query list should be shorter by 3 # Hard deletion: query list should be shorter by 3
expect(DataExplorer::Query.all.length).to eq(1) expect(DataExplorer::Query.all.length).to eq(1)
@ -153,27 +158,27 @@ describe 'Data Explorer rake tasks' do
expect(hidden_queries[0].id).to eq(3) expect(hidden_queries[0].id).to eq(3)
end end
context 'when query does not exist in PluginStore' do context "when query does not exist in PluginStore" do
it 'should not hard delete the query' do it "should not hard delete the query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A', hidden: true) make_query("SELECT 1 as value", id: 1, name: "A", hidden: true)
make_query('SELECT 1 as value', id: 2, name: 'B', hidden: true) make_query("SELECT 1 as value", id: 2, name: "B", hidden: true)
# rake data_explorer:hard_delete[3] => try to hard delete query with ID 3 # rake data_explorer:hard_delete[3] => try to hard delete query with ID 3
silence_stdout { Rake::Task['data_explorer:hard_delete'].invoke(3) } silence_stdout { Rake::Task["data_explorer:hard_delete"].invoke(3) }
# rake data_explorer:hard_delete[3,4,5] => try to hard delete queries with IDs 3, 4 and 5 # rake data_explorer:hard_delete[3,4,5] => try to hard delete queries with IDs 3, 4 and 5
silence_stdout { Rake::Task['data_explorer:hard_delete'].invoke(3, 4, 5) } silence_stdout { Rake::Task["data_explorer:hard_delete"].invoke(3, 4, 5) }
# Array of hidden queries shouldn't change # Array of hidden queries shouldn't change
expect(hidden_queries.length).to eq(2) expect(hidden_queries.length).to eq(2)
end end
end end
context 'when query is not hidden' do context "when query is not hidden" do
it 'should not hard delete the query' do it "should not hard delete the query" do
DataExplorer::Query.destroy_all DataExplorer::Query.destroy_all
make_query('SELECT 1 as value', id: 1, name: 'A') make_query("SELECT 1 as value", id: 1, name: "A")
# rake data_explorer:hard_delete[1] => try to hard delete query with ID 1 # rake data_explorer:hard_delete[1] => try to hard delete query with ID 1
silence_stdout { Rake::Task['data_explorer:hard_delete'].invoke(1) } silence_stdout { Rake::Task["data_explorer:hard_delete"].invoke(1) }
# List of queries shouldn't change # List of queries shouldn't change
expect(DataExplorer::Query.all.length).to eq(1) expect(DataExplorer::Query.all.length).to eq(1)

View File

@ -1,16 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require "rails_helper"
describe 'fix query ids rake task' do describe "fix query ids rake task" do
before do before do
Rake::Task.clear Rake::Task.clear
Discourse::Application.load_tasks Discourse::Application.load_tasks
end end
let(:query_name) { 'Awesome query' } let(:query_name) { "Awesome query" }
it 'fixes the ID of the query if they share the same name' do it "fixes the ID of the query if they share the same name" do
original_query_id = 4 original_query_id = 4
create_plugin_store_row(query_name, original_query_id) create_plugin_store_row(query_name, original_query_id)
create_query(query_name) create_query(query_name)
@ -20,7 +20,7 @@ describe 'fix query ids rake task' do
expect(find(query_name).id).to eq(original_query_id) expect(find(query_name).id).to eq(original_query_id)
end end
it 'only fixes queries with unique name' do it "only fixes queries with unique name" do
original_query_id = 4 original_query_id = 4
create_plugin_store_row(query_name, original_query_id) create_plugin_store_row(query_name, original_query_id)
create_query(query_name) create_query(query_name)
@ -31,7 +31,7 @@ describe 'fix query ids rake task' do
expect(find(query_name).id).not_to eq(original_query_id) expect(find(query_name).id).not_to eq(original_query_id)
end end
it 'skips queries that already have the same ID' do it "skips queries that already have the same ID" do
db_query = create_query(query_name) db_query = create_query(query_name)
last_updated_at = db_query.updated_at last_updated_at = db_query.updated_at
create_plugin_store_row(query_name, db_query.id) create_plugin_store_row(query_name, db_query.id)
@ -41,9 +41,9 @@ describe 'fix query ids rake task' do
expect(find(query_name).updated_at).to eq_time(last_updated_at) expect(find(query_name).updated_at).to eq_time(last_updated_at)
end end
it 'keeps queries the rest of the queries' do it "keeps queries the rest of the queries" do
original_query_id = 4 original_query_id = 4
different_query_name = 'Another query' different_query_name = "Another query"
create_plugin_store_row(query_name, original_query_id) create_plugin_store_row(query_name, original_query_id)
create_query(query_name) create_query(query_name)
create_query(different_query_name) create_query(different_query_name)
@ -53,8 +53,8 @@ describe 'fix query ids rake task' do
expect(find(different_query_name)).not_to be_nil expect(find(different_query_name)).not_to be_nil
end end
it 'works even if they are additional conflicts' do it "works even if they are additional conflicts" do
different_query_name = 'Another query' different_query_name = "Another query"
additional_conflict = create_query(different_query_name) additional_conflict = create_query(different_query_name)
create_query(query_name) create_query(query_name)
create_plugin_store_row(query_name, additional_conflict.id) create_plugin_store_row(query_name, additional_conflict.id)
@ -65,7 +65,7 @@ describe 'fix query ids rake task' do
expect(find(query_name).id).to eq(additional_conflict.id) expect(find(query_name).id).to eq(additional_conflict.id)
end end
describe 'query groups' do describe "query groups" do
let(:group) { Fabricate(:group) } let(:group) { Fabricate(:group) }
it "fixes the query group's query_id" do it "fixes the query group's query_id" do
@ -78,8 +78,8 @@ describe 'fix query ids rake task' do
expect(find_query_group(original_query_id)).not_to be_nil expect(find_query_group(original_query_id)).not_to be_nil
end end
it 'works with additional conflicts' do it "works with additional conflicts" do
different_query_name = 'Another query' different_query_name = "Another query"
additional_conflict = create_query(different_query_name, [group.id]) additional_conflict = create_query(different_query_name, [group.id])
create_query(query_name, [group.id]) create_query(query_name, [group.id])
create_plugin_store_row(query_name, additional_conflict.id, [group.id]) create_plugin_store_row(query_name, additional_conflict.id, [group.id])
@ -98,7 +98,7 @@ describe 'fix query ids rake task' do
end end
end end
it 'changes the serial sequence for future queries' do it "changes the serial sequence for future queries" do
original_query_id = 4 original_query_id = 4
create_plugin_store_row(query_name, original_query_id) create_plugin_store_row(query_name, original_query_id)
create_query(query_name) create_query(query_name)
@ -110,7 +110,7 @@ describe 'fix query ids rake task' do
end end
def run_task def run_task
Rake::Task['data_explorer:fix_query_ids'].invoke Rake::Task["data_explorer:fix_query_ids"].invoke
end end
def create_plugin_store_row(name, id, group_ids = []) def create_plugin_store_row(name, id, group_ids = [])
@ -119,27 +119,25 @@ describe 'fix query ids rake task' do
PluginStore.set( PluginStore.set(
DataExplorer.plugin_name, DataExplorer.plugin_name,
key, key,
attributes(name).merge(group_ids: group_ids, id: id) attributes(name).merge(group_ids: group_ids, id: id),
) )
end end
def create_query(name, group_ids = []) def create_query(name, group_ids = [])
DataExplorer::Query.create!(attributes(name)).tap do |query| DataExplorer::Query
group_ids.each do |group_id| .create!(attributes(name))
query.query_groups.create!(group_id: group_id) .tap { |query| group_ids.each { |group_id| query.query_groups.create!(group_id: group_id) } }
end
end
end end
def attributes(name) def attributes(name)
{ {
id: DataExplorer::Query.count == 0 ? 5 : DataExplorer::Query.maximum(:id) + 1, id: DataExplorer::Query.count == 0 ? 5 : DataExplorer::Query.maximum(:id) + 1,
name: name, name: name,
description: 'A Query', description: "A Query",
sql: "SELECT 1", sql: "SELECT 1",
created_at: 3.hours.ago, created_at: 3.hours.ago,
last_run_at: 1.hour.ago, last_run_at: 1.hour.ago,
hidden: false hidden: false,
} }
end end