Merge branch 'feature/remove-grouped-reports' into 'develop'
Remove grouped reports See merge request pleroma/admin-fe!98
This commit is contained in:
commit
5293260870
10 changed files with 24 additions and 336 deletions
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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: '' })
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -273,7 +273,6 @@ export default {
|
|||
},
|
||||
reports: {
|
||||
reports: 'Reports',
|
||||
groupedReports: 'Grouped reports',
|
||||
reply: 'Reply',
|
||||
from: 'From',
|
||||
showNotes: 'Show notes',
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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>
|
|
@ -180,6 +180,9 @@ export default {
|
|||
height: 15px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.divider {
|
||||
margin: 15px 0;
|
||||
}
|
||||
.el-card__body {
|
||||
padding: 17px;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue