Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: GEO-1048 - admin dashboard widget for number of submissions in current year #772

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions admin-frontend/src/components/DashboardPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
</h4>
<v-row :dense="true">
<v-col>
<NumSubmissionsThisYear></NumSubmissionsThisYear>
<NumSubmissionsInYear
ref="numSubmissionsInYear"
></NumSubmissionsInYear>
</v-col>
<v-col>
<NumEmployerLogins></NumEmployerLogins>
Expand Down Expand Up @@ -67,7 +69,7 @@
<script lang="ts" setup>
import RecentlySubmittedReports from './dashboard/RecentlySubmittedReports.vue';
import RecentlyViewedReports from './dashboard/RecentlyViewedReports.vue';
import NumSubmissionsThisYear from './dashboard/NumSubmissionsThisYear.vue';
import NumSubmissionsInYear from './dashboard/NumSubmissionsInYear.vue';
import NumEmployerLogins from './dashboard/NumEmployerLogins.vue';
import PublicAnnouncements from './dashboard/PublicAnnouncements.vue';
import ToolTip from './ToolTip.vue';
Expand All @@ -79,6 +81,7 @@ const isDashboardAvailable =

const recentlySubmittedReports = ref<typeof RecentlySubmittedReports>();
const recentlyViewedReports = ref<typeof RecentlyViewedReports>();
const numSubmissionsInYear = ref<typeof NumSubmissionsInYear>();

onMounted(() => {
//Periodically refresh the widgets
Expand All @@ -90,5 +93,6 @@ onMounted(() => {
async function refresh() {
await recentlySubmittedReports.value?.refresh();
await recentlyViewedReports.value?.refresh();
await numSubmissionsInYear.value?.refresh();
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>
<v-card class="ptap-widget">
<v-card-text class="h-100 d-flex flex-column">
<div class="widget-header flex-grow-0 flex-shrink-0">
Number of reports submitted for the current reporting year ({{
currentReportingYear
}})
</div>
<div
class="d-flex flex-column justify-center align-center text-primary flex-grow-1 flex-shrink-0"
>
<v-skeleton-loader v-if="isLoading" type="avatar"></v-skeleton-loader>
<div v-if="!isLoading">
<span v-if="hasError">
<v-tooltip text="Unable to load the data">
<template #activator="{ props }">
<v-icon
icon="mdi-alert"
size="x-large"
color="grey"
v-bind="props"
@click="refresh"
></v-icon>
</template>
</v-tooltip>
</span>
<span v-if="!hasError" class="widget-value">{{
numReportsInCurrentReportingYear
}}</span>
</div>
</div>
</v-card-text>
</v-card>
</template>

<script setup lang="ts">
import ApiService from '../../services/apiService';
import { ref, onMounted } from 'vue';
import { ReportMetrics } from '../../types/reports';

onMounted(() => {
refresh();
});

const currentReportingYear = ref<number | null>();
const numReportsInCurrentReportingYear = ref<number | null>();
const hasError = ref<boolean>(false);
const isLoading = ref<boolean>(false);

async function refresh() {
hasError.value = false;
isLoading.value = true;
try {
const reportMetrics: ReportMetrics = await ApiService.getReportMetrics();
currentReportingYear.value =
reportMetrics?.report_metrics[0].reporting_year;
numReportsInCurrentReportingYear.value =
reportMetrics?.report_metrics[0].num_published_reports;
} catch (e) {
hasError.value = true;
} finally {
isLoading.value = false;
}
}

defineExpose({
refresh,
});
</script>

<style lang="scss">
.widget-header {
font-size: 1.2em;
}
.widget-value {
font-size: 6em;
font-weight: bold;
}
</style>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createTestingPinia } from '@pinia/testing';
import { render, screen, waitFor } from '@testing-library/vue';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
import { ReportMetrics } from '../../../types/reports';
import NumSubmissionsInYear from '../NumSubmissionsInYear.vue';

global.ResizeObserver = require('resize-observer-polyfill');
const pinia = createTestingPinia();
const vuetify = createVuetify({ components, directives });

const mockReportMetrics: ReportMetrics = {
report_metrics: [{ reporting_year: 2024, num_published_reports: 10 }],
};
const mockGetReportMetrics = vi.fn().mockResolvedValue(mockReportMetrics);

vi.mock('../../../services/apiService', () => ({
default: {
getReportMetrics: (...args) => {
return mockGetReportMetrics(...args);
},
},
}));

const wrappedRender = () => {
return render(NumSubmissionsInYear, {
global: {
plugins: [pinia, vuetify],
},
});
};

describe('NumSubmissionsInYear', () => {
afterEach(() => {
vi.clearAllMocks();
});

it('displays the number of reports submitted in the current reporting year', async () => {
await wrappedRender();
expect(mockGetReportMetrics).toHaveBeenCalled();

await waitFor(() => {
expect(
screen.getByText(
`${mockReportMetrics.report_metrics[0].reporting_year}`,
{ exact: false /* match substring */ },
),
).toBeInTheDocument();
expect(
screen.getByText(
`${mockReportMetrics.report_metrics[0].num_published_reports}`,
{
exact: false /* match substring */,
},
),
).toBeInTheDocument();
});
});
});
36 changes: 27 additions & 9 deletions admin-frontend/src/services/__tests__/apiService.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AxiosError } from 'axios';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AnnouncementStatus } from '../../types/announcements';
import { ReportMetrics } from '../../types/reports';
import ApiService from '../apiService';

//Mock the interceptor used by the ApiService so it no longer depends on
Expand Down Expand Up @@ -589,15 +590,15 @@ describe('ApiService', () => {
data: new File([], 'test.pdf'),
headers: {
'content-disposition': 'attachment; filename="test.pdf"',
}
},
};
global.URL.createObjectURL = vi.fn().mockReturnValueOnce("test.pdf");
global.URL.createObjectURL = vi.fn().mockReturnValueOnce('test.pdf');
vi.spyOn(ApiService.apiAxios, 'get').mockResolvedValueOnce(
mockResponse,
);

const resp = await ApiService.downloadFile(mockFileId);

expect(resp.mode).toEqual('open');
});
it('download file', async () => {
Expand All @@ -606,15 +607,15 @@ describe('ApiService', () => {
data: new File([], 'test.pdf'),
headers: {
'content-disposition': 'attachment; filename="test.jpg"',
}
},
};
global.URL.createObjectURL = vi.fn().mockReturnValueOnce("test.jpg");
global.URL.createObjectURL = vi.fn().mockReturnValueOnce('test.jpg');
vi.spyOn(ApiService.apiAxios, 'get').mockResolvedValueOnce(
mockResponse,
);

const resp = await ApiService.downloadFile(mockFileId);

expect(resp.mode).toEqual('download');
});
});
Expand All @@ -625,9 +626,26 @@ describe('ApiService', () => {
new Error('Some backend error occurred'),
);

await expect(
ApiService.downloadFile(mockFileId),
).rejects.toThrow();
await expect(ApiService.downloadFile(mockFileId)).rejects.toThrow();
});
});
});

