Skip to content
Permalink
Browse files

Allow groups to access queries (#36)

* [WIP] group ids saving on new reports

* Add groups to default queries, and added tab connector

* group_ids set to empty array for default queries

* group reports route (in & and) action

* [WIP] created group reports show route/controller

* Find correct query in show route

* Removed empty array for group_ids in query file

* Add report show view, where users can run queries

* Removed unneeded commas from queries.rb

* Allow non-admin group members to access reports

* query-result component dynamic download url based on location

* Removed accidental changes, and corrected tab size

* Group members can add params to queries

* Specs for new QueryController actions

* remove "Inlude query plan" from group reports

* Run prettier

* return and return -> return render

Co-Authored-By: Robin Ward <robin.ward@gmail.com>

* [WIP] changes from review

* Remove weird [-1] group_ids logic, for a simply check for [] in query update action

* Added integration tests for group report access

* Using guardian for securing endpoints, and much improved specs

* Update assets/javascripts/discourse/components/group-reports-nav-item.js.es6

Co-Authored-By: Robin Ward <robin.ward@gmail.com>
  • Loading branch information...
markvanlan and eviltrout committed Sep 11, 2019
1 parent 677722d commit 30fe9289b86dfe62deb83f67a948f6bc8325a8a6
@@ -0,0 +1,21 @@
import { ajax } from "discourse/lib/ajax";

export default Ember.Component.extend({
group: null,
showReportsTab: false,

checkForReports() {
return ajax(`/g/${this.group.name}/reports`).then(response => {
return this.set("showReportsTab", response.queries.length > 0);
});
},

init(args) {
this.set("group", args.group);
if (this.currentUser.groups.some(g => g.id === this.group.id)) {
// User is a part of the group. Now check if the group has reports
this.checkForReports();
}
this._super(args);
}
});
@@ -146,6 +146,12 @@ const QueryResultComponent = Ember.Component.extend({
return this.site.get("categoriesById")[id];
},

download_url() {
return this.group
? `/g/${this.group.name}/reports/`
: "/admin/plugins/explorer/queries/";
},

downloadResult(format) {
// Create a frame to submit the form in (?)
// to avoid leaving an about:blank behind
@@ -161,7 +167,7 @@ const QueryResultComponent = Ember.Component.extend({
form.setAttribute(
"action",
Discourse.getURL(
"/admin/plugins/explorer/queries/" +
this.download_url() +
this.get("query.id") +
"/run." +
format +
@@ -54,6 +54,22 @@ export default Ember.Controller.extend({
return item || NoQuery;
},

@computed("selectedItem", "editing")
selectedGroupNames(selectedItem) {
const groupIds = this.selectedItem.group_ids || [];
const groupNames = groupIds.map(id => {
return this.groupOptions.find(groupOption => groupOption.id == id).name;
});
return groupNames.join(", ");
},

@computed("groups")
groupOptions(groups) {
return groups.arrangedContent.map(g => {
return { id: g.id.toString(), name: g.name };
});
},

@computed("selectedItem", "selectedItem.dirty")
othersDirty(selectedItem) {
return !!this.model.find(q => q !== selectedItem && q.dirty);
@@ -81,6 +97,7 @@ export default Ember.Controller.extend({
this.set("loading", true);
if (this.get("selectedItem.description") === "")
this.set("selectedItem.description", "");

return this.selectedItem
.save()
.then(() => {
@@ -183,6 +200,8 @@ export default Ember.Controller.extend({
.then(result => {
const query = this.get("selectedItem");
query.setProperties(result.getProperties(Query.updatePropertyNames));
if (!query.group_ids || !Array.isArray(query.group_ids))
query.set("group_ids", []);
query.markNotDirty();
this.set("editing", false);
})
@@ -0,0 +1,10 @@
import Query from "discourse/plugins/discourse-data-explorer/discourse/models/query";
import { ajax } from "discourse/lib/ajax";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";

export default Ember.Controller.extend({
queries: Ember.computed.alias("model.queries")
});
@@ -0,0 +1,44 @@
import Query from "discourse/plugins/discourse-data-explorer/discourse/models/query";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { ajax } from "discourse/lib/ajax";
import {
default as computed,
observes
} from "ember-addons/ember-computed-decorators";

export default Ember.Controller.extend({
showResults: false,
explain: false,
loading: false,
results: Ember.computed.alias("model.results"),
hasParams: Ember.computed.gt("model.param_info.length", 0),

actions: {
run() {
this.setProperties({ loading: true, showResults: false });
ajax(`/g/${this.get("group.name")}/reports/${this.model.id}/run`, {
type: "POST",
data: {
params: JSON.stringify(this.model.params),
explain: this.explain
}
})
.then(result => {
this.set("results", result);
if (!result.success) {
return;
}

this.set("showResults", true);
})
.catch(err => {
if (err.jqXHR && err.jqXHR.status === 422 && err.jqXHR.responseJSON) {
this.set("results", err.jqXHR.responseJSON);
} else {
popupAjaxError(err);
}
})
.finally(() => this.set("loading", false));
}
}
});
@@ -0,0 +1,9 @@
export default {
resource: "group",

map() {
this.route("reports", function() {
this.route("show", { path: "/:query_id" });
});
}
};
@@ -23,7 +23,7 @@ const Query = RestModel.extend({
this.resetParams();
},

@observes("name", "description", "sql")
@observes("name", "description", "sql", "group_ids")
markDirty() {
this.set("dirty", true);
},
@@ -85,6 +85,7 @@ Query.reopenClass({
"sql",
"created_by",
"created_at",
"group_ids",
"last_run_at"
]
});
@@ -4,19 +4,36 @@ export default Discourse.Route.extend({
controllerName: "admin-plugins-explorer",

model() {
const p1 = this.store.findAll("query");
const p2 = ajax("/admin/plugins/explorer/schema.json", { cache: true });
return p1
.then(model => {
model.forEach(query => query.markNotDirty());
const groupPromise = this.store.findAll("group");
const schemaPromise = ajax("/admin/plugins/explorer/schema.json", { cache: true });
const queryPromise = this.store.findAll("query");

return p2.then(schema => {
return { model, schema };
return groupPromise
.then(groups => {
let groupNames = {};
groups.forEach(g => {
groupNames[g.id] = g.name;
});
return schemaPromise.then(schema => {
return queryPromise.then(model => {
model.forEach(query => {
query.markNotDirty();
query.set(
"group_names",
query.group_ids
.map(id => groupNames[id])
.filter(n => n)
.join(", ")
);
});
return { model, schema, groups };
});
});
})
.catch(() => {
p2.catch(() => {});
return { model: null, schema: null, disallow: true };
schemaPromise.catch(() => {});
queryPromise.catch(() => {});
return { model: null, schema: null, disallow: true, groups: null };
});
},

@@ -0,0 +1,38 @@
import { ajax } from "discourse/lib/ajax";

export default Discourse.Route.extend({
controllerName: "group-reports-index",

model() {
const group = this.modelFor("group");
return ajax(`/g/${group.name}/reports`)
.then(queries => {
return {
model: queries,
group: group
};
})
.catch(() => {
this.transitionTo("group.members", group);
});
},
afterModel(model) {
if (
!model.group.get("is_group_user") &&
!(this.currentUser && this.currentUser.admin)
) {
this.transitionTo("group.members", group);
}
},

setupController(controller, model) {
controller.setProperties(model);
},

actions: {
refreshModel() {
this.refresh();
return false;
}
}
});
@@ -0,0 +1,30 @@
import { ajax } from "discourse/lib/ajax";

export default Discourse.Route.extend({
controllerName: "group-reports-show",

model(params) {
const group = this.modelFor("group");
return ajax(`/g/${group.name}/reports/${params.query_id}`)
.then(response => {
return {
model: Object.assign({ params: {} }, response.query),
group: group
};
})
.catch(err => {
this.transitionTo("group.members", group);
});
},

setupController(controller, model) {
controller.setProperties(model);
},

actions: {
refreshModel() {
this.refresh();
return false;
}
}
});
@@ -38,6 +38,7 @@
<div class="desc">
{{textarea value=selectedItem.description placeholder=(i18n "explorer.description_placeholder")}}
</div>

{{else}}
<div class="name">
{{d-button action=(action "goHome") icon="chevron-left" class="previous"}}
@@ -52,6 +53,20 @@
</div>
{{/if}}

<div class="groups">
<span class="label">Allow groups to acess this query</span>
<span>
{{multi-select values=selectedItem.group_ids content=groupOptions}}
</span>
{{#if runDisabled}}
{{#unless editing}}
<span class='setting-controls'>
{{d-button class="ok" action=(action "save") icon="check"}}
{{d-button class="cancel" action=(action "discard") icon="times"}}
</span>
{{/unless}}
{{/if}}
</div>
{{! the SQL editor will show the first time you }}
{{#if everEditing}}
<div class="query-editor {{if hideSchema "no-schema"}}">
@@ -158,6 +173,11 @@
{{directory-toggle field="username" labelKey="explorer.query_user" order=order asc=asc}}
</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 class="heading-toggle" {{action "sortByProperty" "last_run_at"}}>
{{directory-toggle field="last_run_at" labelKey="explorer.query_time" order=order asc=asc}}
@@ -181,6 +201,11 @@
</a>
{{/if}}
</td>
<td class="query-group-names">
{{#if query.group_names}}
<medium>{{query.group_names}}</medium>
{{/if}}
</td>
<td class="query-created-at">
{{#if query.last_run_at}}
<medium>
@@ -0,0 +1,9 @@
{{#if showReportsTab}}
<ul class ='nav-pills'>
<li>
{{#link-to 'group.reports'}}
{{i18n 'group.reports'}}
{{/link-to}}
</li>
</ul>
{{/if}}
@@ -0,0 +1 @@
{{group-reports-nav-item group = group}}
@@ -1,6 +1,6 @@
<div class="result-info">
{{d-button action=(action "downloadResultJson") icon="download" label="explorer.download_json"}}
{{d-button action=(action "downloadResultCsv") icon="download" label="explorer.download_csv"}}
{{d-button action=(action "downloadResultJson") icon="download" label="explorer.download_json" group=group}}
{{d-button action=(action "downloadResultCsv") icon="download" label="explorer.download_csv" group=group}}
</div>

<div class="result-about">
@@ -0,0 +1,27 @@
<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>
<tr></tr>
<tbody>
{{#each queries as |query|}}
<tr>
<td>
{{#link-to 'group.reports.show' group.name query.id}}{{query.name}}{{/link-to}}
</td>
<td>{{query.description}}</td>
<td>{{bound-date query.last_run_at}}</td>
</tr>
{{/each}}
</tbody>
</table>
</section>

0 comments on commit 30fe928

Please sign in to comment.
You can’t perform that action at this time.