From d1626d177724891c3ba6b38df21293085b1689c1 Mon Sep 17 00:00:00 2001 From: jedi Date: Sat, 13 Jan 2024 01:40:37 +0100 Subject: [PATCH] add text field to add comment to ticket --- core/tickets/api_v2.py | 27 ++++++++++++++ core/tickets/tests/v2/test_tickets.py | 17 +++++++++ web/src/components/Navbar.vue | 1 + web/src/components/Timeline.vue | 52 ++++++++++++++++++++------- web/src/store/index.js | 9 +++-- web/src/views/Ticket.vue | 14 ++++++-- 6 files changed, 102 insertions(+), 18 deletions(-) diff --git a/core/tickets/api_v2.py b/core/tickets/api_v2.py index 8cadcb1..3105e00 100644 --- a/core/tickets/api_v2.py +++ b/core/tickets/api_v2.py @@ -88,6 +88,12 @@ class IssueViewSet(viewsets.ModelViewSet): class CommentSerializer(serializers.ModelSerializer): + + def validate(self, attrs): + if 'comment' not in attrs or attrs['comment'] == '': + raise serializers.ValidationError('comment cannot be empty') + return attrs + class Meta: model = Comment fields = ('id', 'comment', 'timestamp', 'issue_thread') @@ -176,12 +182,33 @@ def get_available_states(request): return Response(get_state_choices()) +@api_view(['POST']) +@permission_classes([IsAuthenticated]) +@permission_required('tickets.add_comment', raise_exception=True) +def add_comment(request, pk): + issue = IssueThread.objects.get(pk=pk) + if 'comment' not in request.data or request.data['comment'] == '': + return Response({'status': 'error', 'message': 'missing comment'}, status=status.HTTP_400_BAD_REQUEST) + comment = Comment.objects.create( + issue_thread=issue, + comment=request.data['comment'], + ) + systemevent = SystemEvent.objects.create(type='comment added', reference=comment.id) + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + 'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id, + "message": "comment added"} + ) + return Response(CommentSerializer(comment).data, status=status.HTTP_201_CREATED) + + router = routers.SimpleRouter() router.register(r'tickets', IssueViewSet, basename='issues') router.register(r'comments', CommentViewSet, basename='comments') urlpatterns = ([ re_path(r'^tickets/(?P\d+)/reply/$', reply, name='reply'), + re_path(r'^tickets/(?P\d+)/comment/$', add_comment, name='add_comment'), re_path(r'^tickets/manual/$', manual_ticket, name='manual_ticket'), re_path(r'^tickets/states/$', get_available_states, name='get_available_states'), ] + router.urls) diff --git a/core/tickets/tests/v2/test_tickets.py b/core/tickets/tests/v2/test_tickets.py index 09cc298..9529aec 100644 --- a/core/tickets/tests/v2/test_tickets.py +++ b/core/tickets/tests/v2/test_tickets.py @@ -175,6 +175,23 @@ class IssueApiTest(TestCase): self.assertEqual(response.json()['issue_thread'], issue.id) self.assertEqual(response.json()['timestamp'], response.json()['timestamp']) + def test_post_comment_altenative(self): + issue = IssueThread.objects.create( + name="test issue", + ) + response = self.client.post(f'/api/2/tickets/{issue.id}/comment/', {'comment': 'test'}) + self.assertEqual(response.status_code, 201) + self.assertEqual(response.json()['comment'], 'test') + self.assertEqual(response.json()['issue_thread'], issue.id) + self.assertEqual(response.json()['timestamp'], response.json()['timestamp']) + + def test_post_alt_comment_empty(self): + issue = IssueThread.objects.create( + name="test issue", + ) + response = self.client.post(f'/api/2/tickets/{issue.id}/comment/', {'comment': ''}) + self.assertEqual(response.status_code, 400) + def test_state_change(self): issue = IssueThread.objects.create( name="test issue", diff --git a/web/src/components/Navbar.vue b/web/src/components/Navbar.vue index 714dee0..03f5c0e 100644 --- a/web/src/components/Navbar.vue +++ b/web/src/components/Navbar.vue @@ -148,6 +148,7 @@ export default { padding-bottom: 1rem !important; border: var(--gray) solid 1px !important; border-bottom: none !important; + color: var(--blue) !important; &.active { background: black !important; diff --git a/web/src/components/Timeline.vue b/web/src/components/Timeline.vue index 0758d49..1c05cd9 100644 --- a/web/src/components/Timeline.vue +++ b/web/src/components/Timeline.vue @@ -23,12 +23,31 @@ -
-
- - +
+
+ +
  • + + + +
    +
    + {{ newestMailSubject }} +
    +
    + +
    @@ -52,18 +71,27 @@ export default { default: () => [] } }, - emits: ['sendMail'], + emits: ['sendMail', 'addComment'], data: () => ({ - newMail: "" + newMail: "", + newComment: "" }), computed: { ...mapGetters(['stateInfo']), + newestMailSubject() { + const mail = this.timeline.filter(item => item.type === 'mail').pop(); + return mail ? mail.subject : ""; + }, }, methods: { - sendMailandClear: function () { + sendMailAndClear: function () { this.$emit('sendMail', this.newMail); this.newMail = ""; }, + addCommentAndClear: function () { + this.$emit('addComment', this.newComment); + this.newComment = ""; + } }, }; @@ -117,14 +145,14 @@ img { } } -.new-comment { +.new-comment, .new-mail { width: 100%; - input { + textarea, input { border: 1px solid var(--gray); border-radius: 6px; - height: 48px; - padding: 0 16px; + height: 5em; + padding: 8px 16px; &::placeholder { color: var(--gray-dark); diff --git a/web/src/store/index.js b/web/src/store/index.js index 5808e38..165f996 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -35,9 +35,7 @@ axios.interceptors.response.use(response => response, error => { store.commit('createToast', {title: 'Error: Access denied', message, color: 'danger'}); return Promise.reject(error) } else { - console.log('error interceptor fired'); - console.error(error); // todo: toast error - console.log(Object.entries(error)); + console.error('error interceptor fired', error.message); if (error.isAxiosError) { const message = ` @@ -272,6 +270,7 @@ const store = new Vuex.Store({ //credentials failed, logout store.commit('logout'); }, + //async verifyToken({commit, state}) { async afterLogin({dispatch}) { const boxes = dispatch('loadBoxes'); const items = dispatch('loadEventItems'); @@ -375,6 +374,10 @@ const store = new Vuex.Store({ }); await dispatch('loadTickets'); }, + async postComment({commit, dispatch}, {id, message}) { + const {data} = await axios.post(`/2/tickets/${id}/comment/`, {comment: message}); + await dispatch('loadTickets'); + }, async loadUsers({commit}) { const {data} = await axios.get('/2/users/'); commit('replaceUsers', data); diff --git a/web/src/views/Ticket.vue b/web/src/views/Ticket.vue index f772370..af6914d 100644 --- a/web/src/views/Ticket.vue +++ b/web/src/views/Ticket.vue @@ -6,7 +6,7 @@

    Ticket #{{ ticket.id }} - {{ ticket.name }}

    - +