FEATURE: Display PMs in assigned activity and differentiate them. (#27)

* FEATURE: Display PMs in assigned activity and differentiate them.

* FEATURE: Unassign/Re-assign tasks from the activity view

* Remove bulkAssign. Reuse assign/unassign with a service. Change PM icon position

* Reuse ListItemDefaults instead of duplicating code.

* Conditionally show/hide feature if list items defaults is present in core
This commit is contained in:
Roman Rizzi 2019-05-07 03:30:47 -03:00 committed by Sam
parent f5a19501cc
commit e3b2e5bdb6
19 changed files with 310 additions and 39 deletions

View File

@ -1,5 +1,4 @@
import showModal from "discourse/lib/show-modal";
import { ajax } from "discourse/lib/ajax";
import { getOwner } from "discourse-common/lib/get-owner";
export default {
shouldRender(args, component) {
@ -11,22 +10,18 @@ export default {
);
},
setupComponent(args, component) {
const taskActions = getOwner(this).lookup("service:task-actions");
component.set("taskActions", taskActions);
},
actions: {
unassign() {
this.set("topic.assigned_to_user", null);
return ajax("/assign/unassign", {
type: "PUT",
data: { topic_id: this.get("topic.id") }
});
this.get("taskActions").unassign(this.get("topic.id"));
},
assign() {
showModal("assign-user", {
model: {
topic: this.topic,
username: this.topic.get("assigned_to_user.username")
}
});
this.get("taskActions").assign(this.topic);
}
}
};

View File

@ -44,7 +44,9 @@ export default Ember.Controller.extend({
}
})
.then(() => {
// done
if (this.get("model.onSuccess")) {
this.get("model.onSuccess")();
}
})
.catch(popupAjaxError);
}

View File

@ -1,8 +1,10 @@
import { ajax } from "discourse/lib/ajax";
import computed from "ember-addons/ember-computed-decorators";
import UserTopicsList from "discourse/controllers/user-topics-list";
export default Ember.Controller.extend({
export default UserTopicsList.extend({
user: Ember.inject.controller(),
taskActions: Ember.inject.service(),
@computed("model.topics")
canUnassignAll(topics) {
@ -21,10 +23,19 @@ export default Ember.Controller.extend({
ajax("/assign/unassign-all", {
type: "PUT",
data: { user_id: user.get("id") }
}).then(() => this.send("unassignedAll"));
}).then(() => this.send("changeAssigned"));
}
}
);
},
unassign(topic) {
this.get("taskActions")
.unassign(topic.get("id"))
.then(() => this.send("changeAssigned"));
},
reassign(topic) {
const controller = this.get("taskActions").assign(topic);
controller.set("model.onSuccess", () => this.send("changeAssigned"));
}
}
});

View File

