DEV: Update linting config and run gjs-codemod (#376)
This commit is contained in:
@@ -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
@@ -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>
|
||||
+9
@@ -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>
|
||||
+38
@@ -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}}
|
||||
+10
@@ -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>
|
||||
+96
@@ -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}}
|
||||
+14
@@ -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>
|
||||
+12
@@ -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>
|
||||
+8
@@ -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}}
|
||||
+13
@@ -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}}
|
||||
+2
@@ -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}}
|
||||
+38
@@ -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>
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Generated
+435
-437
File diff suppressed because it is too large
Load Diff
+12
-7
@@ -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");
|
||||
|
||||
+59
-33
@@ -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, {
|
||||
Reference in New Issue
Block a user