1
0
mirror of synced 2026-05-22 14:03:19 +00:00

DEV: Update linting config and run gjs-codemod (#376)

This commit is contained in:
Jarek Radosz
2025-06-05 12:36:14 +02:00
committed by GitHub
parent 5a2bfcebff
commit 85b485e1bf
65 changed files with 1593 additions and 1342 deletions
+1
View File
@@ -1,3 +1,4 @@
< 3.5.0.beta5-dev: 71141cb6b274c778ad00c8f5eb17b3b6bc59dce9
< 3.5.0.beta1-dev: 2ba204a1de2638a7959e588b88f3b6c7fcf7a70f
< 3.4.0.beta2-dev: bdff229ca088ead9512ba333eea4fdbbb258b250
< 3.4.0.beta1-dev: 2d0dc39767f0c68d333f113c550731a5546c3137
+23 -21
View File
@@ -14,30 +14,31 @@ GEM
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
ast (2.4.2)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.9)
ast (2.4.3)
base64 (0.3.0)
benchmark (0.4.1)
bigdecimal (3.2.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.0)
drb (2.2.1)
connection_pool (2.5.3)
drb (2.2.3)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
json (2.10.2)
language_server-protocol (3.17.0.4)
json (2.12.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.6.6)
logger (1.7.0)
minitest (5.25.5)
parallel (1.26.3)
parser (3.3.7.1)
parallel (1.27.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
prettier_print (1.2.1)
prism (1.4.0)
racc (1.8.1)
rack (3.1.12)
rack (3.1.15)
rainbow (3.1.1)
regexp_parser (2.10.0)
rubocop (1.74.0)
rubocop (1.75.8)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -45,11 +46,12 @@ GEM
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.38.1)
parser (>= 3.3.1.0)
rubocop-ast (1.44.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
@@ -65,13 +67,13 @@ GEM
rubocop-factory_bot (2.27.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rails (2.30.3)
rubocop-rails (2.32.0)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.72.1, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rspec (3.5.0)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)
@@ -97,4 +99,4 @@ DEPENDENCIES
syntax_tree
BUNDLED WITH
2.6.6
2.6.9
@@ -0,0 +1,5 @@
const CodeView = <template>
<pre><code class={{@codeClass}}>{{@value}}</code></pre>
</template>;
export default CodeView;
@@ -1 +0,0 @@
<pre><code class={{@codeClass}}>{{@value}}</code></pre>
@@ -1,5 +1,7 @@
import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { bind } from "discourse/lib/decorators";
import loadScript from "discourse/lib/load-script";
import themeColor from "../lib/themeColor";
@@ -89,4 +91,11 @@ export default class DataExplorerBarChart extends Component {
this.chart.data = this.data;
this.chart.update();
}
<template>
<canvas
{{didInsert this.initChart}}
{{on "change" this.updateChartData}}
></canvas>
</template>
}
@@ -1,4 +0,0 @@
<canvas
{{did-insert this.initChart}}
{{on "change" this.updateChartData}}
></canvas>
@@ -1,8 +1,12 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { isBlank, isEmpty } from "@ember/utils";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import { debounce } from "discourse/lib/decorators";
import OneTable from "./explorer-schema/one-table";
export default class ExplorerSchema extends Component {
@tracked filter;
@@ -135,4 +139,38 @@ export default class ExplorerSchema extends Component {
this.hideSchema = false;
this.args.updateHideSchema(false);
}
<template>
{{#if this.hideSchema}}
<DButton
@action={{this.expandSchema}}
@icon="chevron-left"
class="no-text unhide"
/>
{{else}}
<div class="schema">
<div class="schema-search inline-form full-width">
<input
type="text"
{{on "input" (action "filterChanged" value="target.value")}}
/>
<DButton
@action={{this.collapseSchema}}
@icon="chevron-right"
class="no-text"
/>
</div>
<div class="schema-container">
<ConditionalLoadingSpinner @condition={{this.loading}}>
<ul>
{{#each this.filteredTables as |table|}}
<OneTable @table={{table}} />
{{/each}}
</ul>
</ConditionalLoadingSpinner>
</div>
</div>
{{/if}}
</template>
}
@@ -1,31 +0,0 @@
{{#if this.hideSchema}}
<DButton
@action={{this.expandSchema}}
@icon="chevron-left"
class="no-text unhide"
/>
{{else}}
<div class="schema">
<div class="schema-search inline-form full-width">
<input
type="text"
{{on "input" (action "filterChanged" value="target.value")}}
/>
<DButton
@action={{this.collapseSchema}}
@icon="chevron-right"
class="no-text"
/>
</div>
<div class="schema-container">
<ConditionalLoadingSpinner @condition={{this.loading}}>
<ul>
{{#each this.filteredTables as |table|}}
<ExplorerSchema::OneTable @table={{table}} />
{{/each}}
</ul>
</ConditionalLoadingSpinner>
</div>
</div>
{{/if}}
@@ -7,4 +7,14 @@ export default class EnumInfo extends Component {
name,
}));
}
<template>
<ol>
{{#each this.enuminfo as |enum|}}
<li value={{enum.value}}>
{{enum.name}}
</li>
{{/each}}
</ol>
</template>
}
@@ -1,7 +0,0 @@
<ol>
{{#each this.enuminfo as |enum|}}
<li value={{enum.value}}>
{{enum.name}}
</li>
{{/each}}
</ol>
@@ -0,0 +1,78 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import icon from "discourse/helpers/d-icon";
import { bind } from "discourse/lib/decorators";
import { i18n } from "discourse-i18n";
import EnumInfo from "./enum-info";
export default class OneTable extends Component {
@tracked open = this.args.table.open;
get styles() {
return this.open ? "open" : "";
}
@bind
toggleOpen() {
this.open = !this.open;
}
<template>
<li class="schema-table {{this.styles}}">
{{! template-lint-enable no-invalid-interactive }}
<div
class="schema-table-name"
role="button"
{{on "click" this.toggleOpen}}
>
{{#if this.open}}
{{icon "caret-down"}}
{{else}}
{{icon "caret-right"}}
{{/if}}
{{@table.name}}
</div>
<div class="schema-table-cols">
{{#if this.open}}
<dl>
{{#each @table.columns as |col|}}
<div>
<dt
class={{if col.sensitive "sensitive"}}
title={{if col.sensitive (i18n "explorer.schema.sensitive")}}
>
{{#if col.sensitive}}
{{icon "triangle-exclamation"}}
{{/if}}
{{col.column_name}}
</dt>
<dd>
{{col.data_type}}
{{#if col.havetypeinfo}}
<br />
{{#if col.havepopup}}
<div class="popup-info">
{{icon "info"}}
<div class="popup">
{{col.column_desc}}
{{#if col.enum}}
<EnumInfo @col={{col}} />
{{/if}}
</div>
</div>
{{/if}}
<span class="schema-typenotes">
{{col.notes}}
</span>
{{/if}}
</dd>
</div>
{{/each}}
</dl>
{{/if}}
</div>
</li>
</template>
}
@@ -1,51 +0,0 @@
<li class="schema-table {{this.styles}}">
{{! template-lint-enable no-invalid-interactive }}
<div class="schema-table-name" role="button" {{on "click" this.toggleOpen}}>
{{#if this.open}}
{{d-icon "caret-down"}}
{{else}}
{{d-icon "caret-right"}}
{{/if}}
{{@table.name}}
</div>
<div class="schema-table-cols">
{{#if this.open}}
<dl>
{{#each @table.columns as |col|}}
<div>
<dt
class={{if col.sensitive "sensitive"}}
title={{if col.sensitive (i18n "explorer.schema.sensitive")}}
>
{{#if col.sensitive}}
{{d-icon "triangle-exclamation"}}
{{/if}}
{{col.column_name}}
</dt>
<dd>
{{col.data_type}}
{{#if col.havetypeinfo}}
<br />
{{#if col.havepopup}}
<div class="popup-info">
{{d-icon "info"}}
<div class="popup">
{{col.column_desc}}
{{#if col.enum}}
<ExplorerSchema::EnumInfo @col={{col}} />
{{/if}}
</div>
</div>
{{/if}}
<span class="schema-typenotes">
{{col.notes}}
</span>
{{/if}}
</dd>
</div>
{{/each}}
</dl>
{{/if}}
</div>
</li>
@@ -1,16 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { bind } from "discourse/lib/decorators";
export default class OneTable extends Component {
@tracked open = this.args.table.open;
get styles() {
return this.open ? "open" : "";
}
@bind
toggleOpen() {
this.open = !this.open;
}
}
@@ -0,0 +1,17 @@
import { i18n } from "discourse-i18n";
const BooleanThree = <template>
<@field.Select name={{@info.identifier}} as |select|>
<select.Option @value="Y">
{{i18n "explorer.types.bool.true"}}
</select.Option>
<select.Option @value="N">
{{i18n "explorer.types.bool.false"}}
</select.Option>
<select.Option @value="#null">
{{i18n "explorer.types.bool.null_"}}
</select.Option>
</@field.Select>
</template>;
export default BooleanThree;
@@ -1,11 +0,0 @@
<@field.Select name={{@info.identifier}} as |select|>
<select.Option @value="Y">
{{i18n "explorer.types.bool.true"}}
</select.Option>
<select.Option @value="N">
{{i18n "explorer.types.bool.false"}}
</select.Option>
<select.Option @value="#null">
{{i18n "explorer.types.bool.null_"}}
</select.Option>
</@field.Select>
@@ -0,0 +1,15 @@
import { hash } from "@ember/helper";
import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
const UserIdInput = <template>
<@field.Custom id={{@field.id}}>
<EmailGroupUserChooser
@value={{@field.value}}
@options={{hash maximum=1}}
@onChange={{@field.set}}
name={{@info.identifier}}
/>
</@field.Custom>
</template>;
export default UserIdInput;
@@ -1,8 +0,0 @@
<@field.Custom id={{@field.id}}>
<EmailGroupUserChooser
@value={{@field.value}}
@options={{hash maximum=1}}
@onChange={{@field.set}}
name={{@info.identifier}}
/>
</@field.Custom>
@@ -0,0 +1,13 @@
import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
const UserListInput = <template>
<@field.Custom id={{@field.id}}>
<EmailGroupUserChooser
@value={{@field.value}}
@onChange={{@field.set}}
name={{@info.identifier}}
/>
</@field.Custom>
</template>;
export default UserListInput;
@@ -1,7 +0,0 @@
<@field.Custom id={{@field.id}}>
<EmailGroupUserChooser
@value={{@field.value}}
@onChange={{@field.set}}
name={{@info.identifier}}
/>
</@field.Custom>
@@ -4,11 +4,14 @@ import { action } from "@ember/object";
import { schedule } from "@ember/runloop";
import { service } from "@ember/service";
import { capitalize } from "@ember/string";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import getURL from "discourse/lib/get-url";
import Badge from "discourse/models/badge";
import Category from "discourse/models/category";
import I18n, { i18n } from "discourse-i18n";
import DataExplorerBarChart from "./data-explorer-bar-chart";
import QueryRowContent from "./query-row-content";
import BadgeViewComponent from "./result-types/badge";
import CategoryViewComponent from "./result-types/category";
import GroupViewComponent from "./result-types/group";
@@ -288,6 +291,99 @@ export default class QueryResult extends Component {
schedule("afterRender", () => document.body.removeChild(form));
});
}
<template>
<article>
<header class="result-header">
<div class="result-info">
<DButton
@action={{this.downloadResultJson}}
@icon="download"
@label="explorer.download_json"
/>
<DButton
@action={{this.downloadResultCsv}}
@icon="download"
@label="explorer.download_csv"
/>
{{#if this.canShowChart}}
{{#if this.chartDisplayed}}
<DButton
@action={{this.hideChart}}
@icon="table"
@label="explorer.show_table"
/>
{{else}}
<DButton
@action={{this.showChart}}
@icon="chart-bar"
@label="explorer.show_graph"
/>
{{/if}}
{{/if}}
</div>
<div class="result-about">
{{this.resultCount}}
{{this.duration}}
</div>
<br />
{{~#if this.explainText}}
<pre class="result-explain">
<code>
{{~this.explainText}}
</code>
</pre>
{{~/if}}
<br />
</header>
<section>
{{#if this.chartDisplayed}}
<DataExplorerBarChart
@labels={{this.chartLabels}}
@values={{this.chartValues}}
@datasetName={{this.chartDatasetName}}
/>
{{else}}
<table>
<thead>
<tr class="headers">
{{#each this.columnNames as |col|}}
<th>{{col}}</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each this.rows as |row|}}
<QueryRowContent
@row={{row}}
@columnComponents={{this.columnComponents}}
@lookupUser={{this.lookupUser}}
@lookupBadge={{this.lookupBadge}}
@lookupPost={{this.lookupPost}}
@lookupTopic={{this.lookupTopic}}
@lookupGroup={{this.lookupGroup}}
@lookupCategory={{this.lookupCategory}}
@transformedPostTable={{this.transformedPostTable}}
@transformedBadgeTable={{this.transformedBadgeTable}}
@transformedUserTable={{this.transformedUserTable}}
@transformedGroupTable={{this.transformedGroupTable}}
@transformedTopicTable={{this.transformedTopicTable}}
@site={{this.site}}
/>
{{/each}}
</tbody>
</table>
{{/if}}
</section>
</article>
</template>
}
function randomIdShort() {
@@ -1,90 +0,0 @@
<article>
<header class="result-header">
<div class="result-info">
<DButton
@action={{this.downloadResultJson}}
@icon="download"
@label="explorer.download_json"
/>
<DButton
@action={{this.downloadResultCsv}}
@icon="download"
@label="explorer.download_csv"
/>
{{#if this.canShowChart}}
{{#if this.chartDisplayed}}
<DButton
@action={{this.hideChart}}
@icon="table"
@label="explorer.show_table"
/>
{{else}}
<DButton
@action={{this.showChart}}
@icon="chart-bar"
@label="explorer.show_graph"
/>
{{/if}}
{{/if}}
</div>
<div class="result-about">
{{this.resultCount}}
{{this.duration}}
</div>
<br />
{{~#if this.explainText}}
<pre class="result-explain">
<code>
{{~this.explainText}}
</code>
</pre>
{{~/if}}
<br />
</header>
<section>
{{#if this.chartDisplayed}}
<DataExplorerBarChart
@labels={{this.chartLabels}}
@values={{this.chartValues}}
@datasetName={{this.chartDatasetName}}
/>
{{else}}
<table>
<thead>
<tr class="headers">
{{#each this.columnNames as |col|}}
<th>{{col}}</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each this.rows as |row|}}
<QueryRowContent
@row={{row}}
@columnComponents={{this.columnComponents}}
@lookupUser={{this.lookupUser}}
@lookupBadge={{this.lookupBadge}}
@lookupPost={{this.lookupPost}}
@lookupTopic={{this.lookupTopic}}
@lookupGroup={{this.lookupGroup}}
@lookupCategory={{this.lookupCategory}}
@transformedPostTable={{this.transformedPostTable}}
@transformedBadgeTable={{this.transformedBadgeTable}}
@transformedUserTable={{this.transformedUserTable}}
@transformedGroupTable={{this.transformedGroupTable}}
@transformedTopicTable={{this.transformedTopicTable}}
@site={{this.site}}
/>
{{/each}}
</tbody>
</table>
{{/if}}
</section>
</article>
@@ -0,0 +1,17 @@
import QueryResult from "./query-result";
const QueryResultsWrapper = <template>
{{#if @results}}
<div class="query-results">
{{#if @showResults}}
<QueryResult @query={{@query}} @content={{@results}} />
{{else}}
{{#each @results.errors as |err|}}
<pre class="query-error"><code>{{~err}}</code></pre>
{{/each}}
{{/if}}
</div>
{{/if}}
</template>;
export default QueryResultsWrapper;
@@ -1,11 +0,0 @@
{{#if @results}}
<div class="query-results">
{{#if @showResults}}
<QueryResult @query={{@query}} @content={{@results}} />
{{else}}
{{#each @results.errors as |err|}}
<pre class="query-error"><code>{{~err}}</code></pre>
{{/each}}
{{/if}}
</div>
{{/if}}
@@ -52,6 +52,20 @@ export default class QueryRowContent extends Component {
}
});
}
<template>
<tr>
{{#each this.results as |result|}}
<td>
<result.component
@ctx={{result.ctx}}
@params={{result.params}}
@textValue={{result.textValue}}
/>
</td>
{{/each}}
</tr>
</template>
}
function guessUrl(columnValue) {
@@ -1,11 +0,0 @@
<tr>
{{#each this.results as |result|}}
<td>
<result.component
@ctx={{result.ctx}}
@params={{result.params}}
@textValue={{result.textValue}}
/>
</td>
{{/each}}
</tr>
@@ -16,4 +16,16 @@ export default class Badge extends Component {
return htmlSafe("<img src='" + this.args.ctx.badge.icon + "'>");
}
}
<template>
<a
href="{{@ctx.baseuri}}/badges/{{@ctx.badge.id}}/{{@ctx.badge.name}}"
class="user-badge {{@ctx.badge.badgeTypeClassName}}"
title={{@ctx.badge.display_name}}
data-badge-name={{@ctx.badge.name}}
>
{{this.iconOrImageReplacement}}
<span class="badge-display-name">{{@ctx.badge.display_name}}</span>
</a>
</template>
}
@@ -1,9 +0,0 @@
<a
href="{{@ctx.baseuri}}/badges/{{@ctx.badge.id}}/{{@ctx.badge.name}}"
class="user-badge {{@ctx.badge.badgeTypeClassName}}"
title={{@ctx.badge.display_name}}
data-badge-name={{@ctx.badge.name}}
>
{{this.iconOrImageReplacement}}
<span class="badge-display-name">{{@ctx.badge.display_name}}</span>
</a>
@@ -7,4 +7,12 @@ export default class Category extends Component {
allowUncategorized: true,
});
}
<template>
{{#if @ctx.category}}
{{this.categoryBadgeReplacement}}
{{else}}
<a href="{{@ctx.baseuri}}/t/{{@ctx.id}}">{{@ctx.id}}</a>
{{/if}}
</template>
}
@@ -1,5 +0,0 @@
{{#if @ctx.category}}
{{this.categoryBadgeReplacement}}
{{else}}
<a href="{{@ctx.baseuri}}/t/{{@ctx.id}}">{{@ctx.id}}</a>
{{/if}}
@@ -0,0 +1,11 @@
const Group = <template>
{{#if @ctx.group}}
<a
href="{{@ctx.baseuri}}/groups/{{@ctx.group.name}}"
>{{@ctx.group.name}}</a>
{{else}}
{{@ctx.id}}
{{/if}}
</template>;
export default Group;
@@ -1,5 +0,0 @@
{{#if @ctx.group}}
<a href="{{@ctx.baseuri}}/groups/{{@ctx.group.name}}">{{@ctx.group.name}}</a>
{{else}}
{{@ctx.id}}
{{/if}}
@@ -0,0 +1,5 @@
import htmlSafe from "discourse/helpers/html-safe";
const Html = <template>{{htmlSafe @ctx.value}}</template>;
export default Html;
@@ -1 +0,0 @@
{{html-safe @ctx.value}}
@@ -2,6 +2,7 @@ import Component from "@glimmer/component";
import { cached } from "@glimmer/tracking";
import { action } from "@ember/object";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import FullscreenCodeModal from "discourse/components/modal/fullscreen-code";
export default class Json extends Component {
@@ -28,4 +29,16 @@ export default class Json extends Component {
},
});
}
<template>
<div class="result-json">
<div class="result-json-value">{{@ctx.value}}</div>
<DButton
class="result-json-button"
@action={{action "viewJson"}}
@icon="ellipsis"
@title="explorer.view_json"
/>
</div>
</template>
}
@@ -1,9 +0,0 @@
<div class="result-json">
<div class="result-json-value">{{@ctx.value}}</div>
<DButton
class="result-json-button"
@action={{action "viewJson"}}
@icon="ellipsis"
@title="explorer.view_json"
/>
</div>
@@ -0,0 +1,41 @@
import avatar from "discourse/helpers/avatar";
import htmlSafe from "discourse/helpers/html-safe";
const Post = <template>
{{#if @ctx.post}}
<aside
class="quote"
data-post={{@ctx.post.post_number}}
data-topic={{@ctx.post.topic_id}}
>
<div class="title">
<div class="quote-controls">
{{! template-lint-disable no-invalid-link-text }}
<a
href="/t/via-quote/{{@ctx.post.topic_id}}/{{@ctx.post.post_number}}"
title="go to the quoted post"
class="quote-other-topic"
>
</a>
</div>
<a
class="result-post-link"
href="/t/{{@ctx.post.topic_id}}/{{@ctx.post.post_number}}"
>
{{avatar @ctx.post imageSize="tiny"}}{{@ctx.post.username}}:
</a>
</div>
<blockquote>
<p>
{{htmlSafe @ctx.post.excerpt}}
</p>
</blockquote>
</aside>
{{else}}
{{@ctx.id}}
{{/if}}
</template>;
export default Post;
@@ -1,34 +0,0 @@
{{#if @ctx.post}}
<aside
class="quote"
data-post={{@ctx.post.post_number}}
data-topic={{@ctx.post.topic_id}}
>
<div class="title">
<div class="quote-controls">
{{! template-lint-disable no-invalid-link-text }}
<a
href="/t/via-quote/{{@ctx.post.topic_id}}/{{@ctx.post.post_number}}"
title="go to the quoted post"
class="quote-other-topic"
>
</a>
</div>
<a
class="result-post-link"
href="/t/{{@ctx.post.topic_id}}/{{@ctx.post.post_number}}"
>
{{avatar @ctx.post imageSize="tiny"}}{{@ctx.post.username}}:
</a>
</div>
<blockquote>
<p>
{{html-safe @ctx.post.excerpt}}
</p>
</blockquote>
</aside>
{{else}}
{{@ctx.id}}
{{/if}}
@@ -8,4 +8,6 @@ export default class Reltime extends Component {
autoUpdatingRelativeAge(new Date(this.args.ctx.value), { title: true })
);
}
<template>{{this.boundDateReplacement}}</template>
}
@@ -1 +0,0 @@
{{this.boundDateReplacement}}
@@ -0,0 +1,3 @@
const Text = <template>{{@textValue}}</template>;
export default Text;
@@ -1 +0,0 @@
{{@textValue}}
@@ -0,0 +1,14 @@
import htmlSafe from "discourse/helpers/html-safe";
const Topic = <template>
{{#if @ctx.topic}}
<a href="{{@ctx.baseuri}}/t/{{@ctx.topic.slug}}/{{@ctx.topic.id}}">
{{htmlSafe @ctx.topic.fancy_title}}
</a>
({{@ctx.topic.posts_count}})
{{else}}
<a href="{{@ctx.baseuri}}/t/{{@ctx.id}}">{{@ctx.id}}</a>
{{/if}}
</template>;
export default Topic;
@@ -1,8 +0,0 @@
{{#if @ctx.topic}}
<a href="{{@ctx.baseuri}}/t/{{@ctx.topic.slug}}/{{@ctx.topic.id}}">
{{html-safe @ctx.topic.fancy_title}}
</a>
({{@ctx.topic.posts_count}})
{{else}}
<a href="{{@ctx.baseuri}}/t/{{@ctx.id}}">{{@ctx.id}}</a>
{{/if}}
@@ -0,0 +1,5 @@
const Url = <template>
<a href={{@ctx.href}}>{{@ctx.target}}</a>
</template>;
export default Url;
@@ -1 +0,0 @@
<a href={{@ctx.href}}>{{@ctx.target}}</a>
@@ -0,0 +1,17 @@
import avatar from "discourse/helpers/avatar";
const User = <template>
{{#if @ctx.user}}
<a
href="{{@ctx.baseuri}}/u/{{@ctx.user.username}}/activity"
data-user-card={{@ctx.user.username}}
>
{{avatar @ctx.user imageSize="tiny"}}
{{@ctx.user.username}}
</a>
{{else}}
{{@ctx.id}}
{{/if}}
</template>;
export default User;
@@ -1,11 +0,0 @@
{{#if @ctx.user}}
<a
href="{{@ctx.baseuri}}/u/{{@ctx.user.username}}/activity"
data-user-card={{@ctx.user.username}}
>
{{avatar @ctx.user imageSize="tiny"}}
{{@ctx.user.username}}
</a>
{{else}}
{{@ctx.id}}
{{/if}}
@@ -1,8 +1,14 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
import DButton from "discourse/components/d-button";
import icon from "discourse/helpers/d-icon";
import { bind } from "discourse/lib/decorators";
import getURL from "discourse/lib/get-url";
import { i18n } from "discourse-i18n";
export default class ShareReport extends Component {
@tracked visible = false;
@@ -60,4 +66,36 @@ export default class ShareReport extends Component {
close() {
this.visible = false;
}
<template>
<div class="share-report">
<a href="#" {{on "click" this.open}} class="share-report-button">
{{icon "link"}}
{{@group}}
</a>
{{#if this.visible}}
<div
class="popup"
{{didInsert this.registerListeners}}
{{willDestroy this.unregisterListeners}}
>
<label>{{i18n "explorer.link"}} {{@group}}</label>
<input
type="text"
value={{this.link}}
{{didInsert this.focusInput}}
/>
<DButton
@action={{this.close}}
@icon="xmark"
@aria-label="share.close"
@title="share.close"
class="btn-flat close"
/>
</div>
{{/if}}
</div>
</template>
}
@@ -1,25 +0,0 @@
<div class="share-report">
<a href="#" {{on "click" this.open}} class="share-report-button">
{{d-icon "link"}}
{{@group}}
</a>
{{#if this.visible}}
<div
class="popup"
{{did-insert this.registerListeners}}
{{will-destroy this.unregisterListeners}}
>
<label>{{i18n "explorer.link"}} {{@group}}</label>
<input type="text" value={{this.link}} {{did-insert this.focusInput}} />
<DButton
@action={{this.close}}
@icon="xmark"
@aria-label="share.close"
@title="share.close"
class="btn-flat close"
/>
</div>
{{/if}}
</div>
@@ -0,0 +1,19 @@
import Component from "@ember/component";
import { LinkTo } from "@ember/routing";
import { classNames, tagName } from "@ember-decorators/component";
import icon from "discourse/helpers/d-icon";
import { i18n } from "discourse-i18n";
@tagName("li")
@classNames("group-reports-nav-item-outlet", "nav-item")
export default class NavItem extends Component {
static shouldRender(args) {
return args.group.has_visible_data_explorer_queries;
}
<template>
<LinkTo @route="group.reports">
{{icon "chart-bar"}}{{i18n "group.reports"}}
</LinkTo>
</template>
}
@@ -1,3 +0,0 @@
<LinkTo @route="group.reports">
{{d-icon "chart-bar"}}{{i18n "group.reports"}}
</LinkTo>
@@ -1,5 +0,0 @@
export default {
shouldRender(args) {
return args.group.has_visible_data_explorer_queries;
},
};
@@ -0,0 +1,187 @@
import { fn } from "@ember/helper";
import { on } from "@ember/modifier";
import RouteTemplate from "ember-route-template";
import { not } from "truth-helpers";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import PickFilesButton from "discourse/components/pick-files-button";
import TableHeaderToggle from "discourse/components/table-header-toggle";
import TextField from "discourse/components/text-field";
import boundDate from "discourse/helpers/bound-date";
import icon from "discourse/helpers/d-icon";
import { i18n } from "discourse-i18n";
import ShareReport from "../../components/share-report";
export default RouteTemplate(
<template>
{{#if @controller.disallow}}
<h1>{{i18n "explorer.admins_only"}}</h1>
{{else}}
<div class="query-list">
<TextField
@value={{@controller.search}}
@placeholderKey="explorer.search_placeholder"
@onChange={{@controller.updateSearch}}
/>
<DButton
@action={{@controller.displayCreate}}
@icon="plus"
class="no-text btn-right"
/>
<PickFilesButton
@label="explorer.import.label"
@icon="upload"
@acceptedFormatsOverride={{@controller.acceptedImportFileTypes}}
@showButton="true"
@onFilesPicked={{@controller.import}}
class="import-btn"
/>
</div>
{{#if @controller.showCreate}}
<div class="query-create">
<TextField
@value={{@controller.newQueryName}}
@placeholderKey="explorer.create_placeholder"
@onChange={{@controller.updateNewQueryName}}
/>
<DButton
@action={{@controller.create}}
@disabled={{@controller.createDisabled}}
@label="explorer.create"
@icon="plus"
/>
</div>
{{/if}}
{{#if @controller.othersDirty}}
<div class="warning">
{{icon "triangle-exclamation"}}
{{i18n "explorer.others_dirty"}}
</div>
{{/if}}
{{#if @controller.model.length}}
<ConditionalLoadingSpinner @condition={{@controller.loading}} />
<div class="container">
<table class="d-admin-table recent-queries">
<thead class="heading-container">
<th class="col heading name">
<div
role="button"
class="heading-toggle"
{{on "click" (fn @controller.updateSortProperty "name")}}
>
<TableHeaderToggle
@field="name"
@labelKey="explorer.query_name"
@order={{@controller.order}}
@asc={{not @controller.sortDescending}}
@automatic="true"
/>
</div>
</th>
<th class="col heading created-by">
<div
role="button"
class="heading-toggle"
{{on "click" (fn @controller.updateSortProperty "username")}}
>
<TableHeaderToggle
@field="username"
@labelKey="explorer.query_user"
@order={{@controller.order}}
@asc={{not @controller.sortDescending}}
@automatic="true"
/>
</div>
</th>
<th class="col heading group-names">
<div class="group-names-header">
{{i18n "explorer.query_groups"}}
</div>
</th>
<th class="col heading created-at">
<div
role="button"
class="heading-toggle"
{{on
"click"
(fn @controller.updateSortProperty "last_run_at")
}}
>
<TableHeaderToggle
@field="last_run_at"
@labelKey="explorer.query_time"
@order={{@controller.order}}
@asc={{not @controller.sortDescending}}
@automatic="true"
/>
</div>
</th>
</thead>
<tbody>
{{#each @controller.filteredContent as |query|}}
<tr class="d-admin-row__content query-row">
<td class="d-admin-row__overview">
<a
{{on "click" @controller.scrollTop}}
href="/admin/plugins/explorer/queries/{{query.id}}"
>
<b class="query-name">{{query.name}}</b>
<span class="query-desc">{{query.description}}</span>
</a>
</td>
<td class="d-admin-row__detail query-created-by">
<div class="d-admin-row__mobile-label">
{{i18n "explorer.query_user"}}
</div>
{{#if query.username}}
<div>
<a href="/u/{{query.username}}/activity">
<span>{{query.username}}</span>
</a>
</div>
{{/if}}
</td>
<td class="d-admin-row__detail query-group-names">
<div class="d-admin-row__mobile-label">
{{i18n "explorer.query_groups"}}
</div>
<div class="group-names">
{{#each query.group_names as |group|}}
<ShareReport @group={{group}} @query={{query}} />
{{/each}}
</div>
</td>
<td class="d-admin-row__detail query-created-at">
<div class="d-admin-row__mobile-label">
{{i18n "explorer.query_time"}}
</div>
{{#if query.last_run_at}}
<span>
{{boundDate query.last_run_at}}
</span>
{{else if query.created_at}}
<span>
{{boundDate query.created_at}}
</span>
{{/if}}
</td>
</tr>
{{else}}
<br />
<em class="no-search-results">
{{i18n "explorer.no_search_results"}}
</em>
{{/each}}
</tbody>
</table>
</div>
<div class="explorer-pad-bottom"></div>
{{/if}}
{{/if}}
</template>
);
@@ -1,166 +0,0 @@
{{#if this.disallow}}
<h1>{{i18n "explorer.admins_only"}}</h1>
{{else}}
<div class="query-list">
<TextField
@value={{this.search}}
@placeholderKey="explorer.search_placeholder"
@onChange={{this.updateSearch}}
/>
<DButton
@action={{this.displayCreate}}
@icon="plus"
class="no-text btn-right"
/>
<PickFilesButton
@label="explorer.import.label"
@icon="upload"
@acceptedFormatsOverride={{this.acceptedImportFileTypes}}
@showButton="true"
@onFilesPicked={{this.import}}
class="import-btn"
/>
</div>
{{#if this.showCreate}}
<div class="query-create">
<TextField
@value={{this.newQueryName}}
@placeholderKey="explorer.create_placeholder"
@onChange={{this.updateNewQueryName}}
/>
<DButton
@action={{this.create}}
@disabled={{this.createDisabled}}
@label="explorer.create"
@icon="plus"
/>
</div>
{{/if}}
{{#if this.othersDirty}}
<div class="warning">
{{d-icon "triangle-exclamation"}}
{{i18n "explorer.others_dirty"}}
</div>
{{/if}}
{{#if this.model.length}}
<ConditionalLoadingSpinner @condition={{this.loading}} />
<div class="container">
<table class="d-admin-table recent-queries">
<thead class="heading-container">
<th class="col heading name">
<div
role="button"
class="heading-toggle"
{{on "click" (fn this.updateSortProperty "name")}}
>
<TableHeaderToggle
@field="name"
@labelKey="explorer.query_name"
@order={{this.order}}
@asc={{not this.sortDescending}}
@automatic="true"
/>
</div>
</th>
<th class="col heading created-by">
<div
role="button"
class="heading-toggle"
{{on "click" (fn this.updateSortProperty "username")}}
>
<TableHeaderToggle
@field="username"
@labelKey="explorer.query_user"
@order={{this.order}}
@asc={{not this.sortDescending}}
@automatic="true"
/>
</div>
</th>
<th class="col heading group-names">
<div class="group-names-header">
{{i18n "explorer.query_groups"}}
</div>
</th>
<th class="col heading created-at">
<div
role="button"
class="heading-toggle"
{{on "click" (fn this.updateSortProperty "last_run_at")}}
>
<TableHeaderToggle
@field="last_run_at"
@labelKey="explorer.query_time"
@order={{this.order}}
@asc={{not this.sortDescending}}
@automatic="true"
/>
</div>
</th>
</thead>
<tbody>
{{#each this.filteredContent as |query|}}
<tr class="d-admin-row__content query-row">
<td class="d-admin-row__overview">
<a
{{on "click" this.scrollTop}}
href="/admin/plugins/explorer/queries/{{query.id}}"
>
<b class="query-name">{{query.name}}</b>
<medium class="query-desc">{{query.description}}</medium>
</a>
</td>
<td class="d-admin-row__detail query-created-by">
<div class="d-admin-row__mobile-label">
{{i18n "explorer.query_user"}}
</div>
{{#if query.username}}
<div>
<a href="/u/{{query.username}}/activity">
<medium>{{query.username}}</medium>
</a>
</div>
{{/if}}
</td>
<td class="d-admin-row__detail query-group-names">
<div class="d-admin-row__mobile-label">
{{i18n "explorer.query_groups"}}
</div>
<div class="group-names">
{{#each query.group_names as |group|}}
<ShareReport @group={{group}} @query={{query}} />
{{/each}}
</div>
</td>
<td class="d-admin-row__detail query-created-at">
<div class="d-admin-row__mobile-label">
{{i18n "explorer.query_time"}}
</div>
{{#if query.last_run_at}}
<medium>
{{bound-date query.last_run_at}}
</medium>
{{else if query.created_at}}
<medium>
{{bound-date query.created_at}}
</medium>
{{/if}}
</td>
</tr>
{{else}}
<br />
<em class="no-search-results">
{{i18n "explorer.no_search_results"}}
</em>
{{/each}}
</tbody>
</table>
</div>
<div class="explorer-pad-bottom"></div>
{{/if}}
{{/if}}
@@ -0,0 +1,264 @@
import { Input } from "@ember/component";
import { fn, hash } from "@ember/helper";
import { on } from "@ember/modifier";
import RouteTemplate from "ember-route-template";
import AceEditor from "discourse/components/ace-editor";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import DTextarea from "discourse/components/d-textarea";
import TextField from "discourse/components/text-field";
import icon from "discourse/helpers/d-icon";
import draggable from "discourse/modifiers/draggable";
import { i18n } from "discourse-i18n";
import MultiSelect from "select-kit/components/multi-select";
import CodeView from "../../components/code-view";
import ExplorerSchema from "../../components/explorer-schema";
import ParamInputForm from "../../components/param-input-form";
import QueryResultsWrapper from "../../components/query-results-wrapper";
export default RouteTemplate(
<template>
{{#if @controller.disallow}}
<h1>{{i18n "explorer.admins_only"}}</h1>
{{else}}
<div class="query-edit {{if @controller.editingName 'editing'}}">
{{#if @controller.editingName}}
<div class="name">
<DButton
@action={{@controller.goHome}}
@icon="chevron-left"
class="previous"
/>
<DButton
@action={{@controller.exitEdit}}
@icon="xmark"
class="previous"
/>
<div class="name-text-field">
<TextField
@value={{@controller.model.name}}
@onChange={{@controller.setDirty}}
/>
</div>
</div>
<div class="desc">
<DTextarea
@value={{@controller.model.description}}
@placeholder={{i18n "explorer.description_placeholder"}}
@input={{@controller.setDirty}}
/>
</div>
{{else}}
<div class="name">
<DButton
@action={{@controller.goHome}}
@icon="chevron-left"
class="previous"
/>
<h1>
{{@controller.model.name}}
{{#unless @controller.editDisabled}}
<a
href
{{on "click" @controller.editName}}
class="edit-query-name"
>
{{icon "pencil"}}
</a>
{{/unless}}
</h1>
</div>
<div class="desc">
{{@controller.model.description}}
</div>
{{/if}}
{{#unless @controller.model.destroyed}}
<div class="groups">
<span class="label">{{i18n "explorer.allow_groups"}}</span>
<span>
<MultiSelect
@value={{@controller.model.group_ids}}
@content={{@controller.groupOptions}}
@options={{hash allowAny=false}}
@onChange={{@controller.updateGroupIds}}
/>
</span>
</div>
{{/unless}}
<div class="clear"></div>
{{#if @controller.editingQuery}}
<div class="query-editor {{if @controller.hideSchema 'no-schema'}}">
<div class="panels-flex">
<div class="editor-panel">
<AceEditor
{{on "click" @controller.setDirty}}
@content={{@controller.model.sql}}
@onChange={{fn (mut @controller.model.sql)}}
@mode="sql"
@disabled={{@controller.model.destroyed}}
@save={{@controller.save}}
@submit={{@controller.saveAndRun}}
/>
</div>
<div class="right-panel">
<ExplorerSchema
@schema={{@controller.schema}}
@hideSchema={{@controller.hideSchema}}
@updateHideSchema={{@controller.updateHideSchema}}
/>
</div>
</div>
<div
class="grippie"
{{draggable
didStartDrag=@controller.didStartDrag
didEndDrag=@controller.didEndDrag
dragMove=@controller.dragMove
}}
>
{{icon "discourse-expand"}}
</div>
<div class="clear"></div>
</div>
{{else}}
<div class="sql">
<CodeView
@value={{@controller.model.sql}}
@codeClass="sql"
@setDirty={{@controller.setDirty}}
/>
</div>
{{/if}}
<div class="clear"></div>
<div class="pull-left left-buttons">
{{#if @controller.editingQuery}}
<DButton
class="btn-save-query"
@action={{@controller.save}}
@label="explorer.save"
@disabled={{@controller.saveDisabled}}
/>
{{else}}
{{#unless @controller.editDisabled}}
<DButton
class="btn-edit-query"
@action={{@controller.editQuery}}
@label="explorer.edit"
@icon="pencil"
/>
{{/unless}}
{{/if}}
<DButton
@action={{@controller.download}}
@label="explorer.export"
@disabled={{@controller.runDisabled}}
@icon="download"
/>
{{#if @controller.editingQuery}}
<DButton
@action={{@controller.showHelpModal}}
@label="explorer.help.label"
@icon="circle-question"
/>
{{/if}}
</div>
<div class="pull-right right-buttons">
{{#if @controller.model.destroyed}}
<DButton
@action={{@controller.recover}}
@icon="arrow-rotate-left"
@label="explorer.recover"
/>
{{else}}
{{#if @controller.editingQuery}}
<DButton
@action={{@controller.discard}}
@icon="arrow-rotate-left"
@label="explorer.undo"
@disabled={{@controller.saveDisabled}}
/>
{{/if}}
<DButton
@action={{@controller.destroyQuery}}
@icon="trash-can"
@label="explorer.delete"
class="btn-danger"
/>
{{/if}}
</div>
<div class="clear"></div>
</div>
<form class="query-run" {{on "submit" @controller.run}}>
{{#if @controller.model.hasParams}}
<ParamInputForm
@initialValues={{@controller.parsedParams}}
@paramInfo={{@controller.model.param_info}}
@onRegisterApi={{@controller.onRegisterApi}}
/>
{{/if}}
{{#if @controller.runDisabled}}
{{#if @controller.saveDisabled}}
<DButton
@label="explorer.run"
@disabled="true"
class="btn-primary"
/>
{{else}}
<DButton
@action={{@controller.saveAndRun}}
@icon="play"
@label="explorer.saverun"
class="btn-primary"
/>
{{/if}}
{{else}}
<DButton
@action={{@controller.run}}
@icon="play"
@label="explorer.run"
@disabled={{@controller.runDisabled}}
@type="submit"
class="btn-primary"
/>
{{/if}}
<label class="query-plan">
<Input
@type="checkbox"
@checked={{@controller.explain}}
name="explain"
/>
{{i18n "explorer.explain_label"}}
</label>
</form>
<hr />
<ConditionalLoadingSpinner @condition={{@controller.loading}} />
<QueryResultsWrapper
@results={{@controller.results}}
@showResults={{@controller.showResults}}
@query={{@controller.model}}
@content={{@controller.results}}
/>
{{/if}}
</template>
);
@@ -1,223 +0,0 @@
{{#if this.disallow}}
<h1>{{i18n "explorer.admins_only"}}</h1>
{{else}}
<div class="query-edit {{if this.editName 'editing'}}">
{{#if this.editingName}}
<div class="name">
<DButton
@action={{this.goHome}}
@icon="chevron-left"
class="previous"
/>
<DButton @action={{this.exitEdit}} @icon="xmark" class="previous" />
<div class="name-text-field">
<TextField @value={{this.model.name}} @onChange={{this.setDirty}} />
</div>
</div>
<div class="desc">
<DTextarea
@value={{this.model.description}}
@placeholder={{i18n "explorer.description_placeholder"}}
@input={{this.setDirty}}
/>
</div>
{{else}}
<div class="name">
<DButton
@action={{this.goHome}}
@icon="chevron-left"
class="previous"
/>
<h1>
{{this.model.name}}
{{#unless this.editDisabled}}
<a href {{action "editName"}} class="edit-query-name">
{{d-icon "pencil"}}
</a>
{{/unless}}
</h1>
</div>
<div class="desc">
{{this.model.description}}
</div>
{{/if}}
{{#unless this.model.destroyed}}
<div class="groups">
<span class="label">{{i18n "explorer.allow_groups"}}</span>
<span>
<MultiSelect
@value={{this.model.group_ids}}
@content={{this.groupOptions}}
@options={{hash allowAny=false}}
@onChange={{this.updateGroupIds}}
/>
</span>
</div>
{{/unless}}
<div class="clear"></div>
{{#if this.editingQuery}}
<div class="query-editor {{if this.hideSchema 'no-schema'}}">
<div class="panels-flex">
<div class="editor-panel">
<AceEditor
{{on "click" this.setDirty}}
@content={{this.model.sql}}
@onChange={{fn (mut this.model.sql)}}
@mode="sql"
@disabled={{this.model.destroyed}}
@save={{this.save}}
@submit={{this.saveAndRun}}
/>
</div>
<div class="right-panel">
<ExplorerSchema
@schema={{this.schema}}
@hideSchema={{this.hideSchema}}
@updateHideSchema={{this.updateHideSchema}}
/>
</div>
</div>
<div
class="grippie"
{{draggable
didStartDrag=this.didStartDrag
didEndDrag=this.didEndDrag
dragMove=this.dragMove
}}
>
{{d-icon "discourse-expand"}}
</div>
<div class="clear"></div>
</div>
{{else}}
<div class="sql">
<CodeView
@value={{this.model.sql}}
@codeClass="sql"
@setDirty={{this.setDirty}}
/>
</div>
{{/if}}
<div class="clear"></div>
<div class="pull-left left-buttons">
{{#if this.editingQuery}}
<DButton
class="btn-save-query"
@action={{this.save}}
@label="explorer.save"
@disabled={{this.saveDisabled}}
/>
{{else}}
{{#unless this.editDisabled}}
<DButton
class="btn-edit-query"
@action={{this.editQuery}}
@label="explorer.edit"
@icon="pencil"
/>
{{/unless}}
{{/if}}
<DButton
@action={{this.download}}
@label="explorer.export"
@disabled={{this.runDisabled}}
@icon="download"
/>
{{#if this.editingQuery}}
<DButton
@action={{this.showHelpModal}}
@label="explorer.help.label"
@icon="circle-question"
/>
{{/if}}
</div>
<div class="pull-right right-buttons">
{{#if this.model.destroyed}}
<DButton
@action={{this.recover}}
@icon="arrow-rotate-left"
@label="explorer.recover"
/>
{{else}}
{{#if this.editingQuery}}
<DButton
@action={{this.discard}}
@icon="arrow-rotate-left"
@label="explorer.undo"
@disabled={{this.saveDisabled}}
/>
{{/if}}
<DButton
@action={{this.destroyQuery}}
@icon="trash-can"
@label="explorer.delete"
class="btn-danger"
/>
{{/if}}
</div>
<div class="clear"></div>
</div>
<form class="query-run" {{on "submit" this.run}}>
{{#if this.model.hasParams}}
<ParamInputForm
@initialValues={{this.parsedParams}}
@paramInfo={{this.model.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>
{{/if}}
{{#if this.runDisabled}}
{{#if this.saveDisabled}}
<DButton @label="explorer.run" @disabled="true" class="btn-primary" />
{{else}}
<DButton
@action={{this.saveAndRun}}
@icon="play"
@label="explorer.saverun"
class="btn-primary"
/>
{{/if}}
{{else}}
<DButton
@action={{this.run}}
@icon="play"
@label="explorer.run"
@disabled={{this.runDisabled}}
@type="submit"
class="btn-primary"
/>
{{/if}}
<label class="query-plan">
<Input @type="checkbox" @checked={{this.explain}} name="explain" />
{{i18n "explorer.explain_label"}}
</label>
</form>
<hr />
<ConditionalLoadingSpinner @condition={{this.loading}} />
<QueryResultsWrapper
@results={{this.results}}
@showResults={{this.showResults}}
@query={{this.model}}
@content={{this.results}}
/>
{{/if}}
@@ -0,0 +1,45 @@
import { array } from "@ember/helper";
import { LinkTo } from "@ember/routing";
import RouteTemplate from "ember-route-template";
import boundDate from "discourse/helpers/bound-date";
import { i18n } from "discourse-i18n";
export default RouteTemplate(
<template>
<section class="user-content">
<table class="group-reports">
<thead>
<th>
{{i18n "explorer.report_name"}}
</th>
<th>
{{i18n "explorer.query_description"}}
</th>
<th>
{{i18n "explorer.query_time"}}
</th>
</thead>
<tbody>
{{#each @controller.model.queries as |query|}}
<tr>
<td>
<LinkTo
@route="group.reports.show"
@models={{array @controller.group.name query.id}}
>
{{query.name}}
</LinkTo>
</td>
<td>{{query.description}}</td>
<td>
{{#if query.last_run_at}}
{{boundDate query.last_run_at}}
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</section>
</template>
);
@@ -1,35 +0,0 @@
<section class="user-content">
<table class="group-reports">
<thead>
<th>
{{i18n "explorer.report_name"}}
</th>
<th>
{{i18n "explorer.query_description"}}
</th>
<th>
{{i18n "explorer.query_time"}}
</th>
</thead>
<tbody>
{{#each this.model.queries as |query|}}
<tr>
<td>
<LinkTo
@route="group.reports.show"
@models={{array this.group.name query.id}}
>
{{query.name}}
</LinkTo>
</td>
<td>{{query.description}}</td>
<td>
{{#if query.last_run_at}}
{{bound-date query.last_run_at}}
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</section>
@@ -0,0 +1,58 @@
import { on } from "@ember/modifier";
import RouteTemplate from "ember-route-template";
import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner";
import DButton from "discourse/components/d-button";
import ParamInputForm from "../components/param-input-form";
import QueryResult from "../components/query-result";
export default RouteTemplate(
<template>
<section class="user-content">
<h1>{{@controller.model.name}}</h1>
<p>{{@controller.model.description}}</p>
<form class="query-run" {{on "submit" @controller.run}}>
{{#if @controller.hasParams}}
<ParamInputForm
@initialValues={{@controller.parsedParams}}
@paramInfo={{@controller.model.param_info}}
@onRegisterApi={{@controller.onRegisterApi}}
/>
{{/if}}
<DButton
@action={{@controller.run}}
@icon="play"
@label="explorer.run"
@type="submit"
class="btn-primary"
/>
<DButton
@action={{@controller.toggleBookmark}}
@label={{@controller.bookmarkLabel}}
@icon={{@controller.bookmarkIcon}}
class={{@controller.bookmarkClassName}}
/>
</form>
<ConditionalLoadingSpinner @condition={{@controller.loading}} />
{{#if @controller.results}}
<div class="query-results">
{{#if @controller.showResults}}
<QueryResult
@query={{@controller.model}}
@content={{@controller.results}}
@group={{@controller.group}}
/>
{{else}}
{{#each @controller.results.errors as |err|}}
<pre class="query-error"><code>{{~err}}</code></pre>
{{/each}}
{{/if}}
</div>
{{/if}}
</section>
</template>
);
@@ -1,47 +0,0 @@
<section class="user-content">
<h1>{{this.model.name}}</h1>
<p>{{this.model.description}}</p>
<form class="query-run" {{on "submit" this.run}}>
{{#if this.hasParams}}
<ParamInputForm
@initialValues={{this.parsedParams}}
@paramInfo={{this.model.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>
{{/if}}
<DButton
@action={{this.run}}
@icon="play"
@label="explorer.run"
@type="submit"
class="btn-primary"
/>
<DButton
@action={{this.toggleBookmark}}
@label={{this.bookmarkLabel}}
@icon={{this.bookmarkIcon}}
class={{this.bookmarkClassName}}
/>
</form>
<ConditionalLoadingSpinner @condition={{this.loading}} />
{{#if this.results}}
<div class="query-results">
{{#if this.showResults}}
<QueryResult
@query={{this.model}}
@content={{this.results}}
@group={{this.group}}
/>
{{else}}
{{#each this.results.errors as |err|}}
<pre class="query-error"><code>{{~err}}</code></pre>
{{/each}}
{{/if}}
</div>
{{/if}}
</section>
+5 -3
View File
@@ -1,3 +1,5 @@
@use "lib/viewport";
table.group-reports {
width: 100%;
table-layout: fixed;
@@ -247,7 +249,7 @@ table.group-reports {
flex-direction: row;
align-items: center;
@include breakpoint("tablet") {
@include viewport.until(md) {
flex-direction: column;
align-items: flex-start;
}
@@ -262,7 +264,7 @@ table.group-reports {
}
.select-kit.multi-select {
@include breakpoint("tablet") {
@include viewport.until(md) {
width: 360px;
}
}
@@ -464,7 +466,7 @@ table.group-reports {
}
.group-names {
@include breakpoint("tablet") {
@include viewport.until(md) {
text-align: right;
}
}
+4 -4
View File
@@ -1,11 +1,11 @@
{
"private": true,
"devDependencies": {
"@discourse/lint-configs": "2.11.1",
"ember-template-lint": "7.0.1",
"eslint": "9.22.0",
"@discourse/lint-configs": "2.23.0",
"ember-template-lint": "7.7.0",
"eslint": "9.28.0",
"prettier": "3.5.3",
"stylelint": "16.16.0"
"stylelint": "16.20.0"
},
"engines": {
"node": ">= 22",
+435 -437
View File
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
import { fillIn, render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import ExplorerSchema from "discourse/plugins/discourse-data-explorer/discourse/components/explorer-schema";
const schema = {
posts: [
@@ -40,18 +40,23 @@ module("Data Explorer Plugin | Component | explorer-schema", function (hooks) {
setupRenderingTest(hooks);
test("will automatically convert to lowercase", async function (assert) {
const self = this;
this.setProperties({
schema,
hideSchema: false,
updateHideSchema: () => {},
});
await render(hbs`
<ExplorerSchema
@schema={{this.schema}}
@hideSchema={{this.hideSchema}}
@updateHideSchema={{this.updateHideSchema}}
/>`);
await render(
<template>
<ExplorerSchema
@schema={{self.schema}}
@hideSchema={{self.hideSchema}}
@updateHideSchema={{self.updateHideSchema}}
/>
</template>
);
await fillIn(`.schema-search input`, "Cat");
@@ -1,10 +1,11 @@
import { fillIn, render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import formKit from "discourse/tests/helpers/form-kit-helper";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import { ERRORS } from "discourse/plugins/discourse-data-explorer/discourse/components/param-input-form";
import ParamInputForm, {
ERRORS,
} from "discourse/plugins/discourse-data-explorer/discourse/components/param-input-form";
const InputTestCases = [
{
@@ -170,6 +171,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
}
test(testName.join(" "), async function (assert) {
const self = this;
this.setProperties({
param_info: [
{
@@ -188,13 +191,16 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
},
});
await render(hbs`
<ParamInputForm
@hasParams=true
@initialValues={{this.initialValues}}
@paramInfo={{this.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>`);
await render(
<template>
<ParamInputForm
@hasParams="true"
@initialValues={{self.initialValues}}
@paramInfo={{self.param_info}}
@onRegisterApi={{self.onRegisterApi}}
/>
</template>
);
await this.allNormalized;
@@ -245,6 +251,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
}
test("empty form will reject submit", async function (assert) {
const self = this;
this.setProperties({
param_info: [
{
@@ -260,12 +268,15 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
},
});
await render(hbs`
<ParamInputForm
@initialValues={{this.initialValues}}
@paramInfo={{this.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>`);
await render(
<template>
<ParamInputForm
@initialValues={{self.initialValues}}
@paramInfo={{self.param_info}}
@onRegisterApi={{self.onRegisterApi}}
/>
</template>
);
assert.rejects(this.submit());
@@ -277,6 +288,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
});
test("async normalization", async function (assert) {
const self = this;
this.setProperties({
param_info: [
{
@@ -292,12 +305,15 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
},
});
await render(hbs`
<ParamInputForm
@initialValues={{this.initialValues}}
@paramInfo={{this.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>`);
await render(
<template>
<ParamInputForm
@initialValues={{self.initialValues}}
@paramInfo={{self.param_info}}
@onRegisterApi={{self.onRegisterApi}}
/>
</template>
);
await this.paramInputApi.allNormalized;
@@ -307,6 +323,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
});
test("show error message when default value is invalid", async function (assert) {
const self = this;
this.setProperties({
param_info: [
{
@@ -320,12 +338,15 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
onRegisterApi: () => {},
});
await render(hbs`
<ParamInputForm
@initialValues={{this.initialValues}}
@paramInfo={{this.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>`);
await render(
<template>
<ParamInputForm
@initialValues={{self.initialValues}}
@paramInfo={{self.param_info}}
@onRegisterApi={{self.onRegisterApi}}
/>
</template>
);
assert
.form()
@@ -334,6 +355,8 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
});
test("date, time, datetime with initial value in other formats", async function (assert) {
const self = this;
this.setProperties({
param_info: [
{
@@ -365,12 +388,15 @@ module("Data Explorer Plugin | Component | param-input", function (hooks) {
},
});
await render(hbs`
<ParamInputForm
@initialValues={{this.initialValues}}
@paramInfo={{this.param_info}}
@onRegisterApi={{this.onRegisterApi}}
/>`);
await render(
<template>
<ParamInputForm
@initialValues={{self.initialValues}}
@paramInfo={{self.param_info}}
@onRegisterApi={{self.onRegisterApi}}
/>
</template>
);
this.submit().then((res) => {
assert.deepEqual(res, {