From 13f7a84cc14f23708e5e5aacde85ada6735e7ceb Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Wed, 21 Oct 2020 21:27:58 +0300
Subject: [PATCH 1/8] Create api function and module action for fetching single
 report

---
 src/api/reports.js           |  9 +++++++++
 src/store/modules/reports.js | 13 ++++++++++++-
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/api/reports.js b/src/api/reports.js
index abd371c1..4f41f956 100644
--- a/src/api/reports.js
+++ b/src/api/reports.js
@@ -24,6 +24,15 @@ export async function fetchReports(filter, page, pageSize, authHost, token) {
   })
 }
 
+export async function fetchSingleReport(id, authHost, token) {
+  return await request({
+    baseURL: baseName(authHost),
+    url: `/api/pleroma/admin/reports/${id}`,
+    method: 'get',
+    headers: authHeaders(token)
+  })
+}
+
 export async function createNote(content, reportID, authHost, token) {
   return await request({
     baseURL: baseName(authHost),
diff --git a/src/store/modules/reports.js b/src/store/modules/reports.js
index 52eb3bc6..51f08219 100644
--- a/src/store/modules/reports.js
+++ b/src/store/modules/reports.js
@@ -1,4 +1,4 @@
-import { changeState, fetchReports, createNote, deleteNote } from '@/api/reports'
+import { changeState, fetchReports, fetchSingleReport, createNote, deleteNote } from '@/api/reports'
 import {
   activateUsers,
   deactivateUsers,
@@ -14,6 +14,7 @@ const reports = {
     loading: true,
     openReportsCount: 0,
     pageSize: 50,
+    singleReport: {},
     stateFilter: '',
     totalReportsCount: 0
   },
@@ -38,6 +39,9 @@ const reports = {
     },
     SET_REPORTS_FILTER: (state, filter) => {
       state.stateFilter = filter
+    },
+    SET_SINGLE_REPORT: (state, report) => {
+      state.singleReport = report
     }
   },
   actions: {
@@ -120,6 +124,13 @@ const reports = {
       commit('SET_PAGE', page)
       commit('SET_LOADING', false)
     },
+    async FetchSingleReport({ commit, getters }, id) {
+      commit('SET_LOADING', true)
+      const { data } = await fetchSingleReport(id, getters.authHost, getters.token)
+
+      commit('SET_SINGLE_REPORT', data)
+      commit('SET_LOADING', false)
+    },
     async FetchOpenReportsCount({ commit, getters, state }) {
       commit('SET_LOADING', true)
       const { data } = await fetchReports('open', state.currentPage, state.pageSize, getters.authHost, getters.token)
-- 
GitLab


From 25e307442d87487b5b590c3b2c4bfd5e91f230e1 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Wed, 21 Oct 2020 23:34:00 +0300
Subject: [PATCH 2/8] Make report ID in moderation log a link to the respective
 report

---
 src/router/index.js                          | 12 ++++++++++
 src/views/moderation_log/LogEntryMessage.vue | 23 ++++++++++++++++++--
 src/views/moderation_log/index.vue           |  2 +-
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/src/router/index.js b/src/router/index.js
index b61e7b44..60be0f88 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -199,5 +199,17 @@ export const asyncRouterMap = [
     ],
     hidden: true
   },
+  {
+    path: '/reports/:id',
+    component: Layout,
+    children: [
+      {
+        path: '',
+        name: 'ReportsShow',
+        component: () => import('@/views/reports/show')
+      }
+    ],
+    hidden: true
+  },
   { path: '*', redirect: '/404', hidden: true }
 ]
diff --git a/src/views/moderation_log/LogEntryMessage.vue b/src/views/moderation_log/LogEntryMessage.vue
index d9cc8547..d19ada81 100644
--- a/src/views/moderation_log/LogEntryMessage.vue
+++ b/src/views/moderation_log/LogEntryMessage.vue
@@ -8,7 +8,16 @@
         @{{ actor.nickname }}
       </span>
     </router-link>
-    <span>{{ logEntryMessage }}</span>
+    <span v-if="subject.type === 'report' && propertyExists(subject, 'id')">
+      {{ logEntryMessageWithoutId[0] }}
+      <router-link
+        :to="{ name: 'ReportsShow', params: { id: subject.id }}"
+        class="router-link">
+        <span style="font-weight: 600">#{{ subject.id }}</span>
+      </router-link>
+      {{ logEntryMessageWithoutId[1] }}
+    </span>
+    <span v-else>{{ logEntryMessage }}</span>
   </span>
 </template>
 
@@ -24,11 +33,21 @@ export default {
     message: {
       type: String,
       required: true
+    },
+    subject: {
+      type: [Object, Array],
+      required: false,
+      default: function() {
+        return {}
+      }
     }
   },
   computed: {
     logEntryMessage() {
-      return this.message.split(this.actor.nickname)[1]
+      return this.actor.nickname ? this.message.split(this.actor.nickname)[1] : this.message
+    },
+    logEntryMessageWithoutId() {
+      return this.logEntryMessage.split(`#${this.subject.id}`)
     }
   },
   methods: {
diff --git a/src/views/moderation_log/index.vue b/src/views/moderation_log/index.vue
index af123e22..283bf4d2 100644
--- a/src/views/moderation_log/index.vue
+++ b/src/views/moderation_log/index.vue
@@ -43,7 +43,7 @@
         v-for="(logEntry, index) in log"
         :key="index"
         :timestamp="normalizeTimestamp(logEntry.time)">
-        <log-entry-message v-if="propertyExists(logEntry.data.actor, 'nickname')" :actor="logEntry.data.actor" :message="logEntry.message"/>
+        <log-entry-message v-if="propertyExists(logEntry.data.actor, 'nickname')" :actor="logEntry.data.actor" :message="logEntry.message" :subject="logEntry.data.subject"/>
         <span v-else>{{ logEntry.message }}</span>
       </el-timeline-item>
     </el-timeline>
-- 
GitLab


From 4501143b69a3e33c98e7fba4086447be69587ade Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Tue, 27 Oct 2020 01:12:38 +0300
Subject: [PATCH 3/8] Create show page for a single report

---
 src/views/reports/show.vue | 86 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 86 insertions(+)
 create mode 100644 src/views/reports/show.vue

diff --git a/src/views/reports/show.vue b/src/views/reports/show.vue
new file mode 100644
index 00000000..e393afc0
--- /dev/null
+++ b/src/views/reports/show.vue
@@ -0,0 +1,86 @@
+<template>
+  <div v-if="!loading">
+    <header class="report-page-header-container">
+      <div class="report-page-header">
+        <div v-if="propertyExists(report.account, 'nickname')" class="avatar-name-container">
+          <h1 >{{ $t('reports.reportOn') }}</h1>
+          <el-avatar v-if="propertyExists(report.account, 'avatar')" :src="report.account.avatar" size="large" class="report-page-avatar"/>
+          <h1>{{ report.account.nickname }}</h1>
+          <a v-if="propertyExists(report.account, 'url')" :href="report.account.url" target="_blank">
+            <i :title="$t('userProfile.openAccountInInstance')" class="el-icon-top-right"/>
+          </a>
+        </div>
+        <h1 v-else>{{ $t('reports.report') }}</h1>
+        <h4 v-if="propertyExists(report.account, 'id')" class="id">{{ $t('reports.id') }}: {{ report.id }}</h4>
+      </div>
+      <reboot-button/>
+    </header>
+    <el-card class="report"/>
+  </div>
+</template>
+
+<script>
+import RebootButton from '@/components/RebootButton'
+
+export default {
+  name: 'ReportsShow',
+  components: { RebootButton },
+  computed: {
+    loading() {
+      return this.$store.state.reports.loading
+    },
+    report() {
+      return this.$store.state.reports.singleReport
+    }
+  },
+  mounted: function() {
+    this.$store.dispatch('NeedReboot')
+    this.$store.dispatch('GetNodeInfo')
+    this.$store.dispatch('FetchSingleReport', this.$route.params.id)
+  },
+  methods: {
+    propertyExists(account, property, _secondProperty) {
+      if (_secondProperty) {
+        return account[property] && account[_secondProperty]
+      }
+      return account[property]
+    }
+  }
+}
+</script>
+
+<style rel='stylesheet/scss' lang='scss'>
+.report-page-header {
+  display: flex;
+  flex-direction: column;
+  margin: 22px 15px 22px 20px;
+  padding: 0;
+  h1 {
+    display: inline;
+    margin: 0;
+  }
+  h4 {
+    margin-top: 10px;
+  }
+  .avatar-name-container {
+    display: flex;
+    align-items: center;
+    .el-icon-top-right {
+      font-size: 2em;
+      line-height: 36px;
+      color: #606266;
+    }
+  }
+  .id {
+    color: gray;
+  }
+  .report-page-avatar {
+    margin: 0 7px 0 12px;
+  }
+}
+.report-page-header-container {
+  align-items: center;
+  display: flex;
+  justify-content: space-between;
+}
+</style>
-- 
GitLab


From 1be1dbcefba8c00ca0135710524eae10d4ff7e3d Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Tue, 27 Oct 2020 01:46:51 +0300
Subject: [PATCH 4/8] Extract Report content into a separate component and
 reuse it in single report component

---
 src/views/reports/components/Report.vue       | 149 +-------------
 .../reports/components/ReportContent.vue      | 182 ++++++++++++++++++
 src/views/reports/show.vue                    |   7 +-
 3 files changed, 190 insertions(+), 148 deletions(-)
 create mode 100644 src/views/reports/components/ReportContent.vue

diff --git a/src/views/reports/components/Report.vue b/src/views/reports/components/Report.vue
index 7b2bbe7e..81847635 100644
--- a/src/views/reports/components/Report.vue
+++ b/src/views/reports/components/Report.vue
@@ -28,83 +28,7 @@
             </div>
           </div>
           <el-divider class="divider"/>
-          <div class="report-account-container">
-            <span class="report-row-key">{{ $t('reports.account') }}:</span>
-            <div class="report-account">
-              <router-link
-                v-if="propertyExists(report.account, 'id')"
-                :to="{ name: 'UsersShow', params: { id: report.account.id }}"
-                class="router-link">
-                <img
-                  v-if="propertyExists(report.account, 'avatar')"
-                  :src="report.account.avatar"
-                  alt="avatar"
-                  class="avatar-img">
-                <span v-if="propertyExists(report.account, 'nickname')" class="report-account-name">{{ report.account.nickname }}</span>
-                <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
-              </router-link>
-              <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
-              <a v-if="propertyExists(report.account, 'url')" :href="report.account.url" target="_blank" class="account">
-                {{ $t('userProfile.openAccountInInstance') }}
-                <i class="el-icon-top-right"/>
-              </a>
-            </div>
-          </div>
-          <div v-if="report.content && report.content.length > 0">
-            <el-divider class="divider"/>
-            <span class="report-row-key">{{ $t('reports.content') }}:
-              <span>{{ report.content }}</span>
-            </span>
-          </div>
-          <el-divider class="divider"/>
-          <div :style="showStatuses(report.statuses) ? '' : 'margin-bottom:15px'" class="report-account-container">
-            <span class="report-row-key">{{ $t('reports.actor') }}:</span>
-            <div class="report-account">
-              <router-link
-                v-if="propertyExists(report.actor, 'id')"
-                :to="{ name: 'UsersShow', params: { id: report.actor.id }}"
-                class="router-link">
-                <img
-                  v-if="propertyExists(report.actor, 'avatar')"
-                  :src="report.actor.avatar"
-                  alt="avatar"
-                  class="avatar-img">
-                <span v-if="propertyExists(report.actor, 'nickname')" class="report-account-name">{{ report.actor.nickname }}</span>
-                <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
-              </router-link>
-              <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
-              <a v-if="propertyExists(report.actor, 'url')" :href="report.actor.url" target="_blank" class="account">
-                {{ $t('userProfile.openAccountInInstance') }}
-                <i class="el-icon-top-right"/>
-              </a>
-            </div>
-          </div>
-          <div v-if="showStatuses(report.statuses)" class="reported-statuses">
-            <el-collapse>
-              <el-collapse-item :title="getStatusesTitle(report.statuses)">
-                <div v-for="status in report.statuses" :key="status.id">
-                  <status :status="status" :account="status.account.nickname ? status.account : report.account" :show-checkbox="false" :page="currentPage"/>
-                </div>
-              </el-collapse-item>
-            </el-collapse>
-          </div>
-          <div class="report-notes">
-            <el-collapse>
-              <el-collapse-item :title="getNotesTitle(report.notes)">
-                <note-card v-for="(note, index) in report.notes" :key="index" :note="note" :report="report"/>
-              </el-collapse-item>
-            </el-collapse>
-            <div class="report-note-form">
-              <el-input
-                v-model="notes[report.id]"
-                :placeholder="$t('reports.leaveNote')"
-                type="textarea"
-                rows="2"/>
-              <div class="report-post-note">
-                <el-button @click="handleNewNote(report.id)">{{ $t('reports.postNote') }}</el-button>
-              </div>
-            </div>
-          </div>
+          <report-content :report="report"/>
         </el-card>
       </el-timeline-item>
     </el-timeline>
@@ -123,24 +47,18 @@
 
 <script>
 import moment from 'moment'
-import NoteCard from './NoteCard'
-import Status from '@/components/Status'
 import ModerateUserDropdown from './ModerateUserDropdown'
+import ReportContent from './ReportContent'
 
 export default {
   name: 'Report',
-  components: { Status, ModerateUserDropdown, NoteCard },
+  components: { ModerateUserDropdown, ReportContent },
   props: {
     reports: {
       type: Array,
       required: true
     }
   },
-  data() {
-    return {
-      notes: {}
-    }
-  },
   computed: {
     loading() {
       return this.$store.state.reports.loading
@@ -172,16 +90,6 @@ export default {
           return 'primary'
       }
     },
-    getStatusesTitle(statuses = []) {
-      return `Reported statuses: ${statuses.length} item(s)`
-    },
-    getNotesTitle(notes = []) {
-      return `Notes: ${notes.length} item(s)`
-    },
-    handleNewNote(reportID) {
-      this.$store.dispatch('CreateReportNote', { content: this.notes[reportID], reportID })
-      this.notes[reportID] = ''
-    },
     handlePageChange(page) {
       this.$store.dispatch('FetchReports', page)
     },
@@ -193,9 +101,6 @@ export default {
         return account[property] && account[_secondProperty]
       }
       return account[property]
-    },
-    showStatuses(statuses = []) {
-      return statuses.length > 0
     }
   }
 }
@@ -206,25 +111,9 @@ export default {
     margin: 0;
     height: 17px;
   }
-  .account {
-    line-height: 26px;
-    font-size: 13px;
-    color: #606266;
-  }
-  .account:hover {
-    text-decoration: underline;
-  }
-  .avatar-img {
-    vertical-align: bottom;
-    width: 15px;
-    height: 15px;
-  }
   .divider {
     margin: 15px 0;
   }
-  .deactivated {
-    color: gray;
-  }
   .el-card__body {
     padding: 17px;
   }
@@ -279,35 +168,9 @@ export default {
       height: 40px;
     }
   }
-  .report-account {
-    display: flex;
-    align-items: baseline;
-    justify-content: space-between;
-    flex-grow: 2;
-  }
-  .report-account-container {
-    display: flex;
-    align-items: baseline;
-  }
-  .report-account-name {
-    font-size: 15px;
-    font-weight: 500;
-  }
-  .report-row-key {
-    font-size: 14px;
-    font-weight: 500;
-    padding-right: 5px;
-  }
   .report-title {
     margin: 0;
   }
-  .report-note-form {
-    margin: 15px 0 0 0;
-  }
-  .report-post-note {
-    margin: 5px 0 0 0;
-    text-align: right;
-  }
   .reports-pagination {
     margin: 25px 0;
     text-align: center;
@@ -316,12 +179,6 @@ export default {
     margin: 30px 45px 45px 19px;
     padding: 0px;
   }
-  .router-link {
-    text-decoration: none;
-  }
-  .reported-statuses {
-    margin-top: 15px;
-  }
   .submit-button {
     display: block;
     margin: 7px 0 17px auto;
diff --git a/src/views/reports/components/ReportContent.vue b/src/views/reports/components/ReportContent.vue
new file mode 100644
index 00000000..67cf16b4
--- /dev/null
+++ b/src/views/reports/components/ReportContent.vue
@@ -0,0 +1,182 @@
+<template>
+  <div>
+    <div class="report-account-container">
+      <span class="report-row-key">{{ $t('reports.account') }}:</span>
+      <div class="report-account">
+        <router-link
+          v-if="propertyExists(report.account, 'id')"
+          :to="{ name: 'UsersShow', params: { id: report.account.id }}"
+          class="router-link">
+          <img
+            v-if="propertyExists(report.account, 'avatar')"
+            :src="report.account.avatar"
+            alt="avatar"
+            class="avatar-img">
+          <span v-if="propertyExists(report.account, 'nickname')" class="report-account-name">{{ report.account.nickname }}</span>
+          <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
+        </router-link>
+        <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
+        <a v-if="propertyExists(report.account, 'url')" :href="report.account.url" target="_blank" class="account">
+          {{ $t('userProfile.openAccountInInstance') }}
+          <i class="el-icon-top-right"/>
+        </a>
+      </div>
+    </div>
+    <div v-if="report.content && report.content.length > 0">
+      <el-divider class="divider"/>
+      <span class="report-row-key">{{ $t('reports.content') }}:
+        <span>{{ report.content }}</span>
+      </span>
+    </div>
+    <el-divider class="divider"/>
+    <div :style="showStatuses(report.statuses) ? '' : 'margin-bottom:15px'" class="report-account-container">
+      <span class="report-row-key">{{ $t('reports.actor') }}:</span>
+      <div class="report-account">
+        <router-link
+          v-if="propertyExists(report.actor, 'id')"
+          :to="{ name: 'UsersShow', params: { id: report.actor.id }}"
+          class="router-link">
+          <img
+            v-if="propertyExists(report.actor, 'avatar')"
+            :src="report.actor.avatar"
+            alt="avatar"
+            class="avatar-img">
+          <span v-if="propertyExists(report.actor, 'nickname')" class="report-account-name">{{ report.actor.nickname }}</span>
+          <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
+        </router-link>
+        <span v-else class="report-account-name deactivated">({{ $t('users.invalidNickname') }})</span>
+        <a v-if="propertyExists(report.actor, 'url')" :href="report.actor.url" target="_blank" class="account">
+          {{ $t('userProfile.openAccountInInstance') }}
+          <i class="el-icon-top-right"/>
+        </a>
+      </div>
+    </div>
+    <div v-if="showStatuses(report.statuses)" class="reported-statuses">
+      <el-collapse>
+        <el-collapse-item :title="getStatusesTitle(report.statuses)">
+          <div v-for="status in report.statuses" :key="status.id">
+            <status :status="status" :account="status.account.nickname ? status.account : report.account" :show-checkbox="false" :page="currentPage"/> // check why it's currentPage here
+          </div>
+        </el-collapse-item>
+      </el-collapse>
+    </div>
+    <div>
+      <el-collapse>
+        <el-collapse-item :title="getNotesTitle(report.notes)">
+          <note-card v-for="(note, index) in report.notes" :key="index" :note="note" :report="report"/>
+        </el-collapse-item>
+      </el-collapse>
+      <div class="report-note-form">
+        <el-input
+          v-model="notes[report.id]"
+          :placeholder="$t('reports.leaveNote')"
+          type="textarea"
+          rows="2"/>
+        <div class="report-post-note">
+          <el-button @click="handleNewNote(report.id)">{{ $t('reports.postNote') }}</el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import NoteCard from './NoteCard'
+import Status from '@/components/Status'
+
+export default {
+  name: 'ReportContent',
+  components: { NoteCard, Status },
+  props: {
+    report: {
+      type: Object,
+      required: true
+    }
+  },
+  data() {
+    return {
+      notes: {}
+    }
+  },
+  computed: {
+    currentPage() {
+      return this.$store.state.reports.currentPage
+    }
+  },
+  methods: {
+    getNotesTitle(notes = []) {
+      return `Notes: ${notes.length} item(s)`
+    },
+    getStatusesTitle(statuses = []) {
+      return `Reported statuses: ${statuses.length} item(s)`
+    },
+    handleNewNote(reportID) {
+      this.$store.dispatch('CreateReportNote', { content: this.notes[reportID], reportID })
+      this.notes[reportID] = ''
+    },
+    propertyExists(account, property, _secondProperty) {
+      if (_secondProperty) {
+        return account[property] && account[_secondProperty]
+      }
+      return account[property]
+    },
+    showStatuses(statuses = []) {
+      return statuses.length > 0
+    }
+  }
+}
+</script>
+
+<style rel='stylesheet/scss' lang='scss'>
+  .account {
+    line-height: 26px;
+    font-size: 13px;
+    color: #606266;
+  }
+  .account:hover {
+    text-decoration: underline;
+  }
+  .avatar-img {
+    vertical-align: bottom;
+    width: 15px;
+    height: 15px;
+  }
+  .deactivated {
+    color: gray;
+  }
+  .divider {
+    margin: 15px 0;
+  }
+  .report-account {
+    display: flex;
+    align-items: baseline;
+    justify-content: space-between;
+    flex-grow: 2;
+  }
+  .report-account-container {
+    display: flex;
+    align-items: baseline;
+  }
+  .report-account-name {
+    font-size: 15px;
+    font-weight: 500;
+  }
+  .report-note-form {
+    margin: 15px 0 0 0;
+  }
+  .report-post-note {
+    margin: 5px 0 0 0;
+    text-align: right;
+  }
+  .report-row-key {
+    font-size: 14px;
+    font-weight: 500;
+    padding-right: 5px;
+  }
+  .reported-statuses {
+    margin-top: 15px;
+  }
+  .router-link {
+    text-decoration: none;
+  }
+</style>
diff --git a/src/views/reports/show.vue b/src/views/reports/show.vue
index e393afc0..676a518b 100644
--- a/src/views/reports/show.vue
+++ b/src/views/reports/show.vue
@@ -15,16 +15,19 @@
       </div>
       <reboot-button/>
     </header>
-    <el-card class="report"/>
+    <el-card class="report">
+      <report-content :report="report"/>
+    </el-card>
   </div>
 </template>
 
 <script>
 import RebootButton from '@/components/RebootButton'
+import ReportContent from './components/ReportContent'
 
 export default {
   name: 'ReportsShow',
-  components: { RebootButton },
+  components: { RebootButton, ReportContent },
   computed: {
     loading() {
       return this.$store.state.reports.loading
-- 
GitLab


From 2fce51601fbac071610b335a8b31bef70eb1e7b6 Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Wed, 28 Oct 2020 14:40:28 +0300
Subject: [PATCH 5/8] Fix styles for report show page

---
 src/views/reports/show.vue | 65 ++++++++++++++++++++++----------------
 1 file changed, 37 insertions(+), 28 deletions(-)

diff --git a/src/views/reports/show.vue b/src/views/reports/show.vue
index 676a518b..35468fbb 100644
--- a/src/views/reports/show.vue
+++ b/src/views/reports/show.vue
@@ -1,5 +1,5 @@
 <template>
-  <div v-if="!loading">
+  <div v-if="!loading" class="report-show-page-container">
     <header class="report-page-header-container">
       <div class="report-page-header">
         <div v-if="propertyExists(report.account, 'nickname')" class="avatar-name-container">
@@ -11,10 +11,10 @@
           </a>
         </div>
         <h1 v-else>{{ $t('reports.report') }}</h1>
-        <h4 v-if="propertyExists(report.account, 'id')" class="id">{{ $t('reports.id') }}: {{ report.id }}</h4>
       </div>
       <reboot-button/>
     </header>
+    <h4 v-if="propertyExists(report.account, 'id')" class="id">{{ $t('reports.id') }}: {{ report.id }}</h4>
     <el-card class="report">
       <report-content :report="report"/>
     </el-card>
@@ -53,37 +53,46 @@ export default {
 </script>
 
 <style rel='stylesheet/scss' lang='scss'>
-.report-page-header {
-  display: flex;
-  flex-direction: column;
-  margin: 22px 15px 22px 20px;
-  padding: 0;
-  h1 {
-    display: inline;
-    margin: 0;
+.report-show-page-container {
+  .id {
+    color: gray;
+    margin: 0 15px 22px 15px;
   }
-  h4 {
-    margin-top: 10px;
+  .report {
+    width: 1000px;
+    margin: auto;
   }
-  .avatar-name-container {
+  .report-page-header {
     display: flex;
-    align-items: center;
-    .el-icon-top-right {
-      font-size: 2em;
-      line-height: 36px;
-      color: #606266;
+    flex-direction: column;
+    margin: 10px 0;
+    padding: 0;
+    h1 {
+      display: inline;
+      margin: 0;
+    }
+    h4 {
+      margin-top: 10px;
+    }
+    .avatar-name-container {
+      display: flex;
+      align-items: center;
+      .el-icon-top-right {
+        font-size: 2em;
+        line-height: 36px;
+        color: #606266;
+      }
+    }
+    .report-page-avatar {
+      margin: 0 7px 0 12px;
     }
   }
-  .id {
-    color: gray;
-  }
-  .report-page-avatar {
-    margin: 0 7px 0 12px;
+  .report-page-header-container {
+    align-items: center;
+    display: flex;
+    justify-content: space-between;
+    margin: 0 15px;
+    padding: 0;
   }
 }
-.report-page-header-container {
-  align-items: center;
-  display: flex;
-  justify-content: space-between;
-}
 </style>
-- 
GitLab


From 07c3b12a5e4fecf7bd59f47ae67649b8252ad95d Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 30 Oct 2020 21:23:23 +0300
Subject: [PATCH 6/8] Add dropdown for changing report's state and moderating
 user

---
 src/store/modules/reports.js                  | 64 ++++++++++++++++---
 .../components/ModerateUserDropdown.vue       | 31 +++++++--
 src/views/reports/components/Report.vue       | 12 ++--
 src/views/reports/show.vue                    | 48 +++++++++++++-
 4 files changed, 134 insertions(+), 21 deletions(-)

diff --git a/src/store/modules/reports.js b/src/store/modules/reports.js
index 51f08219..c42fa894 100644
--- a/src/store/modules/reports.js
+++ b/src/store/modules/reports.js
@@ -59,6 +59,17 @@ const reports = {
       }
       dispatch('SuccessMessage')
     },
+    async ActivateUserFromReportShow({ commit, dispatch, getters, state }, user) {
+      try {
+        await activateUsers([user.nickname], getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      } finally {
+        const updatedReport = { ...state.singleReport, account: { ...user, deactivated: false }}
+        commit('SET_SINGLE_REPORT', updatedReport)
+      }
+      dispatch('SuccessMessage')
+    },
     async AddTagFromReports({ commit, dispatch, getters, state }, { user, tag, reportId }) {
       try {
         await tagUser([user.nickname], [tag], getters.authHost, getters.token)
@@ -73,16 +84,31 @@ const reports = {
       }
       dispatch('SuccessMessage')
     },
+    async AddTagFromReportsFromReportShow({ commit, dispatch, getters, state }, { user, tag }) {
+      try {
+        await tagUser([user.nickname], [tag], getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      } finally {
+        const updatedReport = { ...state.singleReport, account: { ...user, tags: [...user.tags, tag] }}
+        commit('SET_SINGLE_REPORT', updatedReport)
+      }
+      dispatch('SuccessMessage')
+    },
     async ChangeReportState({ commit, dispatch, getters, state }, reportsData) {
-      changeState(reportsData, getters.authHost, getters.token)
-
-      const updatedReports = state.fetchedReports.map(report => {
-        const updatedReportsIds = reportsData.map(({ id }) => id)
-        return updatedReportsIds.includes(report.id) ? { ...report, state: reportsData[0].state } : report
-      })
+      try {
+        await changeState(reportsData, getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      } finally {
+        const updatedReports = state.fetchedReports.map(report => {
+          const updatedReportsIds = reportsData.map(({ id }) => id)
+          return updatedReportsIds.includes(report.id) ? { ...report, state: reportsData[0].state } : report
+        })
 
-      commit('SET_REPORTS', updatedReports)
-      dispatch('FetchOpenReportsCount')
+        commit('SET_REPORTS', updatedReports)
+        dispatch('FetchOpenReportsCount')
+      }
     },
     ClearFetchedReports({ commit }) {
       commit('SET_REPORTS', [])
@@ -101,6 +127,17 @@ const reports = {
       }
       dispatch('SuccessMessage')
     },
+    async DeactivateUserFromReportShow({ commit, dispatch, getters, state }, user) {
+      try {
+        await deactivateUsers([user.nickname], getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      } finally {
+        const updatedReport = { ...state.singleReport, account: { ...user, deactivated: true }}
+        commit('SET_SINGLE_REPORT', updatedReport)
+      }
+      dispatch('SuccessMessage')
+    },
     async DeleteUserFromReports({ commit, dispatch, getters, state }, { user, reportId }) {
       try {
         await deleteUsers([user.nickname], getters.authHost, getters.token)
@@ -152,6 +189,17 @@ const reports = {
       }
       dispatch('SuccessMessage')
     },
+    async RemoveTagFromReportsFromReportShow({ commit, dispatch, getters, state }, { user, tag }) {
+      try {
+        await untagUser([user.nickname], [tag], getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      } finally {
+        const updatedReport = { ...state.singleReport, account: { ...user, tags: user.tags.filter(userTag => userTag !== tag) }}
+        commit('SET_SINGLE_REPORT', updatedReport)
+      }
+      dispatch('SuccessMessage')
+    },
     SetReportsFilter({ commit }, filter) {
       commit('SET_REPORTS_FILTER', filter)
     },
diff --git a/src/views/reports/components/ModerateUserDropdown.vue b/src/views/reports/components/ModerateUserDropdown.vue
index cf01971a..9dfb58e4 100644
--- a/src/views/reports/components/ModerateUserDropdown.vue
+++ b/src/views/reports/components/ModerateUserDropdown.vue
@@ -1,6 +1,7 @@
 <template>
   <el-dropdown :hide-on-click="false" trigger="click">
-    <el-button :disabled="!account.id" plain size="small" icon="el-icon-files">{{ $t('reports.moderateUser') }}
+    <el-button :disabled="!account.id" :size="renderedFrom === 'showPage' ? 'medium' : 'small'" plain icon="el-icon-files">
+      {{ $t('reports.moderateUser') }}
       <i class="el-icon-arrow-down el-icon--right"/>
     </el-button>
     <el-dropdown-menu slot="dropdown">
@@ -79,6 +80,10 @@ export default {
     reportId: {
       type: String,
       required: true
+    },
+    renderedFrom: {
+      type: String,
+      required: true
     }
   },
   computed: {
@@ -111,9 +116,15 @@ export default {
       })
     },
     handleDeactivation(user) {
-      user.deactivated
-        ? this.$store.dispatch('ActivateUserFromReports', { user, reportId: this.reportId })
-        : this.$store.dispatch('DeactivateUserFromReports', { user, reportId: this.reportId })
+      if (this.renderedFrom === 'showPage') {
+        user.deactivated
+          ? this.$store.dispatch('ActivateUserFromReportShow', user)
+          : this.$store.dispatch('DeactivateUserFromReportShow', user)
+      } else if (this.renderedFrom === 'reportsPage') {
+        user.deactivated
+          ? this.$store.dispatch('ActivateUserFromReports', { user, reportId: this.reportId })
+          : this.$store.dispatch('DeactivateUserFromReports', { user, reportId: this.reportId })
+      }
     },
     handleDeletion(user) {
       this.$confirm(
@@ -135,9 +146,15 @@ export default {
       return this.$store.state.user.id !== id
     },
     toggleTag(user, tag) {
-      user.tags.includes(tag)
-        ? this.$store.dispatch('RemoveTagFromReports', { user, tag, reportId: this.reportId })
-        : this.$store.dispatch('AddTagFromReports', { user, tag, reportId: this.reportId })
+      if (this.renderedFrom === 'showPage') {
+        user.tags.includes(tag)
+          ? this.$store.dispatch('RemoveTagFromReportsFromReportShow', { user, tag })
+          : this.$store.dispatch('AddTagFromReportsFromReportShow', { user, tag })
+      } else if (this.renderedFrom === 'reportsPage') {
+        user.tags.includes(tag)
+          ? this.$store.dispatch('RemoveTagFromReports', { user, tag, reportId: this.reportId })
+          : this.$store.dispatch('AddTagFromReports', { user, tag, reportId: this.reportId })
+      }
     }
   }
 }
diff --git a/src/views/reports/components/Report.vue b/src/views/reports/components/Report.vue
index 81847635..423aa439 100644
--- a/src/views/reports/components/Report.vue
+++ b/src/views/reports/components/Report.vue
@@ -24,7 +24,11 @@
                   <el-dropdown-item v-if="report.state !== 'closed'" @click.native="changeReportState('closed', report.id)">{{ $t('reports.close') }}</el-dropdown-item>
                 </el-dropdown-menu>
               </el-dropdown>
-              <moderate-user-dropdown v-if="propertyExists(report.account, 'nickname')" :account="report.account" :report-id="report.id" />
+              <moderate-user-dropdown
+                v-if="propertyExists(report.account, 'nickname')"
+                :account="report.account"
+                :report-id="report.id"
+                :rendered-from="'reportsPage'"/>
             </div>
           </div>
           <el-divider class="divider"/>
@@ -74,12 +78,12 @@ export default {
     }
   },
   methods: {
-    changeReportState(state, id) {
-      this.$store.dispatch('ChangeReportState', [{ state, id }])
-    },
     capitalizeFirstLetter(str) {
       return str.charAt(0).toUpperCase() + str.slice(1)
     },
+    changeReportState(state, id) {
+      this.$store.dispatch('ChangeReportState', [{ state, id }])
+    },
     getStateType(state) {
       switch (state) {
         case 'closed':
diff --git a/src/views/reports/show.vue b/src/views/reports/show.vue
index 35468fbb..5f30f376 100644
--- a/src/views/reports/show.vue
+++ b/src/views/reports/show.vue
@@ -12,7 +12,23 @@
         </div>
         <h1 v-else>{{ $t('reports.report') }}</h1>
       </div>
-      <reboot-button/>
+      <div>
+        <el-tag :type="getStateType(report.state)" class="report-tag">{{ capitalizeFirstLetter(report.state) }}</el-tag>
+        <el-dropdown trigger="click">
+          <el-button plain icon="el-icon-edit" class="report-actions-button">{{ $t('reports.changeState') }}<i class="el-icon-arrow-down el-icon--right"/></el-button>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item v-if="report.state !== 'resolved'" @click.native="changeReportState('resolved', report.id)">{{ $t('reports.resolve') }}</el-dropdown-item>
+            <el-dropdown-item v-if="report.state !== 'open'" @click.native="changeReportState('open', report.id)">{{ $t('reports.reopen') }}</el-dropdown-item>
+            <el-dropdown-item v-if="report.state !== 'closed'" @click.native="changeReportState('closed', report.id)">{{ $t('reports.close') }}</el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
+        <moderate-user-dropdown
+          v-if="propertyExists(report.account, 'nickname')"
+          :account="report.account"
+          :report-id="report.id"
+          :rendered-from="'showPage'"/>
+        <reboot-button/>
+      </div>
     </header>
     <h4 v-if="propertyExists(report.account, 'id')" class="id">{{ $t('reports.id') }}: {{ report.id }}</h4>
     <el-card class="report">
@@ -22,12 +38,13 @@
 </template>
 
 <script>
+import ModerateUserDropdown from './components/ModerateUserDropdown'
 import RebootButton from '@/components/RebootButton'
 import ReportContent from './components/ReportContent'
 
 export default {
   name: 'ReportsShow',
-  components: { RebootButton, ReportContent },
+  components: { ModerateUserDropdown, RebootButton, ReportContent },
   computed: {
     loading() {
       return this.$store.state.reports.loading
@@ -40,8 +57,26 @@ export default {
     this.$store.dispatch('NeedReboot')
     this.$store.dispatch('GetNodeInfo')
     this.$store.dispatch('FetchSingleReport', this.$route.params.id)
+    this.$store.dispatch('FetchTagPolicySetting')
   },
   methods: {
+    capitalizeFirstLetter(str) {
+      return str.charAt(0).toUpperCase() + str.slice(1)
+    },
+    async changeReportState(state, id) {
+      await this.$store.dispatch('ChangeReportState', [{ state, id }])
+      this.$store.dispatch('FetchSingleReport', id)
+    },
+    getStateType(state) {
+      switch (state) {
+        case 'closed':
+          return 'info'
+        case 'resolved':
+          return 'success'
+        default:
+          return 'primary'
+      }
+    },
     propertyExists(account, property, _secondProperty) {
       if (_secondProperty) {
         return account[property] && account[_secondProperty]
@@ -62,6 +97,9 @@ export default {
     width: 1000px;
     margin: auto;
   }
+  .report-actions-button {
+    margin: 3px 0 6px;
+  }
   .report-page-header {
     display: flex;
     flex-direction: column;
@@ -94,5 +132,11 @@ export default {
     margin: 0 15px;
     padding: 0;
   }
+  .report-tag {
+    height: 36px;
+    line-height: 36px;
+    padding: 0 20px;
+    font-size: 14px;
+  }
 }
 </style>
-- 
GitLab


From 2145706095d0ba96ae0dd3dcb1f476909a2c68cf Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 30 Oct 2020 21:49:43 +0300
Subject: [PATCH 7/8] Fix moderate user dropdown width

---
 src/views/reports/components/ModerateUserDropdown.vue | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/views/reports/components/ModerateUserDropdown.vue b/src/views/reports/components/ModerateUserDropdown.vue
index 9dfb58e4..3d5618f1 100644
--- a/src/views/reports/components/ModerateUserDropdown.vue
+++ b/src/views/reports/components/ModerateUserDropdown.vue
@@ -4,7 +4,7 @@
       {{ $t('reports.moderateUser') }}
       <i class="el-icon-arrow-down el-icon--right"/>
     </el-button>
-    <el-dropdown-menu slot="dropdown">
+    <el-dropdown-menu slot="dropdown" class="moderate-user-dropdown">
       <el-dropdown-item
         v-if="showDeactivatedButton(account)"
         @click.native="handleDeactivation(account)">
@@ -159,3 +159,9 @@ export default {
   }
 }
 </script>
+
+<style rel='stylesheet/scss' lang='scss'>
+.moderate-user-dropdown {
+  width: 350px;
+}
+</style>
-- 
GitLab


From 43c524797d13e4db3661771807e99d5e1a44c87c Mon Sep 17 00:00:00 2001
From: Angelina Filippova <linakirsanova@gmail.com>
Date: Fri, 30 Oct 2020 21:51:37 +0300
Subject: [PATCH 8/8] Update Changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 205b8203..0d2edbbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 - Evicting and banning objects from the MediaProxy cache is disabled if MediaProxy is disabled on the Settings tab. Add ability to enable MediaProxy and Invalidation from MediaProxy tab.
 - Allow to upload the custom Terms of Service and Instance Panel HTML pages via Admin API
+- Add Report show page and link Moderation log references to the respective reports
 
 ### Changed
 
-- 
GitLab