Skip to content

Commit

Permalink
API & Client - display in admin in unresolved reports exists
Browse files Browse the repository at this point in the history
  • Loading branch information
SamR1 committed Nov 10, 2024
1 parent 63db498 commit f5f62d3
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 9 deletions.
14 changes: 14 additions & 0 deletions fittrackee/reports/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,17 @@ def process_appeal(
return InvalidPayloadErrorResponse(str(e))
except (exc.OperationalError, exc.IntegrityError, ValueError) as e:
return handle_error_and_return_response(e, db=db)


@reports_blueprint.route("/reports/unresolved", methods=["GET"])
@require_auth(scopes=["reports:read"], as_admin=True)
def get_unresolved_reports_status(
auth_user: User,
) -> Union[Tuple[Dict, int], HttpResponse]:
unresolved_reports = Report.query.filter(
Report.resolved == False # noqa
).count()
return {
"status": "success",
"unresolved": unresolved_reports > 0,
}, 200
133 changes: 132 additions & 1 deletion fittrackee/tests/reports/test_reports_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ def test_it_returns_all_reports(
"total": 3,
}

def test_it_returns_reports_when_not_visible(
def test_it_returns_reports_when_reported_object_not_visible_to_admin(
self,
app: Flask,
user_1_admin: User,
Expand Down Expand Up @@ -3906,3 +3906,134 @@ def test_expected_scopes_are_defined(
)

self.assert_response_scope(response, can_access)


class TestGetReportsUnresolved(ReportTestCase):
route = "/api/reports/unresolved"

def test_it_returns_error_if_user_is_not_authenticated(
self, app: Flask
) -> None:
client = app.test_client()

response = client.get(self.route, content_type="application/json")

self.assert_401(response)

def test_it_returns_error_if_user_has_no_admin_rights(
self, app: Flask, user_1: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1.email
)

response = client.get(
self.route,
content_type="application/json",
headers=dict(Authorization=f"Bearer {auth_token}"),
)

self.assert_403(response)

def test_it_returns_false_when_no_reports(
self, app: Flask, user_1_admin: User
) -> None:
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)

response = client.get(
self.route,
content_type="application/json",
headers=dict(Authorization=f"Bearer {auth_token}"),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert data["status"] == "success"
assert data["unresolved"] is False

def test_it_returns_false_when_reports_are_resolved(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
for reported_user in [user_2, user_3]:
report = self.create_report(
reporter=user_1_admin, reported_object=reported_user
)
report.resolved = True
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)

response = client.get(
self.route,
content_type="application/json",
headers=dict(Authorization=f"Bearer {auth_token}"),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert data["status"] == "success"
assert data["unresolved"] is False

def test_it_returns_true_when_unresolved_reports_exist(
self,
app: Flask,
user_1_admin: User,
user_2: User,
user_3: User,
) -> None:
for reported_user in [user_2, user_3]:
report = self.create_report(
reporter=user_1_admin, reported_object=reported_user
)
if reported_user.id == user_3.id:
report.resolved = True
db.session.commit()
client, auth_token = self.get_test_client_and_auth_token(
app, user_1_admin.email
)

response = client.get(
self.route,
content_type="application/json",
headers=dict(Authorization=f"Bearer {auth_token}"),
)

assert response.status_code == 200
data = json.loads(response.data.decode())
assert data["status"] == "success"
assert data["unresolved"] is True

@pytest.mark.parametrize(
"client_scope, can_access",
{**OAUTH_SCOPES, "reports:read": True}.items(),
)
def test_expected_scopes_are_defined(
self,
app: Flask,
user_1_admin: User,
client_scope: str,
can_access: bool,
) -> None:
(
client,
oauth_client,
access_token,
_,
) = self.create_oauth2_client_and_issue_token(
app, user_1_admin, scope=client_scope
)

response = client.get(
self.route,
content_type="application/json",
headers=dict(Authorization=f'Bearer {access_token}'),
)

self.assert_response_scope(response, can_access)
18 changes: 15 additions & 3 deletions fittrackee_client/src/components/Administration/AdminMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@
{{ $t('admin.APP_MODERATION.TITLE') }}
</router-link>
</dt>
<dd>
<dd class="application-config-details">
{{ $t('admin.APP_MODERATION.DESCRIPTION') }}
<router-link
to="/admin/reports?resolved=false"
v-if="unresolvedReportsStatus"
>
{{ $t('admin.APP_MODERATION.UNRESOLVED_REPORTS_EXIST') }}
</router-link>
</dd>
<dt>
<router-link to="/admin/sports">
Expand All @@ -70,13 +76,13 @@
</template>

<script setup lang="ts">
import { capitalize, computed, onMounted } from 'vue'
import { capitalize, computed, onBeforeMount, onMounted } from 'vue'
import type { ComputedRef } from 'vue'
import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
import Card from '@/components/Common/Card.vue'
import useApp from '@/composables/useApp'
import { ROOT_STORE } from '@/store/constants'
import { REPORTS_STORE, ROOT_STORE } from '@/store/constants'
import type { IAppStatistics } from '@/types/application'
import { useStore } from '@/use/useStore'
Expand All @@ -87,7 +93,13 @@
const appStatistics: ComputedRef<IAppStatistics> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_STATS]
)
const unresolvedReportsStatus: ComputedRef<boolean> = computed(
() => store.getters[REPORTS_STORE.GETTERS.UNRESOLVED_REPORTS_STATUS]
)
onBeforeMount(() =>
store.dispatch(REPORTS_STORE.ACTIONS.GET_UNRESOLVED_REPORTS_STATUS)
)
onMounted(() => {
const applicationLink = document.getElementById('adminLink')
if (applicationLink) {
Expand Down
1 change: 1 addition & 0 deletions fittrackee_client/src/locales/en/administration.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"UPDATE_APPEAL": "Indicate the reason for this appeal processing."
},
"TITLE": "Moderation",
"UNRESOLVED_REPORTS_EXIST": "There are unresolved reports.",
"VIEW_REPORT": "View report"
},
"BACK_TO_ADMIN": "Back to admin",
Expand Down
1 change: 1 addition & 0 deletions fittrackee_client/src/locales/fr/administration.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"UPDATE_APPEAL": "Indiquer le motif du traitement de ce recours."
},
"TITLE": "Modération",
"UNRESOLVED_REPORTS_EXIST": "Il existe des rapports non résolus.",
"VIEW_REPORT": "voir le report"
},
"BACK_TO_ADMIN": "Revenir à l'admin",
Expand Down
20 changes: 20 additions & 0 deletions fittrackee_client/src/store/modules/reports/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,24 @@ export const actions: ActionTree<IReportsState, IRootState> & IReportsActions =
)
)
},

