FEATURE: Assignments summary tab for groups (#70)
This commit is contained in:
parent
f5e53f8d22
commit
ae83f70e21
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
resource: "group",
|
||||
map() {
|
||||
this.route("assignments", function() {
|
||||
this.route("show", { path: "/:filter" });
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
{{group-assignments-menu-item group = group}}
|
|
@ -0,0 +1,18 @@
|
|||
import UserTopicsList from "discourse/controllers/user-topics-list";
|
||||
|
||||
export default UserTopicsList.extend({
|
||||
user: Ember.inject.controller(),
|
||||
taskActions: Ember.inject.service(),
|
||||
|
||||
actions: {
|
||||
unassign(topic) {
|
||||
this.taskActions
|
||||
.unassign(topic.get("id"))
|
||||
.then(() => this.send("changeAssigned"));
|
||||
},
|
||||
reassign(topic) {
|
||||
const controller = this.taskActions.assign(topic);
|
||||
controller.set("model.onSuccess", () => this.send("changeAssigned"));
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
|
||||
export default Controller.extend({
|
||||
application: controller(),
|
||||
loading: false,
|
||||
|
||||
findMembers(refresh) {
|
||||
if (this.loading || !this.model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!refresh && this.model.members.length >= this.model.user_count) {
|
||||
this.set("application.showFooter", true);
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("loading", true);
|
||||
this.model
|
||||
.findMembers({ order: "", asc: true, filter: null }, refresh)
|
||||
.finally(() => {
|
||||
this.setProperties({
|
||||
"application.showFooter":
|
||||
this.model.members.length >= this.model.user_count,
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore: function() {
|
||||
this.findMembers();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
model(params) {
|
||||
let filter = null;
|
||||
if (params.filter !== "everyone") {
|
||||
filter = `topics/messages-assigned/${params.filter}`;
|
||||
} else {
|
||||
filter = `topics/group-topics-assigned/${this.modelFor("group").get(
|
||||
"name"
|
||||
)}`;
|
||||
}
|
||||
return this.store.findFiltered("topicList", {
|
||||
filter: filter
|
||||
});
|
||||
},
|
||||
|
||||
renderTemplate() {
|
||||
this.render("group-topics-list");
|
||||
},
|
||||
|
||||
actions: {
|
||||
changeAssigned() {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import Route from "@ember/routing/route";
|
||||
|
||||
export default Route.extend({
|
||||
model() {
|
||||
return this.modelFor("group");
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.setProperties({
|
||||
model,
|
||||
showing: "members"
|
||||
});
|
||||
|
||||
controller.findMembers(true);
|
||||
},
|
||||
|
||||
redirect(model, transition) {
|
||||
if (transition.to.params.hasOwnProperty("filter")) {
|
||||
this.transitionTo("group.assignments.show", transition.to.params.filter);
|
||||
} else {
|
||||
this.transitionTo("group.assignments.show", "everyone");
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
tagName: "li"
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
export default Ember.Component.extend({
|
||||
canAssign: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.set("canAssign", this.currentUser.can_assign);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
{{#if show-avatar}}
|
||||
{{#link-to "group.assignments.show" filter.username_lower}}
|
||||
{{avatar filter avatarTemplatePath="avatar_template" usernamePath="username" imageSize="small"}} {{filter.username}}
|
||||
{{/link-to}}
|
||||
{{else}}
|
||||
{{#link-to "group.assignments.show" filter}}
|
||||
{{i18n 'discourse_assign.group_everyone'}}
|
||||
{{/link-to}}
|
||||
{{/if}}
|
|
@ -0,0 +1,9 @@
|
|||
{{#if canAssign}}
|
||||
<ul class ='nav-pills'>
|
||||
<li>
|
||||
{{#link-to 'group.assignments' group}}
|
||||
{{d-icon "user-plus" class="glyph"}}{{i18n 'discourse_assign.group_assignments'}}{{concat ' (' group.assignment_count concat ')'}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
</ul>
|
||||
{{/if}}
|
|
@ -0,0 +1,18 @@
|
|||
{{#load-more class="paginated-topics-list" selector=".paginated-topics-list .topic-list tr" action=(action "loadMore")}}
|
||||
{{basic-assigned-topic-list topicList=model
|
||||
hideCategory=hideCategory
|
||||
showPosters=showPosters
|
||||
bulkSelectEnabled=bulkSelectEnabled
|
||||
selected=selected
|
||||
hasIncoming=hasIncoming
|
||||
incomingCount=incomingCount
|
||||
showInserted=(action "showInserted")
|
||||
tagsForUser=tagsForUser
|
||||
unassign=(action 'unassign')
|
||||
reassign=(action 'reassign')}}
|
||||
|
||||
{{conditional-loading-spinner condition=model.loadingMore}}
|
||||
{{/load-more}}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{{log model}}
|
||||
<section class="user-secondary-navigation">
|
||||
{{#mobile-nav class="activity-nav" desktopClass="action-list activity-list nav-stacked" currentPath=router._router.currentPath}}
|
||||
{{#load-more selector=".activity-nav li" action=(action "loadMore")}}
|
||||
{{group-assignments-filter show-avatar=false filter="everyone" routeType=route_type}}
|
||||
{{#each model.members as |member|}}
|
||||
{{group-assignments-filter show-avatar=true filter=member routeType=route_type}}
|
||||
{{/each}}
|
||||
{{conditional-loading-spinner condition=loading}}
|
||||
{{/load-more}}
|
||||
{{/mobile-nav}}
|
||||
</section>
|
||||
<section class="user-content">
|
||||
{{outlet}}
|
||||
</section>
|
|
@ -12,6 +12,8 @@ en:
|
|||
cant_act: "You cannot act on flags that have been assigned to other users"
|
||||
cant_act_unclaimed: "You must claim this topic before acting on flags."
|
||||
assigned: "Assigned"
|
||||
group_assignments: "Assignments"
|
||||
group_everyone: "Everyone"
|
||||
assigned_to: "Assigned to"
|
||||
assign_notification: "<p><span>%{username}</span> %{description}</p>"
|
||||
unassign:
|
||||
|
|
|
@ -20,7 +20,7 @@ en:
|
|||
already_claimed: "That topic has already been claimed."
|
||||
already_assigned: 'Topic is already assigned to @%{username}'
|
||||
too_many_assigns: "@%{username} has already reached the maximum number of assigned topics (%{max})."
|
||||
forbidden_assign_to: "@%{username} can't be assigned since they don't have access to this topic."
|
||||
forbidden_assign_to: "@%{username} can't be assigned since they don't belong to assigned allowed groups."
|
||||
flag_assigned: "Sorry, that flag's topic is assigned to another user"
|
||||
flag_unclaimed: "You must claim that topic before acting on the flag"
|
||||
topic_assigned_excerpt: "assigned you the topic '%{title}'"
|
||||
|
|
66
plugin.rb
66
plugin.rb
|
@ -20,7 +20,10 @@ load File.expand_path('../lib/discourse_assign/helpers.rb', __FILE__)
|
|||
Discourse::Application.routes.append do
|
||||
mount ::DiscourseAssign::Engine, at: "/assign"
|
||||
get "topics/private-messages-assigned/:username" => "list#private_messages_assigned", as: "topics_private_messages_assigned", constraints: { username: ::RouteFormat.username }
|
||||
get "topics/messages-assigned/:username" => "list#messages_assigned", as: "topics_messages_assigned", constraints: { username: ::RouteFormat.username }
|
||||
get "/topics/messages-assigned/:username" => "list#messages_assigned", constraints: { username: ::RouteFormat.username }
|
||||
get "/topics/group-topics-assigned/:groupname" => "list#group_topics_assigned", constraints: { username: ::RouteFormat.username }
|
||||
get "/g/:id/assignments" => "groups#index"
|
||||
get "/g/:id/assignments/:route_type" => "groups#index"
|
||||
end
|
||||
|
||||
after_initialize do
|
||||
|
@ -36,6 +39,16 @@ after_initialize do
|
|||
add_to_serializer(:user, :reminders_frequency) do
|
||||
RemindAssignsFrequencySiteSettings.values
|
||||
end
|
||||
|
||||
add_to_serializer(:group_show, :assignment_count) do
|
||||
Topic.joins("JOIN topic_custom_fields tcf ON topics.id = tcf.topic_id AND tcf.name = 'assigned_to_id' AND tcf.value IS NOT NULL")
|
||||
.where("tcf.value IN (SELECT group_users.user_id::varchar(255) FROM group_users WHERE (group_id IN (SELECT id FROM groups WHERE name = ?)))", object.name).count
|
||||
end
|
||||
|
||||
add_to_serializer(:group_show, 'include_assignment_count?') do
|
||||
scope.can_assign?
|
||||
end
|
||||
|
||||
add_model_callback(UserCustomField, :before_save) do
|
||||
self.value = self.value.to_i if self.name == frequency_field
|
||||
end
|
||||
|
@ -186,19 +199,66 @@ after_initialize do
|
|||
require_dependency 'list_controller'
|
||||
class ::ListController
|
||||
generate_message_route(:private_messages_assigned)
|
||||
generate_message_route(:messages_assigned)
|
||||
end
|
||||
|
||||
add_to_class(:topic_query, :list_messages_assigned) do |user|
|
||||
secure = Topic.listable_topics.secured(@guardian).or(Topic.private_messages_for_user(@user))
|
||||
list = joined_topic_user.where("
|
||||
topics.id IN (
|
||||
SELECT topic_id FROM topic_custom_fields
|
||||
WHERE name = 'assigned_to_id'
|
||||
AND value = ?)
|
||||
", user.id.to_s)
|
||||
.limit(per_page_setting)
|
||||
.offset(per_page_setting * options[:page])
|
||||
.order("topics.bumped_at DESC")
|
||||
list = list.merge(secure)
|
||||
|
||||
create_list(:assigned, {}, list)
|
||||
create_list(:assigned, { unordered: true }, list)
|
||||
end
|
||||
|
||||
add_to_class(:list_controller, :messages_assigned) do
|
||||
page = (params[:page].to_i || 0).to_i
|
||||
|
||||
user = User.find_by_username(params[:username])
|
||||
raise Discourse::NotFound unless user
|
||||
raise Discourse::InvalidAccess unless current_user.can_assign?
|
||||
|
||||
list_opts = build_topic_list_options
|
||||
list_opts[:page] = page
|
||||
list = generate_list_for("messages_assigned", user, list_opts)
|
||||
list.more_topics_url = "/topics/messages-assigned/#{params[:username]}.json?page=#{page + 1}"
|
||||
respond_with_list(list)
|
||||
end
|
||||
|
||||
add_to_class(:topic_query, :list_group_topics_assigned) do |group|
|
||||
secure = Topic.listable_topics.secured(@guardian).or(Topic.private_messages_for_user(@user))
|
||||
list = joined_topic_user.where("
|
||||
topics.id IN (
|
||||
SELECT topic_id FROM topic_custom_fields
|
||||
WHERE name = 'assigned_to_id'
|
||||
AND value IN (SELECT user_id::varchar(255) from group_users where group_id = ?))
|
||||
", group.id.to_s)
|
||||
.limit(per_page_setting)
|
||||
.offset(per_page_setting * options[:page])
|
||||
.order("topics.bumped_at DESC")
|
||||
list = list.merge(secure)
|
||||
|
||||
create_list(:assigned, { unordered: true }, list)
|
||||
end
|
||||
|
||||
add_to_class(:list_controller, :group_topics_assigned) do
|
||||
page = (params[:page].to_i || 0).to_i
|
||||
|
||||
group = Group.find_by("LOWER(name) = ?", params[:groupname])
|
||||
raise Discourse::NotFound unless group
|
||||
raise Discourse::InvalidAccess unless current_user.can_assign?
|
||||
|
||||
list_opts = build_topic_list_options
|
||||
list_opts[:page] = page
|
||||
list = generate_list_for("group_topics_assigned", group, list_opts)
|
||||
list.more_topics_url = "/topics/group-topics-assigned/#{params[:groupname]}.json?page=#{page + 1}"
|
||||
respond_with_list(list)
|
||||
end
|
||||
|
||||
add_to_class(:topic_query, :list_private_messages_assigned) do |user|
|
||||
|
|
|
@ -28,13 +28,13 @@ describe TopicQuery do
|
|||
end
|
||||
|
||||
it 'Includes topics and PMs assigned to user' do
|
||||
assigned_messages = TopicQuery.new(user).list_messages_assigned(user).topics
|
||||
assigned_messages = TopicQuery.new(user, { page: 0 }).list_messages_assigned(user).topics
|
||||
|
||||
expect(assigned_messages).to contain_exactly(@private_message, @topic)
|
||||
end
|
||||
|
||||
it 'Excludes topics and PMs not assigned to user' do
|
||||
assigned_messages = TopicQuery.new(user2).list_messages_assigned(user2).topics
|
||||
assigned_messages = TopicQuery.new(user2, { page: 0 }).list_messages_assigned(user2).topics
|
||||
|
||||
expect(assigned_messages).to be_empty
|
||||
end
|
||||
|
@ -42,7 +42,32 @@ describe TopicQuery do
|
|||
it 'Returns the results ordered by the bumped_at field' do
|
||||
@topic.update(bumped_at: 2.weeks.ago)
|
||||
|
||||
assigned_messages = TopicQuery.new(user).list_messages_assigned(user).topics
|
||||
assigned_messages = TopicQuery.new(user, { page: 0 }).list_messages_assigned(user).topics
|
||||
|
||||
expect(assigned_messages).to eq([@private_message, @topic])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#list_group_topics_assigned' do
|
||||
|
||||
before do
|
||||
@private_message = Fabricate(:private_message_topic, user: user)
|
||||
@topic = Fabricate(:topic, user: user)
|
||||
|
||||
assign_to(@private_message, user)
|
||||
assign_to(@topic, user2)
|
||||
end
|
||||
|
||||
it 'Includes topics and PMs assigned to user' do
|
||||
assigned_messages = TopicQuery.new(user, { page: 0 }).list_group_topics_assigned(assign_allowed_group).topics
|
||||
|
||||
expect(assigned_messages).to contain_exactly(@private_message, @topic)
|
||||
end
|
||||
|
||||
it 'Returns the results ordered by the bumped_at field' do
|
||||
@topic.update(bumped_at: 2.weeks.ago)
|
||||
|
||||
assigned_messages = TopicQuery.new(user, { page: 0 }).list_group_topics_assigned(assign_allowed_group).topics
|
||||
|
||||
expect(assigned_messages).to eq([@private_message, @topic])
|
||||
end
|
||||
|
|
|
@ -8,4 +8,8 @@ shared_context 'A group that is allowed to assign' do
|
|||
def add_to_assign_allowed_group(user)
|
||||
assign_allowed_group.add(user)
|
||||
end
|
||||
|
||||
def get_assigned_allowed_group()
|
||||
assign_allowed_group
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
import { default as AssignedTopics } from "../fixtures/assigned-group-assignments-fixtures";
|
||||
|
||||
acceptance("GroupAssignments", {
|
||||
loggedIn: true,
|
||||
settings: { assign_enabled: true, assigns_user_url_path: "/" },
|
||||
pretend(server, helper) {
|
||||
const groupPath = "/topics/group-topics-assigned/discourse.json";
|
||||
const memberPath = "/topics/messages-assigned/awesomerobot.json";
|
||||
const groupAssigns = AssignedTopics[groupPath];
|
||||
const memberAssigns = AssignedTopics[memberPath];
|
||||
server.get(groupPath, () => helper.response(groupAssigns));
|
||||
server.get(memberPath, () => helper.response(memberAssigns));
|
||||
}
|
||||
});
|
||||
QUnit.test("Group Assignments Everyone", async assert => {
|
||||
await visit("/g/discourse/assignments");
|
||||
assert.equal(currentPath(), "group.assignments.show");
|
||||
assert.ok(find(".topic-list-item").length === 1);
|
||||
});
|
||||
|
||||
QUnit.test("Group Assignments Awesomerobot", async assert => {
|
||||
await visit("/g/discourse/assignments/awesomerobot");
|
||||
assert.equal(currentPath(), "group.assignments.show");
|
||||
assert.ok(find(".topic-list-item").length === 1);
|
||||
});
|
|
@ -0,0 +1,170 @@
|
|||
export default {
|
||||
"/topics/group-topics-assigned/discourse.json": {
|
||||
users: [
|
||||
{
|
||||
id: -2,
|
||||
username: "discobot",
|
||||
name: "discobot",
|
||||
avatar_template: "/user_avatar/localhost/discobot/{size}/1_2.png"
|
||||
},
|
||||
{
|
||||
id: -1,
|
||||
username: "system",
|
||||
name: "system",
|
||||
avatar_template: "/user_avatar/localhost/system/{size}/2_2.png"
|
||||
}
|
||||
],
|
||||
primary_groups: [],
|
||||
topic_list: {
|
||||
can_create_topic: true,
|
||||
draft: null,
|
||||
draft_key: "new_topic",
|
||||
draft_sequence: 0,
|
||||
per_page: 30,
|
||||
topics: [
|
||||
{
|
||||
id: 10,
|
||||
title: "Greetings!",
|
||||
fancy_title: "Greetings!",
|
||||
slug: "greetings",
|
||||
posts_count: 1,
|
||||
reply_count: 0,
|
||||
highest_post_number: 4,
|
||||
image_url:
|
||||
"//localhost:3000/plugins/discourse-narrative-bot/images/font-awesome-ellipsis.png",
|
||||
created_at: "2019-05-08T13:52:39.394Z",
|
||||
last_posted_at: "2019-05-08T13:52:39.841Z",
|
||||
bumped: true,
|
||||
bumped_at: "2019-05-08T13:52:39.841Z",
|
||||
unseen: false,
|
||||
last_read_post_number: 4,
|
||||
unread: 0,
|
||||
new_posts: 0,
|
||||
pinned: false,
|
||||
unpinned: null,
|
||||
visible: true,
|
||||
closed: false,
|
||||
archived: false,
|
||||
notification_level: 3,
|
||||
bookmarked: false,
|
||||
liked: false,
|
||||
views: 0,
|
||||
like_count: 0,
|
||||
has_summary: false,
|
||||
archetype: "private_message",
|
||||
last_poster_username: "discobot",
|
||||
category_id: null,
|
||||
pinned_globally: false,
|
||||
featured_link: null,
|
||||
posters: [
|
||||
{
|
||||
extras: "latest single",
|
||||
description: "Original Poster, Most Recent Poster",
|
||||
user_id: -2,
|
||||
primary_group_id: null
|
||||
}
|
||||
],
|
||||
participants: [
|
||||
{
|
||||
extras: "latest",
|
||||
description: null,
|
||||
user_id: -2,
|
||||
primary_group_id: null
|
||||
}
|
||||
],
|
||||
assigned_to_user: {
|
||||
id: 19,
|
||||
username: "eviltrout",
|
||||
name: null,
|
||||
avatar_template:
|
||||
"/letter_avatar_proxy/v4/letter/r/ed8c4c/{size}.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/topics/messages-assigned/awesomerobot.json": {
|
||||
users: [
|
||||
{
|
||||
id: -2,
|
||||
username: "discobot",
|
||||
name: "discobot",
|
||||
avatar_template: "/user_avatar/localhost/discobot/{size}/1_2.png"
|
||||
},
|
||||
{
|
||||
id: -1,
|
||||
username: "system",
|
||||
name: "system",
|
||||
avatar_template: "/user_avatar/localhost/system/{size}/2_2.png"
|
||||
}
|
||||
],
|
||||
primary_groups: [],
|
||||
topic_list: {
|
||||
can_create_topic: true,
|
||||
draft: null,
|
||||
draft_key: "new_topic",
|
||||
draft_sequence: 0,
|
||||
per_page: 30,
|
||||
topics: [
|
||||
{
|
||||
id: 10,
|
||||
title: "Greetings!",
|
||||
fancy_title: "Greetings!",
|
||||
slug: "greetings",
|
||||
posts_count: 1,
|
||||
reply_count: 0,
|
||||
highest_post_number: 4,
|
||||
image_url:
|
||||
"//localhost:3000/plugins/discourse-narrative-bot/images/font-awesome-ellipsis.png",
|
||||
created_at: "2019-05-08T13:52:39.394Z",
|
||||
last_posted_at: "2019-05-08T13:52:39.841Z",
|
||||
bumped: true,
|
||||
bumped_at: "2019-05-08T13:52:39.841Z",
|
||||
unseen: false,
|
||||
last_read_post_number: 4,
|
||||
unread: 0,
|
||||
new_posts: 0,
|
||||
pinned: false,
|
||||
unpinned: null,
|
||||
visible: true,
|
||||
closed: false,
|
||||
archived: false,
|
||||
notification_level: 3,
|
||||
bookmarked: false,
|
||||
liked: false,
|
||||
views: 0,
|
||||
like_count: 0,
|
||||
has_summary: false,
|
||||
archetype: "private_message",
|
||||
last_poster_username: "discobot",
|
||||
category_id: null,
|
||||
pinned_globally: false,
|
||||
featured_link: null,
|
||||
posters: [
|
||||
{
|
||||
extras: "latest single",
|
||||
description: "Original Poster, Most Recent Poster",
|
||||
user_id: -2,
|
||||
primary_group_id: null
|
||||
}
|
||||
],
|
||||
participants: [
|
||||
{
|
||||
extras: "latest",
|
||||
description: null,
|
||||
user_id: -2,
|
||||
primary_group_id: null
|
||||
}
|
||||
],
|
||||
assigned_to_user: {
|
||||
id: 2770,
|
||||
username: "awesomerobot",
|
||||
name: null,
|
||||
avatar_template:
|
||||
"/user_avatar/meta.discourse.org/awesomerobot/{size}/33872.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue