Merge branch 'feature/remove-grouped-reports' into 'develop'

Remove grouped reports

See merge request pleroma/admin-fe!98
This commit is contained in:
Angelina Filippova 2020-02-27 17:19:09 +00:00
commit 5293260870
10 changed files with 24 additions and 336 deletions

View file

@ -4,33 +4,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
### Changed
- **breaking** PleromaFE login feature relies on `admin` scope presence in PleromaFE token (older versions of PleromaFE don't support it)
- Moves emoji pack configuration from the main menu to settings tab, redesigns it and fixes bugs
- `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email)
- Remove fetching initial data for configuring server settings
- Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user
- Leave dropdown menu open after clicking an action
- Move current try/catch error handling from view files to module, add it where necessary
- Display checkboxes in status card and fetch statuses only when status card was rendered from Statuses by instance page
- Move statuses by instance state from local state to store state
- Pass user's ID to actions that moderate users when action is called from user's profile page
## [2.0] - 2020-02-27
### Added
- Optimistic update for actions in users module and fetching users after api function finished its execution
- Relay management
- Ability to fetch all statuses from a given instance
- Grouped reports: now you can view reports, which are grouped by status (pagination is not implemented yet, though)
- Ability to confirm users' emails and resend confirmation emails
- Report notes
- Ability to moderate users on the statuses page
- Ability to moderate user on the user's page
- Ability to remove setting's updated value and set it back to initial value
- Ability to restart an application when settings that require instance reboot were changed
- Mobile UI for Settings tab
- Mobile and Tablet UI for all sections
### Changed
- **breaking** PleromaFE login feature relies on `admin` scope presence in PleromaFE token (older versions of PleromaFE don't support it)
- `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email)
- Render inputs for configuring settings based on description that comes from the BE
- Remove fetching initial data for configuring server settings
- Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user
- Leave dropdown menu open after clicking an action
- Display checkboxes in status card and fetch statuses only when status card was rendered from Statuses by instance page
- Move statuses by instance state from local state to store state
### Fixed
@ -39,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Remove duplicated success message
- Fix styles for Statuses by instance page
- Fix styles for Reports section
- Fix listing remote emoji
## [1.2.0] - 2019-09-27

View file

@ -11,40 +11,12 @@ const reports = [
{ created_at: '2019-05-18T13:01:33.000Z', account: { acct: 'nick', display_name: 'Nick Keys', tags: [] }, actor: { acct: 'admin' }, state: 'closed', id: '4', content: '', statuses: [] }
]
const groupedReports = [
{ account: { avatar: 'http://localhost:4000/images/avi.png', confirmation_pending: false, deactivated: false, display_name: 'leo', id: '9oG0YghgBi94EATI9I', local: true, nickname: 'leo', roles: { admin: false, moderator: false }, tags: [] },
actors: [{ acct: 'admin', avatar: 'http://localhost:4000/images/avi.png', deactivated: false, display_name: 'admin', id: '9oFz4pTauG0cnJ581w', local: true, nickname: 'admin', roles: { admin: false, moderator: false }, tags: [], url: 'http://localhost:4000/users/admin', username: 'admin' }],
date: '2019-11-23T12:56:11.969772Z',
reports: [
{ created_at: '2019-05-21T21:35:33.000Z', account: { acct: 'benj', display_name: 'Benjamin Fame', tags: [] }, actor: { acct: 'admin' }, state: 'open', id: '2', content: 'This is a report', statuses: [] },
{ created_at: '2019-05-20T22:45:33.000Z', account: { acct: 'alice', display_name: 'Alice Pool', tags: [] }, actor: { acct: 'admin2' }, state: 'resolved', id: '7', content: 'Please block this user', statuses: [
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'public', sensitive: false, id: '11', content: 'Hey!', url: '', created_at: '2019-05-10T21:35:33.000Z' },
{ account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'unlisted', sensitive: true, id: '10', content: 'Bye!', url: '', created_at: '2019-05-10T21:00:33.000Z' }
] }
],
status: {
account: { acct: 'leo' },
content: 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis',
created_at: '2019-11-23T12:55:20.000Z',
id: '9pFoQO69piu7cUDnJg',
url: 'http://localhost:4000/notice/9pFoQO69piu7cUDnJg',
visibility: 'unlisted',
sensitive: true
},
status_deleted: false
}
]
export async function fetchReports(filter, page, pageSize, authHost, token) {
return filter.length > 0
? Promise.resolve({ data: { reports: reports.filter(report => report.state === filter) }})
: Promise.resolve({ data: { reports }})
}
export async function fetchGroupedReports(authHost, token) {
return Promise.resolve({ data: { reports: groupedReports }})
}
export async function changeState(reportsData, authHost, token) {
return Promise.resolve({ data: '' })
}

