diff --git a/shared/types/core.ts b/shared/types/core.ts new file mode 100644 index 0000000000..3b7f68d505 --- /dev/null +++ b/shared/types/core.ts @@ -0,0 +1,12 @@ +export interface ErrorDto { + message: string +} + +export interface SuccessMessageDto { + message: string +} + +export interface PrivateFormErrorDto extends ErrorDto { + isPageFound: true + formTitle: string +} diff --git a/shared/types/form/form_feedback.ts b/shared/types/form/form_feedback.ts new file mode 100644 index 0000000000..840e4ca201 --- /dev/null +++ b/shared/types/form/form_feedback.ts @@ -0,0 +1,43 @@ +import { Merge } from 'type-fest' +import { DateString } from '../generic' +import { FormDto } from './form' + +export type SubmitFormFeedbackBodyDto = { + isPreview?: boolean + rating: number + comment?: string +} + +/** + * Typing for individual form feedback + */ +export type FormFeedbackBase = { + rating: number + comment?: string + formId: FormDto['_id'] + created?: Date + lastModified?: Date +} + +// Convert to serialized version. +export type FormFeedbackDto = Merge< + FormFeedbackBase, + { created?: DateString; lastModified?: DateString } +> + +export type ProcessedFeedbackMeta = { + index: number + timestamp: number + rating: number + comment: string + // Note that this date is not a DateString, it is actually "D MMM YYYY" + // format. + date: string + dateShort: string +} + +export type FormFeedbackMetaDto = { + average?: string + count: number + feedback: ProcessedFeedbackMeta[] +} diff --git a/src/app/modules/feedback/__tests__/feedback.service.spec.ts b/src/app/modules/feedback/__tests__/feedback.service.spec.ts index ef0aa91fa7..0fe5c831a9 100644 --- a/src/app/modules/feedback/__tests__/feedback.service.spec.ts +++ b/src/app/modules/feedback/__tests__/feedback.service.spec.ts @@ -8,7 +8,7 @@ import getFormFeedbackModel from 'src/app/models/form_feedback.server.model' import dbHandler from 'tests/unit/backend/helpers/jest-db' -import { GetFormFeedbackDto } from '../../../../types/api/form_feedback' +import { FormFeedbackMetaDto } from '../../../../types/api/form_feedback' import { DatabaseError } from '../../core/core.errors' import * as FeedbackService from '../feedback.service' @@ -201,7 +201,7 @@ describe('feedback.service', () => { const actualResult = await FeedbackService.getFormFeedbacks(mockFormId) // Assert - const expectedResult: GetFormFeedbackDto = { + const expectedResult: FormFeedbackMetaDto = { count: 1, average: '3.00', feedback: [ diff --git a/src/app/modules/feedback/feedback.service.ts b/src/app/modules/feedback/feedback.service.ts index 25ec230c45..d39df1e4f4 100644 --- a/src/app/modules/feedback/feedback.service.ts +++ b/src/app/modules/feedback/feedback.service.ts @@ -4,8 +4,8 @@ import mongoose from 'mongoose' import { ResultAsync } from 'neverthrow' import { IFormFeedbackSchema } from '../../../types' -import { GetFormFeedbackDto } from '../../../types/api/form_feedback' -import { ProcessedFeedback } from '../../../types/form_feedback' +import { FormFeedbackMetaDto } from '../../../types/api/form_feedback' +import { ProcessedFeedbackMeta } from '../../../types/form_feedback' import { createLoggerWithLabel } from '../../config/logger' import getFormFeedbackModel from '../../models/form_feedback.server.model' import { getMongoErrorMessage } from '../../utils/handle-mongo-error' @@ -61,7 +61,7 @@ export const getFormFeedbackStream = ( */ export const getFormFeedbacks = ( formId: string, -): ResultAsync => { +): ResultAsync => { return ResultAsync.fromPromise( FormFeedbackModel.find({ formId }).sort({ created: 1 }).exec(), (error) => { @@ -89,7 +89,7 @@ export const getFormFeedbacks = ( let totalRating = 0 const processedFeedback = feedbacks.map((fb, idx) => { totalRating += fb.rating - const response: ProcessedFeedback = { + const response: ProcessedFeedbackMeta = { // 1-based indexing. index: idx + 1, timestamp: moment(fb.created).valueOf(), diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts index 838bf3ba60..b34f5f53a2 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts @@ -69,7 +69,7 @@ import { FieldCreateDto, FieldUpdateDto, } from 'src/types/api' -import { GetFormFeedbackDto } from 'src/types/api/form_feedback' +import { FormFeedbackMetaDto } from 'src/types/api/form_feedback' import { generateDefaultField, @@ -2421,7 +2421,7 @@ describe('admin-form.controller', () => { it('should return 200 with feedback response successfully', async () => { // Arrange const mockRes = expressHandler.mockResponse() - const expectedFormFeedback: GetFormFeedbackDto = { + const expectedFormFeedback: FormFeedbackMetaDto = { count: 212, feedback: [ { diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts index b02bd5330f..75b76ea13d 100644 --- a/src/app/modules/form/admin-form/admin-form.controller.ts +++ b/src/app/modules/form/admin-form/admin-form.controller.ts @@ -36,6 +36,7 @@ import { FieldCreateDto, FieldUpdateDto, FormDto, + FormFeedbackMetaDto, FormFieldDto, FormUpdateParams, PermissionsUpdateDto, @@ -590,9 +591,10 @@ export const handleCountFormSubmissions = [ * @returns 422 when user in session cannot be retrieved from the database * @returns 500 when database error occurs */ -export const handleCountFormFeedback: ControllerHandler<{ - formId: string -}> = async (req, res) => { +export const handleCountFormFeedback: ControllerHandler< + { formId: string }, + number | ErrorDto +> = async (req, res) => { const { formId } = req.params const sessionUserId = (req.session as Express.AuthedSession).user._id @@ -733,9 +735,10 @@ export const handleStreamFormFeedback: ControllerHandler<{ * @returns 422 when user in session cannot be retrieved from the database * @returns 500 when database error occurs */ -export const handleGetFormFeedback: ControllerHandler<{ - formId: string -}> = (req, res) => { +export const handleGetFormFeedback: ControllerHandler< + { formId: string }, + FormFeedbackMetaDto | ErrorDto +> = (req, res) => { const { formId } = req.params const sessionUserId = (req.session as Express.AuthedSession).user._id diff --git a/src/public/modules/forms/helpers/FeedbackCsvGenerator.ts b/src/public/modules/forms/helpers/FeedbackCsvGenerator.ts index 9a12c26ffc..b15e9f5f7a 100644 --- a/src/public/modules/forms/helpers/FeedbackCsvGenerator.ts +++ b/src/public/modules/forms/helpers/FeedbackCsvGenerator.ts @@ -1,6 +1,6 @@ import moment from 'moment-timezone' -import { FormFeedbackResponseDto } from '../../../../types/api/form_feedback' +import { FormFeedbackDto } from '../../../../../shared/types/form/form_feedback' import { CsvGenerator } from './CsvGenerator' @@ -16,7 +16,7 @@ export class FeedbackCsvGenerator extends CsvGenerator { /** * Generate a string representing a form feedback CSV line record */ - addLineFromFeedback(feedback: FormFeedbackResponseDto) { + addLineFromFeedback(feedback: FormFeedbackDto) { const createdAt = moment(feedback.created) .tz('Asia/Singapore') .format('DD MMM YYYY hh:mm:ss A') diff --git a/src/public/modules/forms/helpers/__tests__/FeedbackCsvGenerator.spec.ts b/src/public/modules/forms/helpers/__tests__/FeedbackCsvGenerator.spec.ts index 599d2a2dd2..76bdc31ab8 100644 --- a/src/public/modules/forms/helpers/__tests__/FeedbackCsvGenerator.spec.ts +++ b/src/public/modules/forms/helpers/__tests__/FeedbackCsvGenerator.spec.ts @@ -1,7 +1,8 @@ import { ObjectId } from 'bson' import moment from 'moment-timezone' -import { FormFeedbackResponseDto } from '../../../../../types/api/form_feedback' +import { FormFeedbackDto } from '../../../../../../shared/types/form/form_feedback' +import { DateString } from '../../../../../../shared/types/generic' import { FeedbackCsvGenerator } from '../FeedbackCsvGenerator' describe('FeedbackCsvGenerator', () => { @@ -21,12 +22,14 @@ describe('FeedbackCsvGenerator', () => { it('should add line successfully if created and lastmodified dates are provided', () => { // Arrange const feedbackCsv = new FeedbackCsvGenerator(1) - const feedback: FormFeedbackResponseDto = { + const feedback: FormFeedbackDto = { rating: 3, comment: 'some comment', formId: new ObjectId().toHexString(), - created: new Date('2019-11-05T13:12:14'), - lastModified: new Date('2019-11-05T13:12:14'), + created: new Date('2019-11-05T13:12:14').toISOString() as DateString, + lastModified: new Date( + '2019-11-05T13:12:14', + ).toISOString() as DateString, } const insertedCreatedDate = moment(feedback.created) .tz('Asia/Singapore') @@ -47,7 +50,7 @@ describe('FeedbackCsvGenerator', () => { it('should add line successfully if comment, created and lastmodified dates are not provided', () => { // Arrange const feedbackCsv = new FeedbackCsvGenerator(1) - const feedback: FormFeedbackResponseDto = { + const feedback: FormFeedbackDto = { rating: 3, formId: new ObjectId().toHexString(), } diff --git a/src/public/services/FormFeedbackService.ts b/src/public/services/FormFeedbackService.ts index f7372fdaff..6690bfae21 100644 --- a/src/public/services/FormFeedbackService.ts +++ b/src/public/services/FormFeedbackService.ts @@ -1,10 +1,11 @@ import axios from 'axios' +import { SuccessMessageDto } from '../../../shared/types/core' import { - FormFeedbackPostDto, - FormFeedbackResponseDto, - GetFormFeedbackDto, -} from '../../types/api/form_feedback' + FormFeedbackDto, + FormFeedbackMetaDto, + SubmitFormFeedbackBodyDto, +} from '../../../shared/types/form/form_feedback' import { FeedbackCsvGenerator } from '../modules/forms/helpers/FeedbackCsvGenerator' // Exported for testing @@ -15,14 +16,14 @@ export const ADMIN_FORM_ENDPOINT = '/api/v3/admin/forms' * Post feedback for a given form. * @param formId the id of the form to post feedback for * @param feedbackToPost object containing the feedback - * @returns the posted feedback + * @returns success message */ export const postFeedback = async ( formId: string, - feedbackToPost: FormFeedbackPostDto, -): Promise => { + feedbackToPost: SubmitFormFeedbackBodyDto, +): Promise => { return axios - .post( + .post( `${PUBLIC_FORM_ENDPOINT}/${formId}/feedback`, feedbackToPost, ) @@ -36,9 +37,9 @@ export const postFeedback = async ( */ export const getFeedback = async ( formId: string, -): Promise => { +): Promise => { return axios - .get(`${ADMIN_FORM_ENDPOINT}/${formId}/feedback`) + .get(`${ADMIN_FORM_ENDPOINT}/${formId}/feedback`) .then(({ data }) => data) } @@ -65,7 +66,7 @@ export const downloadFeedback = async ( const expectedNumResponses = await countFeedback(formId) return axios - .get( + .get( `${ADMIN_FORM_ENDPOINT}/${formId}/feedback/download`, ) .then(({ data }) => { diff --git a/src/types/api/core.ts b/src/types/api/core.ts index 3aec7b4572..7be86ef920 100644 --- a/src/types/api/core.ts +++ b/src/types/api/core.ts @@ -1,8 +1 @@ -export interface ErrorDto { - message: string -} - -export interface PrivateFormErrorDto extends ErrorDto { - isPageFound: true - formTitle: string -} +export * from '../../../shared/types/core' diff --git a/src/types/api/form_feedback.ts b/src/types/api/form_feedback.ts index 47d6aa6313..956fbcbba1 100644 --- a/src/types/api/form_feedback.ts +++ b/src/types/api/form_feedback.ts @@ -1,21 +1 @@ -import { ProcessedFeedback } from '../form_feedback' - -export type FormFeedbackPostDto = { - isPreview?: boolean - rating: number - comment?: string -} - -export type FormFeedbackResponseDto = { - rating: number - comment?: string - formId: string - created?: Date - lastModified?: Date -} - -export type GetFormFeedbackDto = { - average?: string - count: number - feedback: ProcessedFeedback[] -} +export { FormFeedbackMetaDto } from '../../../shared/types/form/form_feedback' diff --git a/src/types/api/index.ts b/src/types/api/index.ts index 483fa1912e..ed9b2da691 100644 --- a/src/types/api/index.ts +++ b/src/types/api/index.ts @@ -7,3 +7,4 @@ export * from './examples' export * from './encrypt_submission' export * from './email_submission' export * from './submission' +export * from './form_feedback' diff --git a/src/types/form_feedback.ts b/src/types/form_feedback.ts index 10c9307181..dcd9258671 100644 --- a/src/types/form_feedback.ts +++ b/src/types/form_feedback.ts @@ -1,21 +1,16 @@ import { Document, Model, QueryCursor } from 'mongoose' +import { Merge } from 'type-fest' + +import { FormFeedbackBase } from '../../shared/types/form/form_feedback' import { IFormSchema } from './form' -export type ProcessedFeedback = { - index: number - timestamp: number - rating: number - comment: string - date: string - dateShort: string -} +export { ProcessedFeedbackMeta } from '../../shared/types/form/form_feedback' -export interface IFormFeedback { - formId: IFormSchema['_id'] - rating: number - comment?: string -} +export type IFormFeedback = Merge< + FormFeedbackBase, + { formId: IFormSchema['_id'] } +> export interface IFormFeedbackSchema extends IFormFeedback, Document { created?: Date lastModified?: Date @@ -26,13 +21,6 @@ export interface IFormFeedbackDocument extends IFormFeedbackSchema { lastModified: Date } -export interface IFormFeedbackSchema extends Document, IFormFeedback {} - -export interface IFormFeedbackDoc extends IFormFeedbackSchema { - lastModified: Date - created: Date -} - export interface IFormFeedbackModel extends Model { /** * Returns a cursor for all feedback for the form with formId.