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

fix: Fetch announcement with the edit button is clicked #724

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
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ import { useAnnouncementSelectionStore } from '../../store/modules/announcementS
import { ref } from 'vue';
import { NotificationService } from '../../services/notificationService';
import { useRouter } from 'vue-router';
import { Announcement } from '../../types/announcements';

const router = useRouter();

const announcementSelectionStore = useAnnouncementSelectionStore();
const { announcement } = defineProps<{
announcement: any;
const props = defineProps<{
announcement: Announcement;
}>();

const announcementSearchStore = useAnnouncementSearchStore();
Expand Down Expand Up @@ -116,8 +117,18 @@ async function unpublishAnnouncement(announcementId: string) {
}
}

const editAnnouncement = () => {
announcementSelectionStore.setAnnouncement(announcement);
router.push('/edit-announcement');
const editAnnouncement = async () => {
const { announcement_id } = props.announcement;
try {
const announcement = await ApiService.getAnnouncement(announcement_id);
announcementSelectionStore.setAnnouncement(announcement);
router.push(`/edit-announcement`);
} catch (e) {
console.error(e);
NotificationService.pushNotificationError(
'Error',
'An error occurred while trying to load the announcement.',
);
}
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
import ApiService from '../../../services/apiService';
import { NotificationService } from '../../../services/notificationService';
import { Announcement } from '../../../types/announcements';
import AnnouncementActions from '../AnnouncementActions.vue';

Expand Down Expand Up @@ -143,4 +144,36 @@ describe('AnnouncementActions', () => {
});
});
});

describe('edit announcement', () => {
it('sets the announcement to edit mode', async () => {
const apiSpy = vi
.spyOn(ApiService, 'getAnnouncement')
.mockImplementation(() => {
return Promise.resolve(mockDraftAnnouncement as any);
});
await wrapper.vm.editAnnouncement();

expect(apiSpy).toHaveBeenCalledWith(
mockDraftAnnouncement.announcement_id,
);
});

describe('when the announcement is not successfully retrieved', () => {
it('shows an error message', async () => {
vi.spyOn(ApiService, 'getAnnouncement').mockImplementation(() => {
return Promise.reject();
});

const errorSnackbarSpy = vi.spyOn(
NotificationService,
'pushNotificationError',
);

await wrapper.vm.editAnnouncement();

expect(errorSnackbarSpy).toHaveBeenCalled();
});
});
});
});
28 changes: 28 additions & 0 deletions admin-frontend/src/services/__tests__/apiService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,34 @@ describe('ApiService', () => {
});
});

describe('getAnnouncement', () => {
it('returns an announcement', async () => {
const mockBackendResponse = { title: 'test' };
const mockAxiosResponse = {
data: mockBackendResponse,
};
vi.spyOn(ApiService.apiAxios, 'get').mockResolvedValueOnce(
mockAxiosResponse,
);

const resp = await ApiService.getAnnouncement('1');
expect(resp).toEqual(mockBackendResponse);
});

describe('when the data are not successfully retrieved from the backend', () => {
it('returns a rejected promise', async () => {
const mockAxiosError = new AxiosError();
vi.spyOn(ApiService.apiAxios, 'get').mockRejectedValueOnce(
mockAxiosError,
);

await expect(ApiService.getAnnouncement('1')).rejects.toEqual(
mockAxiosError,
);
});
});
});

