diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index 1aed8882f..589cf4318 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -15,6 +15,11 @@ import { User, UserRole } from '../entities/user'; import { countUniqueDonorsAndSumDonationValueUsd, createDonation, + donationsNumberPerDateRange, + donationsTotalAmountPerDateRange, + donationsTotalAmountPerDateRangeByMonth, + donationsTotalNumberPerDateRangeByMonth, + donorsCountPerDateByMonthAndYear, fillQfRoundDonationsUserScores, findDonationById, findDonationsByProjectIdWhichUseDonationBox, @@ -29,6 +34,7 @@ import { QfRound } from '../entities/qfRound'; import { Project } from '../entities/project'; import { refreshProjectEstimatedMatchingView } from '../services/projectViewsService'; import { calculateEstimateMatchingForProjectById } from '../utils/qfUtils'; +import { ORGANIZATION_LABELS } from '../entities/organization'; import { setPowerRound } from './powerRoundRepository'; describe('createDonation test cases', createDonationTestCases); @@ -62,12 +68,481 @@ describe( 'isVerifiedDonationExistsInQfRound() test cases', isVerifiedDonationExistsInQfRoundTestCases, ); +describe( + 'donationsTotalAmountPerDateRange() test cases', + donationsTotalAmountPerDateRangeTestCases, +); describe('findDonationsToGiveth() test cases', findDonationsToGivethTestCases); +describe( + 'donationsTotalAmountPerDateRangeByMonth() test cases', + donationsTotalAmountPerDateRangeByMonthTestCases, +); +describe( + 'donationsTotalNumberPerDateRangeByMonth() test cases', + donationsTotalNumberPerDateRangeByMonthTestCase, +); +describe( + 'donationsNumberPerDateRange() test cases', + donationsNumberPerDateRangeTestCases, +); +describe( + 'donorsCountPerDateByMonthAndYear() test cases', + donorsCountPerDateByMonthAndYearTestCase, +); +describe('donorsCountPerDate() test cases', donorsCountPerDateTestCases); + describe( 'getSumOfGivbackEligibleDonationsForSpecificRound() test cases', getSumOfGivbackEligibleDonationsForSpecificRoundTestCases, ); +function donorsCountPerDateByMonthAndYearTestCase() { + it('should return per month number of donations for endaoment projects', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const donationStart = moment().add(30, 'months'); + const donationStart1month = moment().add(31, 'month'); + const donationStart2month = moment().add(32, 'month'); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 30, + }), + SEED_DATA.FIRST_USER.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 20, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 30, + }), + SEED_DATA.THIRD_USER.id, + SEED_DATA.SECOND_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 20, + }), + SEED_DATA.THIRD_USER.id, + endaomentProject.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 30, + }), + SEED_DATA.FIRST_USER.id, + endaomentProject.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 40, + }), + user.id, + endaomentProject.id, + ); + const expectedReturnForAllProjects: number[] = [2, 2, 2]; + const expectedReturnEndaomentProjects: number[] = [1, 1, 1]; + + const fromDate = donationStart.toISOString(true); + const toDate = donationStart2month.toISOString(true); + const actualReturnEndaomentProjects = + await donorsCountPerDateByMonthAndYear(fromDate, toDate, undefined, true); + const actualReturnAllProjects = await donorsCountPerDateByMonthAndYear( + fromDate, + toDate, + ); + const endaomentProjectsReturns: number[] = actualReturnEndaomentProjects + .filter(donationPerDate => donationPerDate.total !== undefined) + .map(donationPerDate => Number(donationPerDate.total!)); + const allProjectsReturns: number[] = actualReturnAllProjects + .filter(donationPerDate => donationPerDate.total !== undefined) + .map(donationPerDate => Number(donationPerDate.total!)); + + assert.deepEqual(expectedReturnForAllProjects, allProjectsReturns); + assert.deepEqual(expectedReturnEndaomentProjects, endaomentProjectsReturns); + }); +} + +function donorsCountPerDateTestCases() { + it('should return total donations amount for endaoment projects', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(221, 'days').toDate(), + valueUsd: 30, + }), + SEED_DATA.FIRST_USER.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(221, 'days').toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + const fromDate = moment().add(220, 'days').format('YYYY/MM/DD'); + const toDate = moment().add(222, 'days').toDate().toDateString(); + const totalDonationInTimeFrame = await donationsNumberPerDateRange( + fromDate, + toDate, + ); + const endaomentDonationInTimeFrame = await donationsNumberPerDateRange( + fromDate, + toDate, + undefined, + undefined, + true, + ); + assert.equal(totalDonationInTimeFrame, 2); + assert.equal(endaomentDonationInTimeFrame, 1); + }); +} + +function donationsTotalNumberPerDateRangeByMonthTestCase() { + it('should return per month number of donations for endaoment projects', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const donationStart = moment().add(20, 'months'); + const donationStart1month = moment().add(21, 'month'); + const donationStart2month = moment().add(22, 'month'); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 30, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 20, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 30, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 30, + }), + user.id, + endaomentProject.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 40, + }), + user.id, + endaomentProject.id, + ); + const expectedReturnForAllProjects: number[] = [2, 2, 2]; + const expectedReturnEndaomentProjects: number[] = [1, 1, 1]; + + const fromDate = donationStart.toISOString(true); + const toDate = donationStart2month.toISOString(true); + const actualReturnEndaomentProjects = + await donationsTotalNumberPerDateRangeByMonth( + fromDate, + toDate, + undefined, + undefined, + true, + ); + const actualReturnAllProjects = + await donationsTotalNumberPerDateRangeByMonth(fromDate, toDate); + const endaomentProjectsReturns: number[] = actualReturnEndaomentProjects + .filter(donationPerDate => donationPerDate.total !== undefined) + .map(donationPerDate => Number(donationPerDate.total!)); + const allProjectsReturns: number[] = actualReturnAllProjects + .filter(donationPerDate => donationPerDate.total !== undefined) + .map(donationPerDate => Number(donationPerDate.total!)); + + assert.deepEqual(expectedReturnForAllProjects, allProjectsReturns); + assert.deepEqual(expectedReturnEndaomentProjects, endaomentProjectsReturns); + }); +} + +function donationsNumberPerDateRangeTestCases() { + it('should return total donations amount for endaoment projects', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(445, 'days').toDate(), + valueUsd: 30, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(445, 'days').toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + const fromDate = moment().add(444, 'days').format('YYYY/MM/DD'); + const toDate = moment().add(446, 'days').toDate().toDateString(); + const totalDonationInTimeFrame = await donationsNumberPerDateRange( + fromDate, + toDate, + ); + const endaomentDonationInTimeFrame = await donationsNumberPerDateRange( + fromDate, + toDate, + undefined, + undefined, + true, + ); + assert.equal(totalDonationInTimeFrame, 2); + assert.equal(endaomentDonationInTimeFrame, 1); + }); +} + +function donationsTotalAmountPerDateRangeByMonthTestCases() { + it('should return per month donations amount for endaoment projects', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const donationStart = moment().add(10, 'months'); + const donationStart1month = moment().add(11, 'month'); + const donationStart2month = moment().add(12, 'month'); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const donationValueToNonEndaomentinUSD1 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 30, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + const donationValueToNonEndaomentinUSD2 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 40, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + const donationValueToNonEndaomentinUSD3 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 30, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + const donationValueToEndaomentinUSD1 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + const donationValueToEndaomentinUSD2 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 30, + }), + user.id, + endaomentProject.id, + ); + + const donationValueToEndaomentinUSD3 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 40, + }), + user.id, + endaomentProject.id, + ); + const expectedReturnForAllProjects = [ + donationValueToNonEndaomentinUSD1.valueUsd + + donationValueToEndaomentinUSD1.valueUsd, + donationValueToEndaomentinUSD2.valueUsd + + donationValueToNonEndaomentinUSD2.valueUsd, + donationValueToEndaomentinUSD3.valueUsd + + donationValueToNonEndaomentinUSD3.valueUsd, + ]; + + const expectedReturnEndaomentProjects = [ + donationValueToEndaomentinUSD1.valueUsd, + donationValueToEndaomentinUSD2.valueUsd, + donationValueToEndaomentinUSD3.valueUsd, + ]; + const fromDate = donationStart.toISOString(true); + const toDate = donationStart2month.toISOString(true); + const actualReturnEndaomentProjects = + await donationsTotalAmountPerDateRangeByMonth( + fromDate, + toDate, + undefined, + undefined, + true, + ); + const actualReturnAllProjects = + await donationsTotalAmountPerDateRangeByMonth(fromDate, toDate); + + assert.deepEqual( + expectedReturnEndaomentProjects, + actualReturnEndaomentProjects.map( + donationPerDate => donationPerDate.total, + ), + ); + assert.deepEqual( + expectedReturnForAllProjects, + actualReturnAllProjects.map(donationPerDate => donationPerDate.total), + ); + }); +} + +function donationsTotalAmountPerDateRangeTestCases() { + it('should return total donations amount for endaoment projects', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + const donationValueToNonEndaomentinUSD = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(66, 'days').toDate(), + valueUsd: 30, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + const donationValueToEndaomentinUSD = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(66, 'days').toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + const fromDate = moment().add(65, 'days').format('YYYY/MM/DD'); + const toDate = moment().add(67, 'days').toDate().toDateString(); + const totalDonationInTimeFrame = await donationsTotalAmountPerDateRange( + fromDate, + toDate, + ); + const endaomentDonationInTimeFrame = await donationsTotalAmountPerDateRange( + fromDate, + toDate, + undefined, + undefined, + true, + ); + assert.equal( + totalDonationInTimeFrame, + donationValueToEndaomentinUSD.valueUsd + + donationValueToNonEndaomentinUSD.valueUsd, + ); + assert.equal( + endaomentDonationInTimeFrame, + donationValueToEndaomentinUSD.valueUsd, + ); + }); +} + function fillQfRoundDonationsUserScoresTestCases() { let qfRound: QfRound; let qfRoundProject: Project; diff --git a/src/repositories/donationRepository.ts b/src/repositories/donationRepository.ts index 731bcebdc..4b649faaf 100644 --- a/src/repositories/donationRepository.ts +++ b/src/repositories/donationRepository.ts @@ -6,6 +6,7 @@ import { ResourcesTotalPerMonthAndYear } from '../resolvers/donationResolver'; import { logger } from '../utils/logger'; import { QfRound } from '../entities/qfRound'; import { ChainType } from '../types/network'; +import { ORGANIZATION_LABELS } from '../entities/organization'; import { AppDataSource } from '../orm'; import { getPowerRound } from './powerRoundRepository'; @@ -177,6 +178,7 @@ export const donationsTotalAmountPerDateRange = async ( toDate?: string, networkId?: number, onlyVerified?: boolean, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select(`COALESCE(SUM(donation."valueUsd"), 0)`, 'sum') @@ -200,12 +202,23 @@ export const donationsTotalAmountPerDateRange = async ( .andWhere('project.verified = true'); } + if (onlyEndaoment) { + if (!onlyVerified) { + query.leftJoin('donation.project', 'project'); + } + query + .leftJoin('project.organization', 'organization') + .andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); + } + const donationsUsdAmount = await query.getRawOne(); query.cache( `donationsTotalAmountPerDateRange-${fromDate || ''}-${toDate || ''}-${ networkId || 'all' - }-${onlyVerified || 'all'}`, + }-${onlyVerified || 'all'}-${onlyEndaoment || 'all'}`, 300000, ); @@ -217,6 +230,7 @@ export const donationsTotalAmountPerDateRangeByMonth = async ( toDate?: string, networkId?: number, onlyVerified?: boolean, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -243,6 +257,17 @@ export const donationsTotalAmountPerDateRangeByMonth = async ( .andWhere('project.verified = true'); } + if (onlyEndaoment) { + if (!onlyVerified) { + query.leftJoin('donation.project', 'project'); + } + query + .leftJoin('project.organization', 'organization') + .andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); + } + query.groupBy('year, month'); query.orderBy('year', 'ASC'); query.addOrderBy('month', 'ASC'); @@ -250,7 +275,7 @@ export const donationsTotalAmountPerDateRangeByMonth = async ( query.cache( `donationsTotalAmountPerDateRangeByMonth-${fromDate || ''}-${ toDate || '' - }-${networkId || 'all'}-${onlyVerified || 'all'}`, + }-${networkId || 'all'}-${onlyVerified || 'all'}-${onlyEndaoment || 'all'}`, 300000, ); @@ -262,6 +287,7 @@ export const donationsNumberPerDateRange = async ( toDate?: string, networkId?: number, onlyVerified?: boolean, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select(`COALESCE(COUNT(donation.id), 0)`, 'count') @@ -285,12 +311,23 @@ export const donationsNumberPerDateRange = async ( .andWhere('project.verified = true'); } + if (onlyEndaoment) { + if (!onlyVerified) { + query.leftJoin('donation.project', 'project'); + } + query + .leftJoin('project.organization', 'organization') + .andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); + } + const donationsUsdAmount = await query.getRawOne(); query.cache( `donationsTotalNumberPerDateRange-${fromDate || ''}-${toDate || ''}--${ networkId || 'all' - }-${onlyVerified || 'all'}`, + }-${onlyVerified || 'all'}-${onlyEndaoment || 'all'}`, 300000, ); @@ -302,6 +339,7 @@ export const donationsTotalNumberPerDateRangeByMonth = async ( toDate?: string, networkId?: number, onlyVerified?: boolean, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -327,6 +365,17 @@ export const donationsTotalNumberPerDateRangeByMonth = async ( .andWhere('project.verified = true'); } + if (onlyEndaoment) { + if (!onlyVerified) { + query.leftJoin('donation.project', 'project'); + } + query + .leftJoin('project.organization', 'organization') + .andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); + } + query.groupBy('year, month'); query.orderBy('year', 'ASC'); query.addOrderBy('month', 'ASC'); @@ -334,7 +383,7 @@ export const donationsTotalNumberPerDateRangeByMonth = async ( query.cache( `donationsTotalNumberPerDateRangeByMonth-${fromDate || ''}-${ toDate || '' - }-${networkId || 'all'}-${onlyVerified || 'all'}`, + }-${networkId || 'all'}-${onlyVerified || 'all'}-${onlyEndaoment || 'all'}`, 300000, ); @@ -345,6 +394,7 @@ export const donorsCountPerDate = async ( fromDate?: string, toDate?: string, networkId?: number, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -364,11 +414,18 @@ export const donorsCountPerDate = async ( if (networkId) { query.andWhere(`donation."transactionNetworkId" = ${networkId}`); } + if (onlyEndaoment) { + query.leftJoin('donation.project', 'project'); + query.leftJoin('project.organization', 'organization'); + query.andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); + } query.cache( `donorsCountPerDate-${fromDate || ''}-${toDate || ''}-${ networkId || 'all' - }`, + }-${onlyEndaoment || 'all'}`, 300000, ); @@ -413,6 +470,7 @@ export const donorsCountPerDateByMonthAndYear = async ( fromDate?: string, toDate?: string, networkId?: number, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -432,6 +490,14 @@ export const donorsCountPerDateByMonthAndYear = async ( query.andWhere(`donation."transactionNetworkId" = ${networkId}`); } + if (onlyEndaoment) { + query.leftJoin('donation.project', 'project'); + query.leftJoin('project.organization', 'organization'); + query + .andWhere('organization."label" = :label') + .setParameter('label', ORGANIZATION_LABELS.ENDAOMENT); + } + query.groupBy('year, month'); query.orderBy('year', 'ASC'); query.addOrderBy('month', 'ASC'); @@ -439,7 +505,7 @@ export const donorsCountPerDateByMonthAndYear = async ( query.cache( `donorsCountPerDateByMonthAndYear-${fromDate || ''}-${toDate || ''}-${ networkId || 'all' - }`, + } - ${onlyEndaoment || 'all'}`, 300000, ); diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index df0810600..11b80dace 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -167,6 +167,113 @@ function totalDonationsPerCategoryPerDateTestCases() { ); assert.equal(foodTotal.totalUsd, donationToVerified.valueUsd); }); + + it('should return donation count as per category per time range for endaoment projects', async () => { + const allDonationResponse = await axios.post(graphqlUrl, { + query: fetchTotalDonationsPerCategoryPerDate, + }); + const allEndaomentDonationResponse = await axios.post(graphqlUrl, { + query: fetchTotalDonationsPerCategoryPerDate, + variables: { + onlyEndaoment: true, + }, + }); + assert.isOk(allEndaomentDonationResponse); + assert.isOk(allDonationResponse); + const allEndaomentFoodDonations = + allEndaomentDonationResponse.data.data.totalDonationsPerCategory.find( + donation => donation.title === 'food', + ); + const allFoodDonations = + allDonationResponse.data.data.totalDonationsPerCategory.find( + donation => donation.title === 'food', + ); + const amountFoodDonation = allFoodDonations.totalUsd; + const amountEndaomentFoodDonation = allEndaomentFoodDonations.totalUsd; + + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const donationValueToEndaomentinUSD = 20; + const donationValueToNonEndaomentinUSD = 30; + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(45, 'days').toDate(), + valueUsd: donationValueToEndaomentinUSD, + }), + user.id, + endaomentProject.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(45, 'days').toDate(), + valueUsd: donationValueToNonEndaomentinUSD, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + const afterUpdateAllDonationResponse = await axios.post(graphqlUrl, { + query: fetchTotalDonationsPerCategoryPerDate, + }); + const afterUpdateAllEndaomentDonationResponse = await axios.post( + graphqlUrl, + { + query: fetchTotalDonationsPerCategoryPerDate, + variables: { + onlyEndaoment: true, + }, + }, + ); + assert.isOk(afterUpdateAllDonationResponse); + assert.isOk(afterUpdateAllEndaomentDonationResponse); + const allEndaomentFoodDonationsAfterUpdate = + afterUpdateAllEndaomentDonationResponse.data.data.totalDonationsPerCategory.find( + donation => donation.title === 'food', + ); + const allFoodDonationsAfterUpdate = + afterUpdateAllDonationResponse.data.data.totalDonationsPerCategory.find( + donation => donation.title === 'food', + ); + const amountFoodDonationAfterUpdate = allFoodDonationsAfterUpdate.totalUsd; + const amountEndaomentFoodDonationAfterUpdate = + allEndaomentFoodDonationsAfterUpdate.totalUsd; + + assert.equal( + amountFoodDonation + + donationValueToNonEndaomentinUSD + + donationValueToEndaomentinUSD, + amountFoodDonationAfterUpdate, + ); + assert.equal( + amountEndaomentFoodDonation + donationValueToEndaomentinUSD, + amountEndaomentFoodDonationAfterUpdate, + ); + + const totalDonationsToEndaomentInTimeFrame = await axios.post(graphqlUrl, { + query: fetchTotalDonationsPerCategoryPerDate, + variables: { + fromDate: moment().add(44, 'days').toDate(), + toDate: moment().add(46, 'days').toDate(), + onlyEndaoment: true, + }, + }); + + const foodTotal = + totalDonationsToEndaomentInTimeFrame.data.data.totalDonationsPerCategory.find( + d => d.title === 'food', + ); + + assert.equal(foodTotal.totalUsd, donationValueToEndaomentinUSD); + }); } function totalDonationsNumberPerDateTestCases() { @@ -220,6 +327,108 @@ function totalDonationsNumberPerDateTestCases() { 1, ); }); + it('should return donations count for endaoment projects per time range', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(202, 'days').toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(202, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.FIRST_USER.id, + SEED_DATA.SECOND_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(202, 'days').toDate(), + valueUsd: 30, + }), + SEED_DATA.THIRD_USER.id, + SEED_DATA.NON_VERIFIED_PROJECT.id, + ); + const donationsResponse = await axios.post(graphqlUrl, { + query: fetchTotalDonationsNumberPerDateRange, + variables: { + fromDate: moment() + .add(201, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment().add(203, 'days').toDate().toISOString().split('T')[0], + }, + }); + const donationsResponseToVerifiedandEndaoment = await axios.post( + graphqlUrl, + { + query: fetchTotalDonationsNumberPerDateRange, + variables: { + fromDate: moment() + .add(201, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment() + .add(203, 'days') + .toDate() + .toISOString() + .split('T')[0], + onlyVerified: true, + onlyEndaoment: true, + }, + }, + ); + + const donationsResponseToVerified = await axios.post(graphqlUrl, { + query: fetchTotalDonationsNumberPerDateRange, + variables: { + fromDate: moment() + .add(201, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment().add(203, 'days').toDate().toISOString().split('T')[0], + onlyVerified: true, + }, + }); + + assert.isNumber( + donationsResponse.data.data.totalDonationsNumberPerDate.total, + ); + assert.isTrue( + donationsResponse.data.data.totalDonationsNumberPerDate + .totalPerMonthAndYear.length > 0, + ); + assert.equal( + donationsResponse.data.data.totalDonationsNumberPerDate.total, + 3, + ); + assert.equal( + donationsResponseToVerified.data.data.totalDonationsNumberPerDate.total, + 2, + ); + assert.equal( + donationsResponseToVerifiedandEndaoment.data.data + .totalDonationsNumberPerDate.total, + 1, + ); + }); } function donorsCountPerDateTestCases() { @@ -307,6 +516,127 @@ function donorsCountPerDateTestCases() { total, ); }); + it('should return donors unique total count for endaoment projects in a time range', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const walletAddress = generateRandomEtheriumAddress(); + const user = await saveUserDirectlyToDb(walletAddress); + // should count as 1 as it's the same user + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.FIRST_USER.id, + endaomentProject.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + }), + user.id, + endaomentProject.id, + ); + // anonymous donations count as separate + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + anonymous: true, + }), + undefined, + SEED_DATA.SECOND_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + anonymous: true, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + anonymous: true, + }), + undefined, + SEED_DATA.SECOND_PROJECT.id, + ); + + const donationsResponse = await axios.post(graphqlUrl, { + query: fetchTotalDonors, + variables: { + fromDate: moment() + .add(509, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment().add(511, 'days').toDate().toISOString().split('T')[0], + }, + }); + const donationsResponseForEndaoment = await axios.post(graphqlUrl, { + query: fetchTotalDonors, + variables: { + fromDate: moment() + .add(509, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment().add(511, 'days').toDate().toISOString().split('T')[0], + onlyEndaoment: true, + }, + }); + assert.isOk(donationsResponse); + assert.isOk(donationsResponseForEndaoment); + // 2 unique donor and 2 anonymous + assert.equal(donationsResponse.data.data.totalDonorsCountPerDate.total, 4); + const total = + donationsResponse.data.data.totalDonorsCountPerDate.totalPerMonthAndYear.reduce( + (sum, value) => sum + value.total, + 0, + ); + const totalForEndaoment = + donationsResponseForEndaoment.data.data.totalDonorsCountPerDate.totalPerMonthAndYear.reduce( + (sum, value) => sum + value.total, + 0, + ); + assert.equal( + donationsResponse.data.data.totalDonorsCountPerDate.total, + total, + ); + // 2 donors : User Created and First User + assert.equal( + donationsResponseForEndaoment.data.data.totalDonorsCountPerDate.total, + 2, + ); + assert.equal( + donationsResponseForEndaoment.data.data.totalDonorsCountPerDate.total, + totalForEndaoment, + ); + }); } function newDonorsCountAndTotalDonationPerDateTestCases() { @@ -626,6 +956,79 @@ function donationsUsdAmountTestCases() { donationToVerified.valueUsd, ); }); + + it('should return total usd amount for donations to endaoment project made in a time range', async () => { + const endaomentProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, + }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + const donationToNonEndaoment = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(250, 'days').toDate(), + valueUsd: 20, + }), + user.id, + SEED_DATA.SECOND_PROJECT.id, + ); + const donationToEndaoment = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(250, 'days').toDate(), + valueUsd: 10, + }), + user.id, + endaomentProject.id, + ); + + const donationsResponse = await axios.post(graphqlUrl, { + query: fetchTotalDonationsUsdAmount, + variables: { + fromDate: moment() + .add(249, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment().add(251, 'days').toDate().toISOString().split('T')[0], + }, + }); + + const donationsResponseToEndaoment = await axios.post(graphqlUrl, { + query: fetchTotalDonationsUsdAmount, + variables: { + fromDate: moment() + .add(249, 'days') + .toDate() + .toISOString() + .split('T')[0], + toDate: moment().add(251, 'days').toDate().toISOString().split('T')[0], + onlyEndaoment: true, + }, + }); + + assert.isOk(donationsResponse.data.data); + assert.isOk(donationsResponseToEndaoment.data.data); + assert.equal( + donationsResponse.data.data.donationsTotalUsdPerDate.total, + donationToNonEndaoment.valueUsd + donationToEndaoment.valueUsd, + ); + const total = + donationsResponse.data.data.donationsTotalUsdPerDate.totalPerMonthAndYear.reduce( + (sum, value) => sum + value.total, + 0, + ); + assert.equal( + donationsResponse.data.data.donationsTotalUsdPerDate.total, + total, + ); + assert.equal( + donationsResponseToEndaoment.data.data.donationsTotalUsdPerDate.total, + donationToEndaoment.valueUsd, + ); + }); } function donationsTestCases() { diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 211fa7bf7..2e1b4f8fb 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -72,6 +72,7 @@ import { DraftDonation, } from '../entities/draftDonation'; import { nonZeroRecurringDonationsByProjectId } from '../repositories/recurringDonationRepository'; +import { ORGANIZATION_LABELS } from '../entities/organization'; import { getTokenPrice } from '../services/priceService'; import { findTokenByNetworkAndSymbol } from '../utils/tokenUtils'; @@ -315,6 +316,7 @@ export class DonationResolver { @Arg('toDate', { nullable: true }) toDate?: string, @Arg('networkId', { nullable: true }) networkId?: number, @Arg('onlyVerified', { nullable: true }) onlyVerified?: boolean, + @Arg('onlyEndaoment', { nullable: true }) onlyEndaoment?: boolean, ): Promise { try { validateWithJoiSchema( @@ -355,6 +357,13 @@ export class DonationResolver { query.andWhere('projects.verified = true'); } + if (onlyEndaoment) { + query + .leftJoin('projects.organization', 'organization') + .andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); + } return await query.getRawMany(); } catch (e) { logger.error('totalDonationsPerCategory query error', e); @@ -369,6 +378,7 @@ export class DonationResolver { @Arg('toDate', { nullable: true }) toDate?: string, @Arg('networkId', { nullable: true }) networkId?: number, @Arg('onlyVerified', { nullable: true }) onlyVerified?: boolean, + @Arg('onlyEndaoment', { nullable: true }) onlyEndaoment?: boolean, ): Promise { try { validateWithJoiSchema( @@ -380,6 +390,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); const totalPerMonthAndYear = await donationsTotalAmountPerDateRangeByMonth( @@ -387,6 +398,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); return { @@ -406,6 +418,7 @@ export class DonationResolver { @Arg('toDate', { nullable: true }) toDate?: string, @Arg('networkId', { nullable: true }) networkId?: number, @Arg('onlyVerified', { nullable: true }) onlyVerified?: boolean, + @Arg('onlyEndaoment', { nullable: true }) onlyEndaoment?: boolean, ): Promise { try { validateWithJoiSchema( @@ -417,6 +430,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); const totalPerMonthAndYear = await donationsTotalNumberPerDateRangeByMonth( @@ -424,6 +438,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); return { @@ -495,17 +510,24 @@ export class DonationResolver { @Arg('fromDate', { nullable: true }) fromDate?: string, @Arg('toDate', { nullable: true }) toDate?: string, @Arg('networkId', { nullable: true }) networkId?: number, + @Arg('onlyEndaoment', { nullable: true }) onlyEndaoment?: boolean, ): Promise { try { validateWithJoiSchema( { fromDate, toDate }, resourcePerDateReportValidator, ); - const total = await donorsCountPerDate(fromDate, toDate, networkId); + const total = await donorsCountPerDate( + fromDate, + toDate, + networkId, + onlyEndaoment, + ); const totalPerMonthAndYear = await donorsCountPerDateByMonthAndYear( fromDate, toDate, networkId, + onlyEndaoment, ); return { total, diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index 3977856f0..4f6dd6ca3 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -570,12 +570,14 @@ export const fetchTotalDonationsPerCategoryPerDate = ` $toDate: String $networkId: Float $onlyVerified: Boolean + $onlyEndaoment: Boolean ) { totalDonationsPerCategory( fromDate: $fromDate toDate: $toDate networkId: $networkId onlyVerified: $onlyVerified + onlyEndaoment: $onlyEndaoment ) { id title @@ -611,11 +613,13 @@ export const fetchTotalDonors = ` $fromDate: String $toDate: String $networkId: Float + $onlyEndaoment: Boolean ) { totalDonorsCountPerDate( fromDate: $fromDate toDate: $toDate networkId: $networkId + onlyEndaoment: $onlyEndaoment ) { total totalPerMonthAndYear { @@ -632,12 +636,14 @@ export const fetchTotalDonationsUsdAmount = ` $toDate: String $networkId: Float $onlyVerified: Boolean + $onlyEndaoment: Boolean ) { donationsTotalUsdPerDate ( fromDate: $fromDate toDate: $toDate networkId: $networkId onlyVerified: $onlyVerified + onlyEndaoment: $onlyEndaoment ) { total totalPerMonthAndYear { @@ -650,24 +656,26 @@ export const fetchTotalDonationsUsdAmount = ` export const fetchTotalDonationsNumberPerDateRange = ` query ( - $fromDate: String - $toDate: String - $networkId: Float - $onlyVerified: Boolean + $fromDate: String + $toDate: String + $networkId: Float + $onlyVerified: Boolean + $onlyEndaoment: Boolean +) { + totalDonationsNumberPerDate ( + fromDate: $fromDate + toDate: $toDate + networkId: $networkId + onlyVerified: $onlyVerified + onlyEndaoment: $onlyEndaoment ) { - totalDonationsNumberPerDate ( - fromDate: $fromDate - toDate: $toDate - networkId: $networkId - onlyVerified: $onlyVerified - ) { + total + totalPerMonthAndYear { total - totalPerMonthAndYear { - total - date - } + date } } +} `; export const fetchNewDonorsCount = `