Messages: Add to + cc + add tests.

pull/3939/head
Julio Montoya 5 years ago
parent 9ca29d2790
commit fd10f6cc6b
  1. 14
      assets/vue/components/message/Form.vue
  2. 35
      assets/vue/mixins/CreateMixin.js
  3. 62
      assets/vue/views/message/Create.vue
  4. 63
      assets/vue/views/message/Reply.vue
  5. 16
      assets/vue/views/message/Show.vue
  6. 51
      src/CoreBundle/Entity/Message.php
  7. 22
      src/CoreBundle/Entity/MessageRelUser.php
  8. 31
      src/CoreBundle/Migrations/Schema/V200/Version20200821224242.php
  9. 57
      tests/CoreBundle/Repository/MessageRepositoryTest.php

@ -41,7 +41,8 @@ export default {
return {
title: null,
parentResourceNodeId: null,
receivers: []
receiversTo: [],
receiversCc: [],
};
},
computed: {
@ -50,10 +51,10 @@ export default {
},
receiversErrors() {
const errors = [];
if (!this.v$.item.receivers.$dirty) return errors;
has(this.violations, 'receivers') && errors.push(this.violations.receivers);
if (!this.v$.item.receiversTo.$dirty) return errors;
has(this.violations, 'receiversTo') && errors.push(this.violations.receiversTo);
if (this.v$.item.receivers.required) {
if (this.v$.item.receiversTo.required) {
return this.$t('Field is required')
}
@ -80,9 +81,12 @@ export default {
title: {
required,
},
receivers: {
receiversTo: {
required,
},
/*receiversCc: {
required,
},*/
content: {
required,
},

@ -39,18 +39,47 @@ export default {
createForm.v$.$touch();
// @todo this should be built in in the VueMultiselect component.
if (isEmpty(createForm.v$.item.$model.receivers)) {
if (isEmpty(createForm.v$.item.$model.receiversTo)) {
this.showMessage('Select a user', 'warning');
}
if (!createForm.v$.$invalid) {
let users = [];
createForm.v$.item.$model.receivers.forEach(user => {
createForm.v$.item.$model.receiversTo.forEach(user => {
// Send to inbox
users.push({receiver: user['@id']});
users.push({receiver: user['@id'], receiverType: 1});
});
createForm.v$.item.$model.receiversCc.forEach(user => {
// Send to inbox
users.push({receiver: user['@id'], receiverType: 2});
});
createForm.v$.item.$model.sender = '/api/users/' + this.currentUser.id;
createForm.v$.item.$model.receivers = users;
createForm.v$.item.$model.msgType = 1;
this.create(createForm.v$.item.$model);
}
},
onReplyMessageForm() {
const createForm = this.$refs.createForm;
createForm.v$.$touch();
if (!createForm.v$.$invalid) {
let users = [];
// Send to original sender.
users.push({receiver: createForm.v$.item.$model.originalSender['@id'], receiverType: 1});
// Check Ccs
createForm.v$.item.$model.receiversCc.forEach(user => {
// Send to inbox
users.push({receiver: user.receiver['@id'], receiverType: 2});
});
createForm.v$.item.$model.sender = '/api/users/' + this.currentUser.id;
createForm.v$.item.$model.receiversTo = null;
createForm.v$.item.$model.receiversCc = null;
createForm.v$.item.$model.receivers = users;
createForm.v$.item.$model.msgType = 1;
this.create(createForm.v$.item.$model);

@ -8,23 +8,43 @@
:values="item"
:errors="violations"
>
<!-- @input="v$.item.receiversTo.$touch()"-->
<VueMultiselect
id="to"
placeholder="To"
v-model="item.receivers"
v-model="item.receiversTo"
:loading="isLoadingSelect"
:options="users"
:options="usersTo"
:multiple="true"
:searchable="true"
:internal-search="false"
@search-change="asyncFind"
@search-change="asyncFindTo"
limit-text="3"
limit="3"
label="username"
track-by="id"
:allow-empty="false"
@input="v$.item.receivers.$touch()"
/>
<VueMultiselect
id="cc"
placeholder="Cc"
v-model="item.receiversCc"
:loading="isLoadingSelect"
:options="usersCc"
:multiple="true"
:searchable="true"
:internal-search="false"
@search-change="asyncFindCc"
limit-text="3"
limit="3"
label="username"
track-by="id"
:allow-empty="true"
/>
<!-- @filter-abort="abortFilterFn"-->
<!-- <q-select-->
@ -40,7 +60,6 @@
<!-- hint="With use-chips"-->
<!-- :error-message="receiversErrors"-->
<!-- />-->
<TinyEditor
v-model="item.content"
required
@ -98,30 +117,53 @@ export default {
VueMultiselect
},
setup () {
const users = ref([]);
const usersTo = ref([]);
const usersCc = ref([]);
const isLoadingSelect = ref(false);
function asyncFind (query) {
if (query.toString().length < 3) {
return;
throw new Error('error');
}
isLoadingSelect.value = true;
axios.get(ENTRYPOINT + 'users', {
return axios.get(ENTRYPOINT + 'users', {
params: {
username: query
}
}).then(response => {
isLoadingSelect.value = false;
let data = response.data;
users.value = data['hydra:member'];
console.log('data');
console.log(data);
return data['hydra:member'];
}).catch(function (error) {
isLoadingSelect.value = false;
console.log(error);
});
}
return {v$: useVuelidate(), users, asyncFind, isLoadingSelect};
function asyncFindTo(query) {
try {
asyncFind(query).then(users => {
console.log(users);
usersTo.value = users;
});
} catch (e) {
}
}
function asyncFindCc(query) {
try {
asyncFind(query).then(users => {
usersCc.value = users;
});
} catch (e) {
}
}
return {v$: useVuelidate(), usersTo, usersCc, asyncFindTo, asyncFindCc, isLoadingSelect};
},
data() {
return {

@ -1,7 +1,7 @@
<template>
<!-- :handle-submit="onSendMessageForm"-->
<Toolbar
:handle-send="onSendMessageForm"
:handle-send="onReplyMessageForm"
/>
<MessageForm
@ -10,9 +10,34 @@
:errors="violations"
>
<div v-if="item.originalSender">
To: {{ item.originalSender.username }}
To: <v-chip>{{ item.originalSender.username }}</v-chip>
</div>
<div v-if="item.receiversCc">
<!-- <VueMultiselect-->
<!-- id="cc"-->
<!-- placeholder="Cc"-->
<!-- v-model="item.receiversCc"-->
<!-- :options="usersCc"-->
<!-- :multiple="true"-->
<!-- :searchable="false"-->
<!-- :internal-search="false"-->
<!-- limit-text="3"-->
<!-- limit="3"-->
<!-- label="username"-->
<!-- track-by="id"-->
<!-- />-->
Cc:
<span v-for="messageRelUser in item.receiversCc">
<v-chip>
{{ messageRelUser.receiver.username }}
</v-chip>
</span>
</div>
<TinyEditor
v-model="item.content"
required
@ -55,6 +80,7 @@ import useVuelidate from "@vuelidate/core";
import {useRoute, useRouter} from "vue-router";
const servicePrefix = 'Message';
import VueMultiselect from 'vue-multiselect'
const { mapFields } = createHelpers({
getterType: 'message/getField',
mutationType: 'message/updateField'
@ -65,11 +91,13 @@ export default {
servicePrefix,
mixins: [CreateMixin],
components: {
VueMultiselect,
Loading,
Toolbar,
MessageForm
},
setup () {
const usersCc = ref([]);
const item = ref({});
const isLoadingSelect = ref(false);
const store = useStore();
@ -81,10 +109,15 @@ export default {
id = route.query.id;
}
let replyAll = '1' === route.query['all'];
console.log(replyAll);
onMounted(async () => {
const currentUser = computed(() => store.getters['security/getUser']);
const response = await store.dispatch('message/load', id);
const currentUser = computed(() => store.getters['security/getUser']);
item.value = await response;
delete item.value['@id'];
@ -98,14 +131,30 @@ export default {
item.value['sender'] = currentUser.value['@id'];
// Set new receivers, will be loaded by onSendMessageForm()
item.value['receivers'] = [];
item.value['receivers'][0] = item.value['originalSender'];
if (!replyAll) {
item.value['receivers'] = null;
item.value['receiversTo'] = null;
item.value['receiversCc'] = null;
item.value['receivers'][0] = item.value['originalSender'];
}
/*console.log('-----------------------');
console.log(item.value.receiversCc);
if (item.value.receiversCc) {
item.value.receiversCc.forEach(user => {
console.log(user);
// Send to inbox
usersCc.value.push(user.receiver);
});
}*/
// Set reply content.
item.value.content = `<br /><blockquote>${item.value.content}</blockquote>`;
});
return {v$: useVuelidate(), isLoadingSelect, item};
console.log('---------------------------');
console.log(replyAll);
console.log(item.value['receiversCc'] );
return {v$: useVuelidate(), isLoadingSelect, usersCc, item};
},
computed: {
...mapFields(['error', 'isLoading', 'created', 'violations']),

@ -19,6 +19,15 @@
<v-icon icon="mdi-reply" />
</v-btn>
<v-btn
:loading="isLoading"
tile
icon
@click="replyAll"
>
<v-icon icon="mdi-reply-all" />
</v-btn>
<v-btn
tile
icon
@ -212,6 +221,12 @@ export default {
router.push({name: `${servicePrefix}Reply`, query: params});
}
function replyAll() {
let params = route.query;
params['all'] = 1;
router.push({name: `${servicePrefix}Reply`, query: params});
}
function createEvent() {
let params = route.query;
router.push({name: `CCalendarEventCreate`, query: params});
@ -248,6 +263,7 @@ export default {
removeTagFromMessage,
asyncFind,
reply,
replyAll,
createEvent,
myReceiver
};

@ -141,6 +141,18 @@ class Message
#[ApiSubresource]
protected array | null | Collection $receivers;
/**
* @var Collection|MessageRelUser[]
*/
#[Groups(['message:read', 'message:write'])]
protected array | null | Collection $receiversTo;
/**
* @var Collection|MessageRelUser[]
*/
#[Groups(['message:read', 'message:write'])]
protected array | null | Collection $receiversCc;
/**
* @ORM\Column(name="msg_type", type="smallint", nullable=false)
*/
@ -188,7 +200,7 @@ class Message
protected string $content;
#[Groups(['message:read', 'message:write'])]
protected string $firstReceiver;
protected ?MessageRelUser $firstReceiver;
/**
* @ORM\ManyToOne(targetEntity="Chamilo\CourseBundle\Entity\CGroup")
@ -243,6 +255,8 @@ class Message
$this->children = new ArrayCollection();
$this->likes = new ArrayCollection();
$this->receivers = new ArrayCollection();
$this->receiversCc = new ArrayCollection();
$this->receiversTo = new ArrayCollection();
$this->votes = 0;
$this->status = 0;
}
@ -255,6 +269,38 @@ class Message
return $this->receivers;
}
/**
* @return null|Collection|MessageRelUser[]
*/
public function getReceiversTo()
{
return $this->getReceivers()->filter(function (MessageRelUser $messageRelUser) {
return MessageRelUser::TYPE_TO === $messageRelUser->getReceiverType();
});
}
/**
* @return null|Collection|MessageRelUser[]
*/
public function getReceiversCc()
{
$list = [];
foreach ($this->receivers as $receiver) {
if (MessageRelUser::TYPE_CC === $receiver->getReceiverType()) {
$list[] = $receiver;
}
}
/*
$result = $this->receivers->filter(function (MessageRelUser $messageRelUser) {
error_log((string)$messageRelUser->getId());
return MessageRelUser::TYPE_CC === $messageRelUser->getReceiverType();
});
*/
return $list;
}
public function getFirstReceiver(): ?MessageRelUser
{
if ($this->receivers->count() > 0) {
@ -279,10 +325,11 @@ class Message
return false;
}
public function addReceiver(User $receiver): self
public function addReceiver(User $receiver, int $receiverType = MessageRelUser::TYPE_TO): self
{
$messageRelUser = (new MessageRelUser())
->setReceiver($receiver)
->setReceiverType($receiverType)
->setMessage($this)
;
if (!$this->receivers->contains($messageRelUser)) {

@ -41,6 +41,9 @@ use Symfony\Component\Validator\Constraints as Assert;
])]
class MessageRelUser
{
public const TYPE_TO = 1;
public const TYPE_CC = 2;
/**
* @ORM\Column(name="id", type="bigint")
* @ORM\Id
@ -69,6 +72,12 @@ class MessageRelUser
#[Groups(['message:read', 'message:write'])]
protected bool $read;
/**
* @ORM\Column(name="receiver_type", type="smallint", nullable=false)
*/
#[Groups(['message:read', 'message:write'])]
protected int $receiverType;
/**
* @ORM\Column(name="starred", type="boolean", nullable=false)
*/
@ -90,6 +99,7 @@ class MessageRelUser
$this->tags = new ArrayCollection();
$this->read = false;
$this->starred = false;
$this->receiverType = self::TYPE_TO;
}
public function getId(): ?int
@ -170,4 +180,16 @@ class MessageRelUser
return $this;
}
public function getReceiverType(): int
{
return $this->receiverType;
}
public function setReceiverType(int $receiverType): self
{
$this->receiverType = $receiverType;
return $this;
}
}

@ -71,17 +71,26 @@ final class Version20200821224242 extends AbstractMigrationChamilo
$this->addSql('ALTER TABLE message DROP FOREIGN KEY FK_B6BD307F64482423');
}
if ($schema->hasTable('message_rel_user')) {
/*
*
* CREATE TABLE message_rel_user (id BIGINT AUTO_INCREMENT NOT NULL, message_id BIGINT NOT NULL, user_id INT NOT NULL, msg_read TINYINT(1) NOT NULL, starred TINYINT(1) NOT NULL, INDEX IDX_325D70B9537A1329 (message_id), INDEX IDX_325D70B9A76ED395 (user_id), UNIQUE INDEX message_receiver (message_id, user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC;
* CREATE TABLE message_rel_user_rel_tags (message_rel_user_id BIGINT NOT NULL, message_tag_id BIGINT NOT NULL, INDEX IDX_B4B37A20962B5422 (message_rel_user_id), INDEX IDX_B4B37A208DF5FE1E (message_tag_id), PRIMARY KEY(message_rel_user_id, message_tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC;
* ALTER TABLE message_rel_user ADD CONSTRAINT FK_325D70B9537A1329 FOREIGN KEY (message_id) REFERENCES message (id);
* ALTER TABLE message_rel_user ADD CONSTRAINT FK_325D70B9A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE;
* ALTER TABLE message_rel_user_rel_tags ADD CONSTRAINT FK_B4B37A20962B5422 FOREIGN KEY (message_rel_user_id) REFERENCES message_rel_user (id) ON DELETE CASCADE;
* ALTER TABLE message_rel_user_rel_tags ADD CONSTRAINT FK_B4B37A208DF5FE1E FOREIGN KEY (message_tag_id) REFERENCES message_tag (id) ON DELETE CASCADE;
*
*/
if (!$schema->hasTable('message_rel_user')) {
$this->addSql(
"CREATE TABLE message_rel_user (id BIGINT AUTO_INCREMENT NOT NULL, message_id BIGINT NOT NULL, user_id INT NOT NULL, msg_read TINYINT(1) NOT NULL, starred TINYINT(1) NOT NULL, INDEX IDX_325D70B9537A1329 (message_id), INDEX IDX_325D70B9A76ED395 (user_id), UNIQUE INDEX message_receiver (message_id, user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC"
);
$this->addSql(
"CREATE TABLE message_rel_user_rel_tags (message_rel_user_id BIGINT NOT NULL, message_tag_id BIGINT NOT NULL, INDEX IDX_B4B37A20962B5422 (message_rel_user_id), INDEX IDX_B4B37A208DF5FE1E (message_tag_id), PRIMARY KEY(message_rel_user_id, message_tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB ROW_FORMAT = DYNAMIC"
);
$this->addSql(
"ALTER TABLE message_rel_user ADD CONSTRAINT FK_325D70B9537A1329 FOREIGN KEY (message_id) REFERENCES message (id)"
);
$this->addSql(
"ALTER TABLE message_rel_user ADD CONSTRAINT FK_325D70B9A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE"
);
$this->addSql(
"ALTER TABLE message_rel_user_rel_tags ADD CONSTRAINT FK_B4B37A20962B5422 FOREIGN KEY (message_rel_user_id) REFERENCES message_rel_user (id) ON DELETE CASCADE"
);
$this->addSql(
"ALTER TABLE message_rel_user_rel_tags ADD CONSTRAINT FK_B4B37A208DF5FE1E FOREIGN KEY (message_tag_id) REFERENCES message_tag (id) ON DELETE CASCADE "
);
$this->addSql(" ALTER TABLE message_rel_user ADD receiver_type SMALLINT NOT NULL;");
}
//$this->addSql('ALTER TABLE message CHANGE user_receiver_id user_receiver_id INT DEFAULT NULL');

@ -54,6 +54,10 @@ class MessageRepositoryTest extends AbstractApiTest
$count = $messageRepo->count(['msgType' => Message::MESSAGE_TYPE_INBOX]);
$this->assertSame(1, $count);
// One receiver in MessageRelUser.
$this->assertSame(1, $messageRelUserRepo->count([]));
$this->assertSame(1, $message->getReceivers()->count());
// Check if message was schedule to be sent.
/** @var InMemoryTransport $transport */
$transport = $this->getContainer()->get('messenger.transport.sync_priority_high');
@ -127,6 +131,59 @@ class MessageRepositoryTest extends AbstractApiTest
$this->assertSame(2, $messageTagRepo->count([]));
}
public function testCreateMessageWithCc(): void
{
self::bootKernel();
$em = self::getContainer()->get('doctrine')->getManager();
$messageRepo = self::getContainer()->get(MessageRepository::class);
$userRepo = self::getContainer()->get(UserRepository::class);
$messageRelUserRepo = $em->getRepository(MessageRelUser::class);
$admin = $this->getUser('admin');
$testUser = $this->createUser('test');
$receiverCopy = $this->createUser('cc');
$message =
(new Message())
->setTitle('hello')
->setContent('content')
->setMsgType(Message::MESSAGE_TYPE_INBOX)
->setSender($admin)
->addReceiver($testUser)
->addReceiver($receiverCopy, MessageRelUser::TYPE_CC)
;
$this->assertHasNoEntityViolations($message);
$messageRepo->update($message);
// 1. Message exists in the inbox.
$count = $messageRepo->count(['msgType' => Message::MESSAGE_TYPE_INBOX]);
$this->assertSame(1, $count);
$this->assertSame(2, $messageRelUserRepo->count([]));
// Check if message was schedule to be sent.
/** @var InMemoryTransport $transport */
$transport = $this->getContainer()->get('messenger.transport.sync_priority_high');
$this->assertCount(1, $transport->getSent());
$em->clear();
/** @var Message $message */
$message = $messageRepo->find($message->getId());
$this->assertSame(2, $message->getReceivers()->count());
// Delete message.
$messageRepo->delete($message);
// No messages.
$this->assertSame(0, $messageRepo->count([]));
// No message_rel_user.
$this->assertSame(0, $messageRelUserRepo->count([]));
}
public function testCreateMessageWithApi(): void
{
self::bootKernel();

Loading…
Cancel
Save