View file

@ -24,15 +24,6 @@ export async function fetchReports(filter, page, pageSize, authHost, token) {
})
}
export async function fetchGroupedReports(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/grouped_reports`,
method: 'get',
headers: authHeaders(token)
})
}
export async function createNote(content, reportID, authHost, token) {
return await request({
baseURL: baseName(authHost),

View file

@ -273,7 +273,6 @@ export default {
},
reports: {
reports: 'Reports',
groupedReports: 'Grouped reports',
reply: 'Reply',
from: 'From',
showNotes: 'Show notes',

View file

@ -1,13 +1,11 @@
import { changeState, fetchReports, fetchGroupedReports, createNote, deleteNote } from '@/api/reports'
import { changeState, fetchReports, createNote, deleteNote } from '@/api/reports'
const reports = {
state: {
fetchedReports: [],
fetchedGroupedReports: [],
totalReportsCount: 0,
currentPage: 1,
pageSize: 50,
groupReports: false,
stateFilter: '',
loading: true
},
@ -24,17 +22,11 @@ const reports = {
SET_REPORTS: (state, reports) => {
state.fetchedReports = reports
},
SET_GROUPED_REPORTS: (state, reports) => {
state.fetchedGroupedReports = reports
},
SET_REPORTS_COUNT: (state, total) => {
state.totalReportsCount = total
},
SET_REPORTS_FILTER: (state, filter) => {
state.stateFilter = filter
},
SET_REPORTS_GROUPING: (state) => {
state.groupReports = !state.groupReports
}
},
actions: {
@ -46,14 +38,7 @@ const reports = {
return updatedReportsIds.includes(report.id) ? { ...report, state: reportsData[0].state } : report
})
const updatedGroupedReports = state.fetchedGroupedReports.map(group => {
const updatedReportsIds = reportsData.map(({ id }) => id)
const updatedReports = group.reports.map(report => updatedReportsIds.includes(report.id) ? { ...report, state: reportsData[0].state } : report)
return { ...group, reports: updatedReports }
})
commit('SET_REPORTS', updatedReports)
commit('SET_GROUPED_REPORTS', updatedGroupedReports)
},
ClearFetchedReports({ commit }) {
commit('SET_REPORTS', [])
@ -67,19 +52,9 @@ const reports = {
commit('SET_PAGE', page)
commit('SET_LOADING', false)
},
async FetchGroupedReports({ commit, getters }) {
commit('SET_LOADING', true)
const { data } = await fetchGroupedReports(getters.authHost, getters.token)
commit('SET_GROUPED_REPORTS', data.reports)
commit('SET_LOADING', false)
},
SetFilter({ commit }, filter) {
commit('SET_REPORTS_FILTER', filter)
},
ToggleReportsGrouping({ commit }) {
commit('SET_REPORTS_GROUPING')
},
CreateReportNote({ commit, getters, state, rootState }, { content, reportID }) {
createNote(content, reportID, getters.authHost, getters.token)

View file

@ -36,8 +36,6 @@ const status = {
dispatch('FetchUserStatuses', { userId, godmode })
} else if (fetchStatusesByInstance) { // called from Statuses by Instance
dispatch('FetchStatusesByInstance')
} else { // called from GroupedReports
dispatch('FetchGroupedReports')
}
},
async DeleteStatus({ dispatch, getters }, { statusId, reportCurrentPage, userId, godmode, fetchStatusesByInstance }) {
@ -48,8 +46,6 @@ const status = {
dispatch('FetchUserStatuses', { userId, godmode })
} else if (fetchStatusesByInstance) { // called from Statuses by Instance
dispatch('FetchStatusesByInstance')
} else { // called from GroupedReports
dispatch('FetchGroupedReports')
}
},
async FetchStatusesByInstance({ commit, getters, state }) {

View file

@ -1,169 +0,0 @@
<template>
<el-timeline class="reports-timeline">
<el-timeline-item
v-for="groupedReport in groupedReports"
:key="groupedReport.id"
:timestamp="parseTimestamp(groupedReport.date)"
placement="top"
class="timeline-item-container">
<el-card class="grouped-report">
<div class="header-container">
<div>
<h3 class="report-title">{{ $t('reports.reportsOn') }} {{ groupedReport.account.display_name }}</h3>
</div>
<div>
<el-dropdown trigger="click">
<el-button plain size="small" icon="el-icon-edit" class="report-actions-button">{{ $t('reports.changeAllReports') }}<i class="el-icon-arrow-down el-icon--right"/></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="changeAllReports('resolved', groupedReport.reports)">{{ $t('reports.resolveAll') }}</el-dropdown-item>
<el-dropdown-item @click.native="changeAllReports('open', groupedReport.reports)">{{ $t('reports.reopenAll') }}</el-dropdown-item>
<el-dropdown-item @click.native="changeAllReports('closed', groupedReport.reports)">{{ $t('reports.closeAll') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<moderate-user-dropdown :account="groupedReport.account"/>
</div>
</div>
<div>
<el-divider class="divider"/>
<span class="report-row-key">{{ $t('reports.account') }}:</span>
<img
:src="groupedReport.account.avatar"
alt="avatar"
class="avatar-img">
<a :href="groupedReport.account.url" target="_blank">
<span>{{ groupedReport.account.nickname }}</span>
</a>
</div>
<div>
<el-divider class="divider"/>
<span class="report-row-key">{{ $t('reports.actors') }}:</span>
<span v-for="(actor, index) in groupedReport.actors" :key="actor.id">
<a :href="actor.url" target="_blank">
{{ actor.acct }}<span v-if="index < groupedReport.actors.length - 1">, </span>
</a>
</span>
</div>
<div v-if="groupedReport.status">
<el-divider class="divider"/>
<span class="report-row-key">{{ $t('reports.reportedStatus') }}:</span>
<status :status="groupedReport.status" :show-checkbox="false" class="reported-status"/>
</div>
<div v-if="groupedReport.reports">
<el-collapse>
<el-collapse-item :title="$t('reports.reports')">
<report-card :reports="groupedReport.reports"/>
</el-collapse-item>
</el-collapse>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
</template>
<script>
import moment from 'moment'
import ModerateUserDropdown from './ModerateUserDropdown'
import ReportCard from './ReportCard'
import Status from '@/components/Status'
export default {
name: 'Report',
components: { ModerateUserDropdown, ReportCard, Status },
props: {
groupedReports: {
type: Array,
required: true
}
},
methods: {
changeAllReports(reportState, groupOfReports) {
const reportsData = groupOfReports.map(report => {
return { id: report.id, state: reportState }
})
this.$store.dispatch('ChangeReportState', reportsData)
},
parseTimestamp(timestamp) {
return moment(timestamp).format('L HH:mm')
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
a {
text-decoration: underline;
}
.avatar-img {
vertical-align: bottom;
width: 15px;
height: 15px;
margin-left: 5px;
}
.el-card__body {
padding: 17px;
}
.el-card__header {
background-color: #FAFAFA;
padding: 10px 20px;
}
.el-icon-arrow-right {
margin-right: 6px;
}
.divider {
margin: 15px 0;
}
.grouped-report {
.header-container {
display: flex;
justify-content: space-between;
align-items: baseline;
height: 36px;
}
}
.line {
width: 100%;
height: 0;
border: 0.5px solid #EBEEF5;
margin: 15px 0 15px;
}
.report-title {
margin: 0;
}
.report-row-key {
font-size: 14px;
font-weight: 500;
}
.reports-timeline {
margin: 30px 45px 45px 19px;
padding: 0px;
}
.reported-status {
margin-top: 15px;
}
@media only screen and (max-width:480px) {
.grouped-report {
.header-container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
height: auto;
}
.report-actions-button {
margin: 3px 0 6px;
}
.report-title {
margin-bottom: 7px;
}
}
.block {
.reports-timeline .el-timeline {
margin: 20px 10px;
.el-timeline-item__wrapper {
padding-left: 20px;
}
}
}
}
</style>

View file

@ -180,6 +180,9 @@ export default {
height: 15px;
margin-left: 5px;
}
.divider {
margin: 15px 0;
}
.el-card__body {
padding: 17px;
}

View file

@ -1,22 +1,14 @@
<template>
<div class="reports-container">
<h1 v-if="groupReports">
{{ $t('reports.groupedReports') }}
<span class="report-count">({{ normalizedReportsCount }})</span>
</h1>
<h1 v-else>
<h1>
{{ $t('reports.reports') }}
<span class="report-count">({{ normalizedReportsCount }})</span>
</h1>
<div class="reports-filter-container">
<reports-filter v-if="!groupReports"/>
<el-checkbox v-model="groupReports" class="group-reports-checkbox">
Group reports by statuses
</el-checkbox>
<reports-filter/>
</div>
<div class="block">
<grouped-report v-loading="loading" v-if="groupReports" :grouped-reports="groupedReports"/>
<report v-loading="loading" v-else :reports="reports"/>
<report v-loading="loading" :reports="reports"/>
<div v-if="reports.length === 0" class="no-reports-message">
<p>There are no reports to display</p>
</div>
@ -25,32 +17,18 @@
</template>
<script>
import GroupedReport from './components/GroupedReport'
import numeral from 'numeral'
import Report from './components/Report'
import ReportsFilter from './components/ReportsFilter'
export default {
components: { GroupedReport, Report, ReportsFilter },
components: { Report, ReportsFilter },
computed: {
groupedReports() {
return this.$store.state.reports.fetchedGroupedReports
},
groupReports: {
get() {
return this.$store.state.reports.groupReports
},
set() {
this.toggleReportsGrouping()
}
},
loading() {
return this.$store.state.reports.loading
},
normalizedReportsCount() {
return this.groupReports
? numeral(this.$store.state.reports.fetchedGroupedReports.length).format('0a')
: numeral(this.$store.state.reports.totalReportsCount).format('0a')
return numeral(this.$store.state.reports.totalReportsCount).format('0a')
},
reports() {
return this.$store.state.reports.fetchedReports
@ -58,12 +36,6 @@ export default {
},
mounted() {
this.$store.dispatch('FetchReports', 1)
this.$store.dispatch('FetchGroupedReports')
},
methods: {
toggleReportsGrouping() {
this.$store.dispatch('ToggleReportsGrouping')
}
}
}
</script>
@ -77,9 +49,6 @@ export default {
margin: 22px 15px 22px 15px;
padding-bottom: 0
}
.group-reports-checkbox {
margin-top: 10px;
}
h1 {
margin: 22px 0 0 15px;
}

View file

@ -1,47 +0,0 @@
import Vuex from 'vuex'
import { mount, createLocalVue, config } from '@vue/test-utils'
import Element from 'element-ui'
import GroupedReport from '@/views/reports/components/GroupedReport'
import storeConfig from './store.conf'
import { cloneDeep } from 'lodash'
import flushPromises from 'flush-promises'
config.mocks["$t"] = () => {}
const localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(Element)
jest.mock('@/api/reports')
describe('Grouped report', () => {
let store
beforeEach(async() => {
store = new Vuex.Store(cloneDeep(storeConfig))
store.dispatch('FetchGroupedReports')
await flushPromises()
})
it('changes state of all reports in a group', async (done) => {
const groupedReports = store.state.reports.fetchedGroupedReports
const wrapper = mount(GroupedReport, {
store,
localVue,
propsData: {
groupedReports
}
})
expect(groupedReports[0].reports[0].state).toBe('open')
expect(groupedReports[0].reports[1].state).toBe('resolved')
const button = wrapper.find(`.grouped-report .el-dropdown-menu__item:nth-child(3)`)
button.trigger('click')
await flushPromises()
expect(store.state.reports.fetchedGroupedReports[0].reports[0].state).toBe('closed')
expect(store.state.reports.fetchedGroupedReports[0].reports[1].state).toBe('closed')
done()
})
})