2023-12-01 16:47:54 +01:00
import { click , visit } from "@ember/test-helpers" ;
import { test } from "qunit" ;
2025-02-14 10:11:32 +11:00
import sinon from "sinon" ;
2024-08-28 13:51:37 +02:00
import { acceptance } from "discourse/tests/helpers/qunit-helpers" ;
2025-02-06 16:45:00 +00:00
import { i18n } from "discourse-i18n" ;
2021-02-18 15:06:22 +04:00
acceptance ( "Data Explorer Plugin | Run Query" , function ( needs ) {
needs . user ( ) ;
needs . settings ( { data _explorer _enabled : true } ) ;
2025-02-14 10:11:32 +11:00
needs . hooks . beforeEach ( ( ) => {
sinon . stub ( window , "open" ) ;
} ) ;
needs . hooks . afterEach ( ( ) => {
window . open . restore ( ) ;
} ) ;
2021-02-18 15:06:22 +04:00
needs . pretender ( ( server , helper ) => {
server . get ( "/admin/plugins/explorer/groups.json" , ( ) => {
return helper . response ( [
{
id : 1 ,
name : "admins" ,
} ,
{
id : 2 ,
name : "moderators" ,
} ,
{
id : 3 ,
name : "staff" ,
} ,
{
id : 0 ,
name : "everyone" ,
} ,
{
id : 10 ,
name : "trust_level_0" ,
} ,
{
id : 11 ,
name : "trust_level_1" ,
} ,
{
id : 12 ,
name : "trust_level_2" ,
} ,
{
id : 13 ,
name : "trust_level_3" ,
} ,
{
id : 14 ,
name : "trust_level_4" ,
} ,
] ) ;
} ) ;
server . get ( "/admin/plugins/explorer/schema.json" , ( ) => {
return helper . response ( {
anonymous _users : [
{
column _name : "id" ,
data _type : "serial" ,
primary : true ,
} ,
{
column _name : "user_id" ,
data _type : "integer" ,
fkey _info : "users" ,
} ,
{
column _name : "master_user_id" ,
data _type : "integer" ,
fkey _info : "users" ,
} ,
{
column _name : "active" ,
data _type : "boolean" ,
} ,
{
column _name : "created_at" ,
data _type : "timestamp" ,
} ,
{
column _name : "updated_at" ,
data _type : "timestamp" ,
} ,
] ,
} ) ;
} ) ;
server . get ( "/admin/plugins/explorer/queries" , ( ) => {
return helper . response ( {
queries : [
{
id : - 6 ,
name : "Top 100 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." ,
username : "system" ,
group _ids : [ ] ,
last _run _at : "2021-02-11T08:29:59.337Z" ,
user _id : - 1 ,
} ,
2023-05-04 19:45:43 -03:00
{
id : 2 ,
name : "What about 0?" ,
description : "" ,
username : "system" ,
group _ids : [ ] ,
last _run _at : "2023-05-04T22:16:23.858Z" ,
user _id : 1 ,
} ,
2021-02-18 15:06:22 +04:00
] ,
} ) ;
} ) ;
2025-02-10 14:54:01 +11:00
server . get ( "/admin/plugins/explorer/queries/-6" , ( ) => {
return helper . response ( {
query : {
id : - 6 ,
sql : "-- [params]\n-- int :months_ago = 1\n\nWITH query_period AS (\n SELECT\n date_trunc('month', CURRENT_DATE) - INTERVAL ':months_ago months' as period_start,\n date_trunc('month', CURRENT_DATE) - INTERVAL ':months_ago months' + INTERVAL '1 month' - INTERVAL '1 second' as period_end\n )\n\n SELECT\n ua.user_id,\n count(1) AS like_count\n FROM user_actions ua\n INNER JOIN query_period qp\n ON ua.created_at >= qp.period_start\n AND ua.created_at <= qp.period_end\n WHERE ua.action_type = 1\n GROUP BY ua.user_id\n ORDER BY like_count DESC\n LIMIT 100\n" ,
name : "Top 100 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." ,
param _info : [
{
identifier : "months_ago" ,
type : "int" ,
default : "1" ,
nullable : false ,
} ,
] ,
created _at : "2021-02-02T12:21:11.449Z" ,
username : "system" ,
group _ids : [ ] ,
last _run _at : "2021-02-11T08:29:59.337Z" ,
hidden : false ,
user _id : - 1 ,
} ,
} ) ;
} ) ;
server . get ( "/admin/plugins/explorer/queries/2" , ( ) => {
return helper . response ( {
query : {
id : 2 ,
sql : 'SELECT 0 zero, null "null", false "false"' ,
name : "What about 0?" ,
description : "" ,
param _info : [ ] ,
created _at : "2023-05-04T22:16:06.007Z" ,
username : "system" ,
group _ids : [ ] ,
last _run _at : "2023-05-04T22:16:23.858Z" ,
hidden : false ,
user _id : 1 ,
} ,
} ) ;
} ) ;
2021-02-18 15:06:22 +04:00
server . post ( "/admin/plugins/explorer/queries/-6/run" , ( ) => {
return helper . response ( {
success : true ,
errors : [ ] ,
duration : 27.5 ,
result _count : 2 ,
params : { months _ago : "1" } ,
columns : [ "user_id" , "like_count" ] ,
default _limit : 1000 ,
relations : {
user : [
{
id : - 2 ,
username : "discobot" ,
name : null ,
avatar _template : "/user_avatar/localhost/discobot/{size}/2_2.png" ,
} ,
{
id : 2 ,
username : "andrey1" ,
name : null ,
avatar _template :
"/letter_avatar_proxy/v4/letter/a/c0e974/{size}.png" ,
} ,
] ,
} ,
colrender : {
0 : "user" ,
} ,
rows : [
[ - 2 , 2 ] ,
[ 2 , 2 ] ,
] ,
} ) ;
} ) ;
2023-05-04 19:45:43 -03:00
server . post ( "/admin/plugins/explorer/queries/2/run" , ( ) => {
return helper . response ( {
success : true ,
errors : [ ] ,
duration : 1.0 ,
result _count : 1 ,
params : { } ,
columns : [ "zero" , "null" , "false" ] ,
default _limit : 1000 ,
relations : { } ,
colrender : { } ,
rows : [ [ 0 , null , false ] ] ,
} ) ;
} ) ;
2025-02-14 10:11:32 +11:00
server . get ( "/session/csrf.json" , function ( ) {
return helper . response ( {
csrf : "mgk906YLagHo2gOgM1ddYjAN4hQolBdJCqlY6jYzAYs= " ,
} ) ;
} ) ;
2021-02-18 15:06:22 +04:00
} ) ;
2024-08-28 13:51:37 +02:00
test ( "runs query and renders data and a chart" , async function ( assert ) {
2025-02-10 14:54:01 +11:00
await visit ( "/admin/plugins/explorer/queries/-6" ) ;
2021-02-18 15:06:22 +04:00
2024-08-28 13:51:37 +02:00
assert
. dom ( "div.name h1" )
. hasText ( "Top 100 Likers" , "the query name was rendered" ) ;
2021-02-18 15:06:22 +04:00
2024-08-28 13:51:37 +02:00
assert . dom ( "div.query-edit" ) . exists ( "the query code was rendered" ) ;
2021-02-18 15:06:22 +04:00
2024-08-28 13:51:37 +02:00
assert
. dom ( "form.query-run button span" )
2025-02-06 16:45:00 +00:00
. hasText ( i18n ( "explorer.run" ) , "the run button was rendered" ) ;
2021-02-18 15:06:22 +04:00
await click ( "form.query-run button" ) ;
2024-08-28 13:51:37 +02:00
assert
. dom ( "div.query-results table tbody tr" )
. exists ( { count : 2 } , "the table with query results was rendered" ) ;
2022-12-20 12:09:37 -06:00
2024-08-28 13:51:37 +02:00
assert
. dom ( "div.result-info button:nth-child(3) span" )
2025-02-06 16:45:00 +00:00
. hasText ( i18n ( "explorer.show_graph" ) , "the chart button was rendered" ) ;
2021-02-18 15:06:22 +04:00
await click ( "div.result-info button:nth-child(3)" ) ;
2024-08-28 13:51:37 +02:00
assert . dom ( "canvas" ) . exists ( "the chart was rendered" ) ;
2021-02-18 15:06:22 +04:00
} ) ;
2023-05-04 19:45:43 -03:00
2025-02-14 10:11:32 +11:00
test ( "runs query and is able to download the results" , async function ( assert ) {
await visit ( "/admin/plugins/explorer/queries/-6" ) ;
await click ( "form.query-run button" ) ;
const createElement = document . createElement . bind ( document ) ;
const appendChild = document . body . appendChild . bind ( document . body ) ;
const removeChild = document . body . removeChild . bind ( document . body ) ;
const finishedForm = sinon . promise ( ) ;
let formElement ;
const formStub = sinon
. stub ( document , "createElement" )
. callsFake ( ( tagName ) => {
if ( tagName === "form" ) {
formElement = {
fakeForm : true ,
setAttribute : sinon . stub ( ) ,
appendChild : sinon . stub ( ) ,
submit : sinon . stub ( ) . callsFake ( finishedForm . resolve ) ,
} ;
return formElement ;
}
return createElement ( tagName ) ;
} ) ;
const appendChildStub = sinon
. stub ( document . body , "appendChild" )
. callsFake ( ( el ) => {
if ( ! el . fakeForm ) {
return appendChild ( el ) ;
}
} ) ;
const removeChildStub = sinon
. stub ( document . body , "removeChild" )
. callsFake ( ( el ) => {
if ( ! el . fakeForm ) {
return removeChild ( el ) ;
}
} ) ;
await click ( "div.result-info button:nth-child(1)" ) ;
await finishedForm ;
formStub . restore ( ) ;
appendChildStub . restore ( ) ;
removeChildStub . restore ( ) ;
assert . ok ( window . open . called , "window.open was called for downloading" ) ;
assert . ok ( formStub . called , "form was created for downloading" ) ;
assert . ok ( formElement . submit . called , "form was submitted for downloading" ) ;
assert . ok (
formElement . setAttribute . calledWith ( "action" ) ,
"form action attribute was set"
) ;
assert . ok (
formElement . setAttribute . calledWith ( "method" , "post" ) ,
"form method attribute was set to POST"
) ;
} ) ;
2024-08-28 13:51:37 +02:00
test ( "runs query and renders 0, false, and NULL values correctly" , async function ( assert ) {
2025-02-10 14:54:01 +11:00
await visit ( "/admin/plugins/explorer/queries/2" ) ;
2023-05-04 19:45:43 -03:00
2024-08-28 13:51:37 +02:00
assert
2025-06-11 14:01:11 +08:00
. dom ( "div.name h1 span" )
2024-08-28 13:51:37 +02:00
. hasText ( "What about 0?" , "the query name was rendered" ) ;
2023-05-04 19:45:43 -03:00
2024-08-28 13:51:37 +02:00
assert
. dom ( "form.query-run button span" )
2025-02-06 16:45:00 +00:00
. hasText ( i18n ( "explorer.run" ) , "the run button was rendered" ) ;
2023-05-04 19:45:43 -03:00
await click ( "form.query-run button" ) ;
2024-08-28 13:51:37 +02:00
assert
. dom ( "div.query-results tbody td:nth-child(1)" )
. hasText ( "0" , "renders '0' values" ) ;
assert
. dom ( "div.query-results tbody td:nth-child(2)" )
. hasText ( "NULL" , "renders 'NULL' values" ) ;
assert
. dom ( "div.query-results tbody td:nth-child(3)" )
. hasText ( "false" , "renders 'false' values" ) ;
2023-05-04 19:45:43 -03:00
} ) ;
2021-02-18 15:06:22 +04:00
} ) ;