describe('getReportMetrics', () => {
describe('when the data are successfully retrieved from the backend', () => {
it('returns a list of metrics by reporting year', async () => {
const mockBackendResponse = {
report_metrics: [{ reporting_year: 2024, num_published_reports: 4 }],
};
const mockAxiosResponse = {
data: mockBackendResponse,
};
vi.spyOn(ApiService.apiAxios, 'get').mockResolvedValueOnce(
mockAxiosResponse,
);

const resp: ReportMetrics = await ApiService.getReportMetrics();
expect(resp).toEqual(mockBackendResponse);
});
});
});
Expand Down
20 changes: 17 additions & 3 deletions admin-frontend/src/services/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
AnnouncementSortType,
IAnnouncementSearchResult,
} from '../types/announcements';
import { IReportSearchResult } from '../types/reports';
import { IReportSearchResult, ReportMetrics } from '../types/reports';
import { ApiRoutes, POWERBI_RESOURCE } from '../utils/constant';
import AuthService from './authService';

Expand Down Expand Up @@ -191,10 +191,10 @@ export default {
responseType: 'blob',
},
);
let name = headers['content-disposition']
const name = headers['content-disposition']
.split('filename="')[1]
.split('.')[0];
let extension = headers['content-disposition']
const extension = headers['content-disposition']
.split('.')[1]
.split('"')[0];

Expand Down Expand Up @@ -262,6 +262,20 @@ export default {
throw e;
}
},

async getReportMetrics(): Promise<ReportMetrics> {
try {
const resp = await apiAxios.get(ApiRoutes.REPORT_METRICS);
if (resp?.data) {
return resp.data;
}
throw new Error('Unable to fetch report metrics');
} catch (e) {
console.log(`Failed to get report metrics from API - ${e}`);
throw e;
}
},

async getAnnouncements(
offset: number = 0,
limit: number = 20,
Expand Down
9 changes: 9 additions & 0 deletions admin-frontend/src/types/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,12 @@ export type ReportFilterType = (
| CompanyFilter
| AdminLastAccessDateFilter
)[];

export type ReportMetrics = {
report_metrics: [
{
reporting_year: number;
num_published_reports: number;
},
];
};
1 change: 1 addition & 0 deletions admin-frontend/src/utils/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const ApiRoutes = Object.freeze({
ANNOUNCEMENTS: `${baseRoot}/v1/announcements`,
CLAMAV_SCAN: `${clamavBaseRoot}/`,
RESOURCES: `${baseRoot}/v1/resources`,
REPORT_METRICS: `${baseRoot}/v1/dashboard/reports-metrics`,
});

export const PAGE_TITLES = Object.freeze({
Expand Down
9 changes: 6 additions & 3 deletions backend/src/v1/services/admin-report-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -664,9 +664,12 @@ describe('admin-report-service', () => {

// Assert
expect(result).toEqual({
reports: {
count: 2,
},
report_metrics: [
{
reporting_year: reportingYear,
num_published_reports: 1,
},
],
});
});
});
Expand Down
22 changes: 14 additions & 8 deletions backend/src/v1/services/admin-report-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,27 @@ const adminReportService = {
await adminReportServicePrivate.updateAdminLastAccessDate(reportId);
return report;
},

/**
* Get dashboard metrics
* @param param0
* @returns
*/
async getReportsMetrics({ reportingYear }: IGetReportMetricsInput) {
const reportsCount = await prismaReadOnlyReplica.pay_transparency_report.count({
where: {
reporting_year: reportingYear,
},
});
const reportsCount =
await prismaReadOnlyReplica.pay_transparency_report.count({
where: {
reporting_year: reportingYear,
report_status: 'Published',
},
});
return {
reports: {
count: reportsCount,
},
report_metrics: [
{
reporting_year: reportingYear,
num_published_reports: reportsCount,
},
],
};
},

Expand Down