@ -8,6 +8,9 @@ import { iconNode } from "discourse-common/lib/icon-library";
import { h } from "virtual-dom";
import { iconHTML } from "discourse-common/lib/icon-library";
// TODO: This has to be removed when 2.3 becomes the new stable version.
import { ListItemDefaults } from "discourse/components/topic-list-item";
const ACTION_ID = "assign";
function modifySelectKit(api) {
@ -138,9 +141,18 @@ function initialize(api) {
const assignedTo = topic.get("assigned_to_user.username");
if (assignedTo) {
const assignedPath = topic.get("assignedToUserPath");
return `<a data-auto-route='true' class='assigned-to discourse-tag simple' href='${assignedPath}'>${iconHTML(
let assignLabels = `<a data-auto-route='true' class='assigned-to discourse-tag simple' href='${assignedPath}'>${iconHTML(
"user-plus"
)}${assignedTo}</a>`;
if (
ListItemDefaults === undefined &&
topic.get("archetype") === "private_message"
) {
assignLabels += `<div>${iconHTML("envelope")} Message</div>`;
}
return assignLabels;
}
});

View File

@ -1,4 +1,5 @@
import UserTopicListRoute from "discourse/routes/user-topic-list";
import { ListItemDefaults } from "discourse/components/topic-list-item";
export default UserTopicListRoute.extend({
userActionType: 16,
@ -6,9 +7,10 @@ export default UserTopicListRoute.extend({
model() {
return this.store.findFiltered("topicList", {
filter: "latest",
filter: `topics/messages-assigned/${this.modelFor("user").get(
"username_lower"
)}`,
params: {
assigned: this.modelFor("user").get("username_lower"),
// core is a bit odd here and is not sending an array, should be fixed
exclude_category_ids: [-1]
}
@ -16,8 +18,12 @@ export default UserTopicListRoute.extend({
},
renderTemplate() {
// TODO: This has to be removed when 2.3 becomes the new stable version.
const template = ListItemDefaults
? "user-assigned-topics"
: "user-topics-list";
this.render("user-activity-assigned");
this.render("user-topics-list", { into: "user-activity-assigned" });
this.render(template, { into: "user-activity-assigned" });
},
setupController(controller, model) {
@ -26,7 +32,7 @@ export default UserTopicListRoute.extend({
},
actions: {
unassignedAll() {
changeAssigned() {
this.refresh();
}
}

View File

@ -0,0 +1,39 @@
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
export default DropdownSelectBoxComponent.extend({
classNames: ["assign-actions-dropdown"],
headerIcon: null,
title: "...",
allowInitialValueMutation: false,
showFullTitle: true,
computeContent() {
return [
{
id: "unassign",
icon: "user-times",
name: I18n.t("discourse_assign.unassign.title"),
description: I18n.t("discourse_assign.unassign.help")
},
{
id: "reassign",
icon: "users",
name: I18n.t("discourse_assign.reassign.title"),
description: I18n.t("discourse_assign.reassign.help")
}
];
},
actions: {
onSelect(id) {
switch (id) {
case "unassign":
this.unassign(this.get("topic"), this.get("user"));
break;
case "reassign":
this.reassign(this.get("topic"), this.get("user"));
break;
}
}
}
});

View File

@ -0,0 +1,11 @@
import computed from "ember-addons/ember-computed-decorators";
import { ListItemDefaults } from "discourse/components/topic-list-item";
const privateMessageHelper = {
@computed("topic.archetype")
isPrivateMessage(archetype) {
return archetype === "private_message";
}
};
export default Ember.Component.extend(ListItemDefaults, privateMessageHelper);

View File

@ -0,0 +1,3 @@
import TopicList from "discourse/components/topic-list";
export default TopicList;

View File

@ -0,0 +1,3 @@
import BasicTopicList from "discourse/components/basic-topic-list";
export default BasicTopicList;

View File

@ -0,0 +1,20 @@
import { ajax } from "discourse/lib/ajax";
import showModal from "discourse/lib/show-modal";
export default Ember.Service.extend({
unassign(topicId) {
return ajax("/assign/unassign", {
type: "PUT",
data: { topic_id: topicId }
});
},
assign(topic) {
return showModal("assign-user", {
model: {
topic: topic,
username: topic.get("assigned_to_user.username")
}
});
}
});

View File

@ -0,0 +1,50 @@
{{!--
The `~` syntax strip spaces between the elements, making it produce
`<a class=topic-post-badges>Some text</a><span class=topic-post-badges>`,
with no space between them.
This causes the topic-post-badge to be considered the same word as "text"
at the end of the link, preventing it from line wrapping onto its own line.
--}}
<td class='main-link clearfix' colspan="1">
<span class='link-top-line'>
{{~raw "topic-status" topic=topic}}
{{~#if isPrivateMessage}}
{{~d-icon "envelope" class="private-message-icon"}}
{{~/if}}
{{~topic-link topic class="raw-link raw-topic-link"}}
{{~#if topic.featured_link}}
{{~topic-featured-link topic}}
{{~/if}}
{{~#if showTopicPostBadges}}
{{~raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl newDotText=newDotText}}
{{~/if}}
</span>
<div class="link-bottom-line">
{{#unless hideCategory}}
{{#unless topic.isPinnedUncategorized}}
{{category-link topic.category}}
{{/unless}}
{{/unless}}
{{discourse-tags topic mode="list" tagsForUser=tagsForUser}}
{{raw "list/action-list" topic=topic postNumbers=topic.liked_post_numbers className="likes" icon="heart"}}
</div>
{{#if expandPinned}}
{{raw "list/topic-excerpt" topic=topic}}
{{/if}}
</td>
{{#if showPosters}}
{{raw "list/posters-column" posters=topic.featuredUsers}}
{{/if}}
{{raw "list/posts-count-column" topic=topic}}
<td class="num views {{topic.viewsHeat}}">{{number topic.views numberKey="views_long"}}</td>
{{raw "list/activity-column" topic=topic class="num" tagName="td"}}
<td>
{{assign-actions-dropdown topic=topic
user=username
unassign=unassign
reassign=reassign}}
</td>

View File

@ -0,0 +1,34 @@
{{#unless skipHeader}}
<thead>
{{raw "topic-list-header"
toggleInTitle=toggleInTitle
hideCategory=hideCategory
showPosters=showPosters
showLikes=showLikes
showOpLikes=showOpLikes
order=order
ascending=ascending
sortable=sortable
listTitle=listTitle
bulkSelectEnabled=bulkSelectEnabled}}
</thead>
{{/unless}}
<tbody>
{{#each filteredTopics as |topic|}}
{{assigned-topic-list-item topic=topic
showTopicPostBadges=showTopicPostBadges
hideCategory=hideCategory
showPosters=showPosters
showLikes=showLikes
showOpLikes=showOpLikes
expandGloballyPinned=expandGloballyPinned
expandAllPinned=expandAllPinned
lastVisitedTopic=lastVisitedTopic
selected=selected
tagsForUser=tagsForUser
unassign=unassign
reassign=reassign}}
{{raw "list/visited-line" lastVisitedTopic=lastVisitedTopic topic=topic}}
{{/each}}
</tbody>

View File

@ -0,0 +1,29 @@
{{#conditional-loading-spinner condition=loading}}
{{#if hasIncoming}}
<div class="show-mores">
<div class='alert alert-info clickable' {{action showInserted}}>
<a tabindex="0" href="" {{action showInserted}}>
{{count-i18n key="topic_count_" suffix="latest" count=incomingCount}}
</a>
</div>
</div>
{{/if}}
{{#if topics}}
{{assigned-topic-list showPosters=showPosters
hideCategory=hideCategory
topics=topics
expandExcerpts=expandExcerpts
selected=selected
skipHeader=skipHeader
tagsForUser=tagsForUser
unassign=unassign
reassign=reassign}}
{{else}}
{{#unless loadingMore}}
<div class='alert alert-info'>
{{i18n 'choose_topic.none_found'}}
</div>
{{/unless}}
{{/if}}
{{/conditional-loading-spinner}}

View File

@ -1,11 +1,4 @@
<div class='assign-controls'>
{{#if model.topic_list.assigned_messages_count}}
{{#link-to 'userPrivateMessages.assigned' user.model class="btn btn-default assign-messages-assigned"}}
{{d-icon "envelope" class="glyph"}}
{{i18n 'user.messages.assigned_title' count=model.topic_list.assigned_messages_count}}
{{/link-to}}
{{/if}}
{{#if canUnassignAll}}
{{d-button
action=(action "unassignAll")

View File

@ -0,0 +1,17 @@
{{#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}}

View File

@ -74,3 +74,7 @@
cursor: pointer;
}
}
.private-message-icon {
margin-right: 2px;
}

View File

@ -18,6 +18,9 @@ en:
assign:
title: "Assign"
help: "Assign Topic to User"
reassign:
title: "Re-assign"
help: "Re-assign Topic to a different user"
assign_modal:
title: "Assign Topic"
description: "Enter the username of the person you'd like to assign this topic"

View File

@ -18,6 +18,7 @@ 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: /[\w.\-]+?/ }
get "topics/messages-assigned/:username" => "list#messages_assigned", as: "topics_messages_assigned", constraints: { username: /[\w.\-]+?/ }
end
after_initialize do
@ -139,6 +140,17 @@ 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|
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)
create_list(:assigned, {}, list)
end
add_to_class(:topic_query, :list_private_messages_assigned) do |user|

View File

@ -1,10 +1,36 @@
require 'rails_helper'
describe TopicQuery do
describe '#list_private_messages_assigned' do
let(:user) { Fabricate(:user) }
let(:user2) { Fabricate(:user) }
before do
SiteSetting.assign_enabled = true
end
let(:user) { Fabricate(:user) }
let(:user2) { Fabricate(:user) }
describe '#list_messages_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, user)
end
it 'Includes topics and PMs assigned to user' do
assigned_messages = TopicQuery.new(user).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
expect(assigned_messages).to be_empty
end
end
describe '#list_private_messages_assigned' do
let(:user_topic) do
topic = Fabricate(:private_message_topic,
topic_allowed_users: [
@ -24,10 +50,7 @@ describe TopicQuery do
Fabricate.build(:topic_allowed_user, user: user2)
],
)
topic.posts << Fabricate(:post)
TopicAssigner.new(topic, user).assign(user)
topic
assign_to(topic, user)
end
let(:group) { Fabricate(:group).add(user) }
@ -42,13 +65,10 @@ describe TopicQuery do
],
)
topic.posts << Fabricate(:post)
TopicAssigner.new(topic, user).assign(user)
topic
assign_to(topic, user)
end
before do
SiteSetting.assign_enabled = true
user_topic
assigned_topic
group_assigned_topic
@ -78,4 +98,11 @@ describe TopicQuery do
).to contain_exactly(assigned_topic, group_assigned_topic)
end
end
def assign_to(topic, user)
topic.tap do |t|
t.posts << Fabricate(:post)
TopicAssigner.new(t, user).assign(user)
end
end
end