From dc835826294081fd2d18a7625a2f1c5794e88d51 Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Thu, 28 Nov 2019 11:04:05 +0000 Subject: [PATCH] Combine reports with one topic and add pagination --- src/api/__mocks__/reports.js | 39 ++- src/api/__mocks__/status.js | 7 + src/api/reports.js | 34 +-- src/api/status.js | 24 ++ src/lang/en.js | 18 +- src/store/index.js | 2 + src/store/modules/reports.js | 83 +++--- src/store/modules/status.js | 28 ++ src/store/modules/userProfile.js | 41 +-- .../reports/components/GroupedReport.vue | 143 +++++++++ .../components/ModerateUserDropdown.vue | 86 ++++++ src/views/reports/components/Report.vue | 250 ++++++++++++++++ src/views/reports/components/ReportCard.vue | 130 +++++++++ .../reports/components/ReportsFilter.vue | 2 +- src/views/reports/components/Statuses.vue | 176 ------------ src/views/reports/components/TimelineItem.vue | 271 ------------------ src/views/reports/index.vue | 75 +++-- src/views/status/Status.vue | 269 +++++++++++++++++ src/views/users/show.vue | 55 ++-- test/views/reports/groupedReport.test.js | 47 +++ test/views/reports/index.test.js | 17 +- test/views/reports/report.test.js | 75 +++++ test/views/reports/reportsFilter.test.js | 12 +- test/views/reports/status.test.js | 151 ++++++++++ test/views/reports/store.conf.js | 4 +- test/views/reports/timelineItem.test.js | 157 ---------- 26 files changed, 1414 insertions(+), 782 deletions(-) create mode 100644 src/api/__mocks__/status.js create mode 100644 src/api/status.js create mode 100644 src/store/modules/status.js create mode 100644 src/views/reports/components/GroupedReport.vue create mode 100644 src/views/reports/components/ModerateUserDropdown.vue create mode 100644 src/views/reports/components/Report.vue create mode 100644 src/views/reports/components/ReportCard.vue delete mode 100644 src/views/reports/components/Statuses.vue delete mode 100644 src/views/reports/components/TimelineItem.vue create mode 100644 src/views/status/Status.vue create mode 100644 test/views/reports/groupedReport.test.js create mode 100644 test/views/reports/report.test.js create mode 100644 test/views/reports/status.test.js delete mode 100644 test/views/reports/timelineItem.test.js diff --git a/src/api/__mocks__/reports.js b/src/api/__mocks__/reports.js index e277d8df..ba4e412e 100644 --- a/src/api/__mocks__/reports.js +++ b/src/api/__mocks__/reports.js @@ -11,18 +11,41 @@ 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: [] } ] -export async function fetchReports(limit, max_id, authHost, token) { - const paginatedReports = max_id.length > 0 ? reports.slice(5) : reports.slice(0, 5) - return Promise.resolve({ data: { reports: paginatedReports }}) +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 filterReports(filter, limit, max_id, authHost, token) { - const filteredReports = reports.filter(report => report.state === filter) - const paginatedReports = max_id.length > 0 ? filteredReports.slice(5) : filteredReports.slice(0, 5) - return Promise.resolve({ data: { reports: paginatedReports }}) +export async function fetchGroupedReports(authHost, token) { + return Promise.resolve({ data: { reports: groupedReports }}) } -export async function changeState(state, id, authHost, token) { +export async function changeState(reportsData, authHost, token) { return Promise.resolve({ data: '' }) } diff --git a/src/api/__mocks__/status.js b/src/api/__mocks__/status.js new file mode 100644 index 00000000..0e7bc9fb --- /dev/null +++ b/src/api/__mocks__/status.js @@ -0,0 +1,7 @@ +export async function changeStatusScope(id, sensitive, visibility, authHost, token) { + return Promise.resolve() +} + +export async function deleteStatus(id, authHost, token) { + return Promise.resolve() +} diff --git a/src/api/reports.js b/src/api/reports.js index 79833e40..373e5bd5 100644 --- a/src/api/reports.js +++ b/src/api/reports.js @@ -2,48 +2,32 @@ import request from '@/utils/request' import { getToken } from '@/utils/auth' import { baseName } from './utils' -export async function changeState(state, id, authHost, token) { +export async function changeState(reports, authHost, token) { return await request({ baseURL: baseName(authHost), url: `/api/pleroma/admin/reports`, method: 'patch', headers: authHeaders(token), - data: { reports: [{ id, state }] } + data: { reports } }) } -export async function changeStatusScope(id, sensitive, visibility, authHost, token) { +export async function fetchReports(filter, page, pageSize, authHost, token) { + const url = filter.length > 0 + ? `/api/pleroma/admin/reports?state=${filter}&page=${page}&page_size=${pageSize}` + : `/api/pleroma/admin/reports?page=${page}&page_size=${pageSize}` return await request({ baseURL: baseName(authHost), - url: `/api/pleroma/admin/statuses/${id}`, - method: 'put', - headers: authHeaders(token), - data: { sensitive, visibility } - }) -} - -export async function deleteStatus(id, authHost, token) { - return await request({ - baseURL: baseName(authHost), - url: `/api/pleroma/admin/statuses/${id}`, - method: 'delete', - headers: authHeaders(token) - }) -} - -export async function fetchReports(limit, max_id, authHost, token) { - return await request({ - baseURL: baseName(authHost), - url: `/api/pleroma/admin/reports?limit=${limit}&max_id=${max_id}`, + url, method: 'get', headers: authHeaders(token) }) } -export async function filterReports(filter, limit, max_id, authHost, token) { +export async function fetchGroupedReports(authHost, token) { return await request({ baseURL: baseName(authHost), - url: `/api/pleroma/admin/reports?state=${filter}&limit=${limit}&max_id=${max_id}`, + url: `/api/pleroma/admin/grouped_reports`, method: 'get', headers: authHeaders(token) }) diff --git a/src/api/status.js b/src/api/status.js new file mode 100644 index 00000000..7d931ae0 --- /dev/null +++ b/src/api/status.js @@ -0,0 +1,24 @@ +import request from '@/utils/request' +import { getToken } from '@/utils/auth' +import { baseName } from './utils' + +export async function changeStatusScope(id, sensitive, visibility, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/statuses/${id}`, + method: 'put', + headers: authHeaders(token), + data: { sensitive, visibility } + }) +} + +export async function deleteStatus(id, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/statuses/${id}`, + method: 'delete', + headers: authHeaders(token) + }) +} + +const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/lang/en.js b/src/lang/en.js index fee486d0..d4893455 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -253,6 +253,7 @@ export default { }, reports: { reports: 'Reports', + groupedReports: 'Grouped reports', reply: 'Reply', from: 'From', showNotes: 'Show notes', @@ -264,19 +265,32 @@ export default { deleteCompleted: 'Delete comleted', deleteCanceled: 'Delete canceled', noNotes: 'No notes to display', - changeState: 'Change report state', + changeState: "Change report's state", + changeAllReports: 'Change all reports', changeScope: 'Change scope', moderateUser: 'Moderate user', resolve: 'Resolve', reopen: 'Reopen', close: 'Close', + resolveAll: 'Resolve all', + reopenAll: 'Reopen all', + closeAll: 'Close all', addSensitive: 'Add Sensitive flag', removeSensitive: 'Remove Sensitive flag', public: 'Make status public', private: 'Make status private', unlisted: 'Make status unlisted', sensitive: 'Sensitive', - deleteStatus: 'Delete status' + deleteStatus: 'Delete status', + reportOn: 'Report on', + reportsOn: 'Reports on', + id: 'ID', + account: 'Account', + actor: 'Actor', + actors: 'Actors', + content: 'Content', + reportedStatus: 'Reported status', + statusDeleted: 'This status has been deleted' }, reportsFilter: { inputPlaceholder: 'Select filter', diff --git a/src/store/index.js b/src/store/index.js index c71fca87..87301b69 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -8,6 +8,7 @@ import permission from './modules/permission' import relays from './modules/relays' import reports from './modules/reports' import settings from './modules/settings' +import status from './modules/status' import tagsView from './modules/tagsView' import user from './modules/user' import userProfile from './modules/userProfile' @@ -27,6 +28,7 @@ const store = new Vuex.Store({ relays, reports, settings, + status, tagsView, user, userProfile, diff --git a/src/store/modules/reports.js b/src/store/modules/reports.js index e44664f4..d62f1b55 100644 --- a/src/store/modules/reports.js +++ b/src/store/modules/reports.js @@ -1,10 +1,13 @@ -import { changeState, changeStatusScope, deleteStatus, fetchReports, filterReports } from '@/api/reports' +import { changeState, fetchReports, fetchGroupedReports } from '@/api/reports' const reports = { state: { fetchedReports: [], - idOfLastReport: '', - page_limit: 5, + fetchedGroupedReports: [], + totalReportsCount: 0, + currentPage: 1, + pageSize: 50, + groupReports: false, stateFilter: '', loading: true }, @@ -15,67 +18,67 @@ const reports = { SET_LOADING: (state, status) => { state.loading = status }, + SET_PAGE: (state, page) => { + state.currentPage = page + }, 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: { - async ChangeReportState({ commit, getters, state }, { reportState, reportId }) { - changeState(reportState, reportId, getters.authHost, getters.token) + async ChangeReportState({ commit, getters, state }, reportsData) { + changeState(reportsData, getters.authHost, getters.token) const updatedReports = state.fetchedReports.map(report => { - return report.id === reportId ? { ...report, state: reportState } : report + const updatedReportsIds = reportsData.map(({ id }) => id) + 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) - }, - async ChangeStatusScope({ commit, getters, state }, { statusId, isSensitive, visibility, reportId }) { - const { data } = await changeStatusScope(statusId, isSensitive, visibility, getters.authHost, getters.token) - const updatedReports = state.fetchedReports.map(report => { - if (report.id === reportId) { - const statuses = report.statuses.map(status => status.id === statusId ? data : status) - return { ...report, statuses } - } else { - return report - } - }) - commit('SET_REPORTS', updatedReports) + commit('SET_GROUPED_REPORTS', updatedGroupedReports) }, ClearFetchedReports({ commit }) { commit('SET_REPORTS', []) - commit('SET_LAST_REPORT_ID', '') }, - async DeleteStatus({ commit, getters, state }, { statusId, reportId }) { - deleteStatus(statusId, getters.authHost, getters.token) - const updatedReports = state.fetchedReports.map(report => { - if (report.id === reportId) { - const statuses = report.statuses.filter(status => status.id !== statusId) - return { ...report, statuses } - } else { - return report - } - }) - commit('SET_REPORTS', updatedReports) - }, - async FetchReports({ commit, getters, state }) { + async FetchReports({ commit, getters, state }, page) { commit('SET_LOADING', true) + const { data } = await fetchReports(state.stateFilter, page, state.pageSize, getters.authHost, getters.token) - const response = state.stateFilter.length === 0 - ? await fetchReports(state.page_limit, state.idOfLastReport, getters.authHost, getters.token) - : await filterReports(state.stateFilter, state.page_limit, state.idOfLastReport, getters.authHost, getters.token) + commit('SET_REPORTS', data.reports) + commit('SET_REPORTS_COUNT', data.total) + commit('SET_PAGE', page) + commit('SET_LOADING', false) + }, + async FetchGroupedReports({ commit, getters }) { + commit('SET_LOADING', true) + const { data } = await fetchGroupedReports(getters.authHost, getters.token) - const reports = state.fetchedReports.concat(response.data.reports) - const id = reports.length > 0 ? reports[reports.length - 1].id : state.idOfLastReport - - commit('SET_REPORTS', reports) - commit('SET_LAST_REPORT_ID', id) + commit('SET_GROUPED_REPORTS', data.reports) commit('SET_LOADING', false) }, SetFilter({ commit }, filter) { commit('SET_REPORTS_FILTER', filter) + }, + ToggleReportsGrouping({ commit }) { + commit('SET_REPORTS_GROUPING') } } } diff --git a/src/store/modules/status.js b/src/store/modules/status.js new file mode 100644 index 00000000..7d19d895 --- /dev/null +++ b/src/store/modules/status.js @@ -0,0 +1,28 @@ +import { changeStatusScope, deleteStatus } from '@/api/status' + +const status = { + actions: { + async ChangeStatusScope({ dispatch, getters }, { statusId, isSensitive, visibility, reportCurrentPage, userId, godmode }) { + await changeStatusScope(statusId, isSensitive, visibility, getters.authHost, getters.token) + if (reportCurrentPage !== 0) { // called from Reports + dispatch('FetchReports', reportCurrentPage) + } else if (userId.length > 0) { // called from User profile + dispatch('FetchUserStatuses', { userId, godmode }) + } else { // called from GroupedReports + dispatch('FetchGroupedReports') + } + }, + async DeleteStatus({ dispatch, getters }, { statusId, reportCurrentPage, userId, godmode }) { + await deleteStatus(statusId, getters.authHost, getters.token) + if (reportCurrentPage !== 0) { + dispatch('FetchReports', reportCurrentPage) + } else if (userId.length > 0) { + dispatch('FetchUserStatuses', { userId, godmode }) + } else { + dispatch('FetchGroupedReports') + } + } + } +} + +export default status diff --git a/src/store/modules/userProfile.js b/src/store/modules/userProfile.js index 2dd656e6..5a7e4394 100644 --- a/src/store/modules/userProfile.js +++ b/src/store/modules/userProfile.js @@ -2,33 +2,42 @@ import { fetchUser, fetchUserStatuses } from '@/api/users' const userProfile = { state: { + statuses: [], + statusesLoading: true, user: {}, - loading: true, - statuses: [] + userProfileLoading: true }, mutations: { + SET_STATUSES: (state, statuses) => { + state.statuses = statuses + }, + SET_STATUSES_LOADING: (state, status) => { + state.statusesLoading = status + }, SET_USER: (state, user) => { state.user = user }, - SET_LOADING: (state, status) => { - state.loading = status - }, - SET_STATUSES: (state, statuses) => { - state.statuses = statuses + SET_USER_PROFILE_LOADING: (state, status) => { + state.userProfileLoading = status } }, actions: { - async FetchData({ commit, getters }, { id, godmode }) { - commit('SET_LOADING', true) - - const [userResponse, statusesResponse] = await Promise.all([ - fetchUser(id, getters.authHost, getters.token), - fetchUserStatuses(id, getters.authHost, godmode, getters.token) - ]) + async FetchUserProfile({ commit, dispatch, getters }, { userId, godmode }) { + commit('SET_USER_PROFILE_LOADING', true) + const userResponse = await fetchUser(userId, getters.authHost, getters.token) commit('SET_USER', userResponse.data) - commit('SET_STATUSES', statusesResponse.data) - commit('SET_LOADING', false) + commit('SET_USER_PROFILE_LOADING', false) + + dispatch('FetchUserStatuses', { userId, godmode }) + }, + async FetchUserStatuses({ commit, getters }, { userId, godmode }) { + commit('SET_STATUSES_LOADING', true) + + const statuses = await fetchUserStatuses(userId, getters.authHost, godmode, getters.token) + + commit('SET_STATUSES', statuses.data) + commit('SET_STATUSES_LOADING', false) } } } diff --git a/src/views/reports/components/GroupedReport.vue b/src/views/reports/components/GroupedReport.vue new file mode 100644 index 00000000..e11ba313 --- /dev/null +++ b/src/views/reports/components/GroupedReport.vue @@ -0,0 +1,143 @@ + + + + + diff --git a/src/views/reports/components/ModerateUserDropdown.vue b/src/views/reports/components/ModerateUserDropdown.vue new file mode 100644 index 00000000..2d368999 --- /dev/null +++ b/src/views/reports/components/ModerateUserDropdown.vue @@ -0,0 +1,86 @@ + + + diff --git a/src/views/reports/components/Report.vue b/src/views/reports/components/Report.vue new file mode 100644 index 00000000..7fc5d051 --- /dev/null +++ b/src/views/reports/components/Report.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/views/reports/components/ReportCard.vue b/src/views/reports/components/ReportCard.vue new file mode 100644 index 00000000..67b0b01c --- /dev/null +++ b/src/views/reports/components/ReportCard.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/src/views/reports/components/ReportsFilter.vue b/src/views/reports/components/ReportsFilter.vue index 40c23216..da1fd782 100644 --- a/src/views/reports/components/ReportsFilter.vue +++ b/src/views/reports/components/ReportsFilter.vue @@ -44,7 +44,7 @@ export default { toggleFilters() { this.$store.dispatch('SetFilter', this.$data.filter) this.$store.dispatch('ClearFetchedReports') - this.$store.dispatch('FetchReports') + this.$store.dispatch('FetchReports', 1) } } } diff --git a/src/views/reports/components/Statuses.vue b/src/views/reports/components/Statuses.vue deleted file mode 100644 index a385a700..00000000 --- a/src/views/reports/components/Statuses.vue +++ /dev/null @@ -1,176 +0,0 @@ - - - - - diff --git a/src/views/reports/components/TimelineItem.vue b/src/views/reports/components/TimelineItem.vue deleted file mode 100644 index e7e41e7b..00000000 --- a/src/views/reports/components/TimelineItem.vue +++ /dev/null @@ -1,271 +0,0 @@ - - - - - diff --git a/src/views/reports/index.vue b/src/views/reports/index.vue index 63a830ae..3c87d3e5 100644 --- a/src/views/reports/index.vue +++ b/src/views/reports/index.vue @@ -1,13 +1,22 @@ + + diff --git a/src/views/users/show.vue b/src/views/users/show.vue index 7c733d13..f721a7f5 100644 --- a/src/views/users/show.vue +++ b/src/views/users/show.vue @@ -1,5 +1,5 @@