[REPORTS_STORE.ACTIONS.GET_UNRESOLVED_REPORTS_STATUS](
context: ActionContext<IReportsState, IRootState>
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
authApi
.get('reports/unresolved')
.then((res) => {
if (res.data.status === 'success') {
console.log(res.data)
context.commit(
REPORTS_STORE.MUTATIONS.SET_UNRESOLVED_REPORTS_STATUS,
res.data.unresolved
)
} else {
handleError(context, null)
}
})
.catch((error) => handleError(context, error))
},
}
9 changes: 6 additions & 3 deletions fittrackee_client/src/store/modules/reports/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export enum ReportsActions {
EMPTY_REPORTS = 'EMPTY_REPORTS',
GET_REPORT = 'GET_REPORT',
GET_REPORTS = 'GET_REPORTS',
GET_UNRESOLVED_REPORTS_STATUS = 'GET_UNRESOLVED_REPORTS_STATUS',
PROCESS_APPEAL = 'PROCESS_APPEAL',
SUBMIT_ADMIN_ACTION = 'SUBMIT_ADMIN_ACTION',
SUBMIT_REPORT = 'SUBMIT_REPORT',
Expand All @@ -10,19 +11,21 @@ export enum ReportsActions {

export enum ReportsGetters {
REPORT = 'REPORT',
REPORTS = 'REPORTS',
REPORTS_PAGINATION = 'REPORTS_PAGINATION',
REPORT_STATUS = 'REPORT_STATUS',
REPORT_LOADING = 'REPORT_LOADING',
REPORT_UPDATE_LOADING = 'REPORT_UPDATE_LOADING',
REPORTS = 'REPORTS',
REPORTS_PAGINATION = 'REPORTS_PAGINATION',
UNRESOLVED_REPORTS_STATUS = 'UNRESOLVED_REPORTS_STATUS',
}

export enum ReportsMutations {
EMPTY_REPORT = 'EMPTY_REPORT',
SET_REPORTS = 'SET_REPORTS',
SET_REPORT = 'SET_REPORT',
SET_REPORT_LOADING = 'SET_REPORT_LOADING',
SET_REPORT_STATUS = 'SET_REPORT_STATUS',
SET_REPORT_UPDATE_LOADING = 'SET_REPORT_UPDATE_LOADING',
SET_REPORTS = 'SET_REPORTS',
SET_REPORTS_PAGINATION = 'SET_REPORTS_PAGINATION',
SET_UNRESOLVED_REPORTS_STATUS = 'SET_UNRESOLVED_REPORTS_STATUS',
}
2 changes: 2 additions & 0 deletions fittrackee_client/src/store/modules/reports/getters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type { IRootState } from '@/store/modules/root/types'

export const getters: GetterTree<IReportsState, IRootState> & IReportsGetters =
{
[REPORTS_STORE.GETTERS.UNRESOLVED_REPORTS_STATUS]: (state: IReportsState) =>
state.unresolved,
[REPORTS_STORE.GETTERS.REPORT]: (state: IReportsState) => state.report,
[REPORTS_STORE.GETTERS.REPORT_LOADING]: (state: IReportsState) =>
state.reportLoading,
Expand Down
6 changes: 6 additions & 0 deletions fittrackee_client/src/store/modules/reports/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,10 @@ export const mutations: MutationTree<IReportsState> & TReportsMutations = {
) {
state.pagination = pagination
},
[REPORTS_STORE.MUTATIONS.SET_UNRESOLVED_REPORTS_STATUS](
state: IReportsState,
unresolved: boolean
) {
state.unresolved = unresolved
},
}
1 change: 1 addition & 0 deletions fittrackee_client/src/store/modules/reports/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { IPagination } from '@/types/api'
import type { IReportForAdmin } from '@/types/reports'

export const reportsState: IReportsState = {
unresolved: false,
report: <IReportForAdmin>{},
reports: [],
pagination: <IPagination>{},
Expand Down
15 changes: 13 additions & 2 deletions fittrackee_client/src/store/modules/reports/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
} from '@/types/reports'

export interface IReportsState {
unresolved: boolean
report: IReportForAdmin
reports: IReportForAdmin[]
pagination: IPagination
Expand All @@ -39,6 +40,9 @@ export interface IReportsActions {
context: ActionContext<IReportsState, IRootState>,
payload: TPaginationPayload
): void
[REPORTS_STORE.ACTIONS.GET_UNRESOLVED_REPORTS_STATUS](
context: ActionContext<IReportsState, IRootState>
): void
[REPORTS_STORE.ACTIONS.PROCESS_APPEAL](
context: ActionContext<IReportsState, IRootState>,
payload: IAppealPayload
Expand All @@ -58,6 +62,9 @@ export interface IReportsActions {
}

export interface IReportsGetters {
[REPORTS_STORE.GETTERS.UNRESOLVED_REPORTS_STATUS](
state: IReportsState
): boolean
[REPORTS_STORE.GETTERS.REPORT](state: IReportsState): IReportForAdmin
[REPORTS_STORE.GETTERS.REPORT_LOADING](state: IReportsState): boolean
[REPORTS_STORE.GETTERS.REPORT_STATUS](state: IReportsState): string | null
Expand All @@ -73,6 +80,10 @@ export type TReportsMutations<S = IReportsState> = {
state: S,
reportLoading: boolean
): void
[REPORTS_STORE.MUTATIONS.SET_REPORT_STATUS](
state: S,
reportStatus: string | null
): void
[REPORTS_STORE.MUTATIONS.SET_REPORT_UPDATE_LOADING](
state: S,
reportLoading: boolean
Expand All @@ -82,9 +93,9 @@ export type TReportsMutations<S = IReportsState> = {
state: S,
pagination: IPagination
): void
[REPORTS_STORE.MUTATIONS.SET_REPORT_STATUS](
[REPORTS_STORE.MUTATIONS.SET_UNRESOLVED_REPORTS_STATUS](
state: S,
reportStatus: string | null
unresolved: boolean
): void
}

Expand Down

0 comments on commit f5f62d3

Please sign in to comment.