describe('clamavScanFile', () => {
describe('when the given file is valid', () => {
it('returns a response', async () => {
Expand Down
20 changes: 20 additions & 0 deletions admin-frontend/src/services/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
UserInvite,
} from '../types';
import {
Announcement,
AnnouncementFilterType,
AnnouncementFormValue,
AnnouncementSortType,
Expand Down Expand Up @@ -337,6 +338,25 @@ export default {
}
},

async getAnnouncement(id: string) {
try {
const { data } = await apiAxios.get<
Announcement & {
announcement_resource: {
resource_type: string;
display_name: string;
resource_url: string;
attachment_file_id: string;
}[];
}
>(`${ApiRoutes.ANNOUNCEMENTS}/${id}`);
return data;
} catch (e) {
console.log(`Failed to get announcement from API - ${e}`);
throw e;
}
},

/**
* Download a list of reports in csv format. This method also causes
* the browser to save the resulting file.
Expand Down
20 changes: 20 additions & 0 deletions backend/src/v1/routes/announcement-routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ const mockGetAnnouncements = jest.fn().mockResolvedValue({
const mockPatchAnnouncements = jest.fn();
const mockCreateAnnouncement = jest.fn();
const mockUpdateAnnouncement = jest.fn();
const mockGetAnnouncementById = jest.fn();
jest.mock('../services/announcements-service', () => ({
getAnnouncements: (...args) => {
return mockGetAnnouncements(...args);
},
patchAnnouncements: (...args) => mockPatchAnnouncements(...args),
createAnnouncement: (...args) => mockCreateAnnouncement(...args),
updateAnnouncement: (...args) => mockUpdateAnnouncement(...args),
getAnnouncementById: (...args) => mockGetAnnouncementById(...args),
}));

jest.mock('../middlewares/authorization/authenticate-admin', () => ({
Expand Down Expand Up @@ -431,4 +433,22 @@ describe('announcement-routes', () => {
});
});
});

describe('GET /:id - get announcement by id', () => {
it('should return 200', async () => {
const response = await request(app).get('/123');
expect(response.status).toBe(200);
expect(mockGetAnnouncementById).toHaveBeenCalledWith("123");
});

describe('when service throws error', () => {
it('should return 400', async () => {
mockGetAnnouncementById.mockRejectedValue(new Error('Invalid request'));
const response = await request(app).get('/123');

expect(response.status).toBe(400);
expect(response.body.error).toBeDefined();
});
});
})
});
13 changes: 12 additions & 1 deletion backend/src/v1/routes/announcement-routes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Router } from 'express';
import { Request, Router } from 'express';
import formData from 'express-form-data';
import os from 'os';
import { APP_ANNOUNCEMENTS_FOLDER } from '../../constants/admin';
Expand All @@ -9,6 +9,7 @@ import { useUpload } from '../middlewares/storage/upload';
import { useValidate } from '../middlewares/validations';
import {
createAnnouncement,
getAnnouncementById,
getAnnouncements,
patchAnnouncements,
updateAnnouncement,
Expand Down Expand Up @@ -137,4 +138,14 @@ router.put(
},
);

router.get('/:id', authenticateAdmin(), async (req: Request, res) => {
try {
const announcement = await getAnnouncementById(req.params.id);
return res.json(announcement);
} catch (error) {
logger.error(error);
res.status(400).json({ message: 'Invalid request', error });
}
});

export default router;
17 changes: 14 additions & 3 deletions backend/src/v1/services/announcements-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jest.mock('../prisma/prisma-client', () => ({
count: jest.fn().mockResolvedValue(2),
updateMany: (...args) => mockUpdateMany(...args),
create: (...args) => mockCreateAnnouncement(...args),
findUniqueOrThrow: (...args) => mockFindUniqueOrThrow(...args),
},
announcement_history: {
create: (...args) => mockHistoryCreate(...args),
Expand Down Expand Up @@ -832,7 +833,7 @@ describe('AnnouncementsService', () => {
});
});

it('should default to undefined dates', async () => {
it('should default to null dates', async () => {
mockFindUniqueOrThrow.mockResolvedValue({
id: 'announcement-id',
announcement_resource: [],
Expand All @@ -855,8 +856,8 @@ describe('AnnouncementsService', () => {
expect.objectContaining({
where: { announcement_id: 'announcement-id' },
data: expect.objectContaining({
expires_on: undefined,
published_on: undefined,
expires_on: null,
published_on: null,
}),
}),
);
Expand Down Expand Up @@ -912,4 +913,14 @@ describe('AnnouncementsService', () => {
});
});
});

describe('getAnnouncementById', () => {
it('should return announcement by id', async () => {
await AnnouncementService.getAnnouncementById('1');
expect(mockFindUniqueOrThrow).toHaveBeenCalledWith({
where: { announcement_id: '1' },
include: { announcement_resource: true },
});
});
});
});
20 changes: 18 additions & 2 deletions backend/src/v1/services/announcements-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,22 @@ export const getAnnouncements = async (
};
};

/**
* Get announcement by id
* @param id
* @returns
*/
export const getAnnouncementById = async (id: string): Promise<announcement> => {
return prisma.announcement.findUniqueOrThrow({
where: {
announcement_id: id,
},
include: {
announcement_resource: true,
},
});
}

/**
* Patch announcements by ids.
* This method also copies the original record into the announcement history table.
Expand Down Expand Up @@ -411,8 +427,8 @@ export const updateAnnouncement = async (
},
published_on: !isEmpty(input.published_on)
? input.published_on
: undefined,
expires_on: !isEmpty(input.expires_on) ? input.expires_on : undefined,
: null,
expires_on: !isEmpty(input.expires_on) ? input.expires_on : null,
admin_user_announcement_updated_byToadmin_user: {
connect: { admin_user_id: currentUserId },
},
Expand Down