From f77816bf9835bce378545db07b8de51d1db5354c Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Wed, 4 Sep 2024 09:19:32 +0530 Subject: [PATCH 01/17] onlyEndaement option added to donationResolvers to get only endaoment projects --- src/repositories/donationRepository.ts | 78 ++++++++++++++++++++++++-- src/resolvers/donationResolver.ts | 21 ++++++- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/repositories/donationRepository.ts b/src/repositories/donationRepository.ts index 398c735a8..3235c4e70 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'; export const fillQfRoundDonationsUserScores = async (): Promise => { await Donation.query(` @@ -175,6 +176,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') @@ -198,12 +200,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, ); @@ -215,6 +228,7 @@ export const donationsTotalAmountPerDateRangeByMonth = async ( toDate?: string, networkId?: number, onlyVerified?: boolean, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -241,6 +255,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'); @@ -248,7 +273,7 @@ export const donationsTotalAmountPerDateRangeByMonth = async ( query.cache( `donationsTotalAmountPerDateRangeByMonth-${fromDate || ''}-${ toDate || '' - }-${networkId || 'all'}-${onlyVerified || 'all'}`, + }-${networkId || 'all'}-${onlyVerified || 'all'}-${onlyEndaoment || 'all'}`, 300000, ); @@ -260,6 +285,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') @@ -283,12 +309,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, ); @@ -300,6 +337,7 @@ export const donationsTotalNumberPerDateRangeByMonth = async ( toDate?: string, networkId?: number, onlyVerified?: boolean, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -325,6 +363,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'); @@ -332,7 +381,7 @@ export const donationsTotalNumberPerDateRangeByMonth = async ( query.cache( `donationsTotalNumberPerDateRangeByMonth-${fromDate || ''}-${ toDate || '' - }-${networkId || 'all'}-${onlyVerified || 'all'}`, + }-${networkId || 'all'}-${onlyVerified || 'all'}-${onlyEndaoment || 'all'}`, 300000, ); @@ -343,6 +392,7 @@ export const donorsCountPerDate = async ( fromDate?: string, toDate?: string, networkId?: number, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -362,11 +412,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, ); @@ -411,6 +468,7 @@ export const donorsCountPerDateByMonthAndYear = async ( fromDate?: string, toDate?: string, networkId?: number, + onlyEndaoment?: boolean, ): Promise => { const query = Donation.createQueryBuilder('donation') .select( @@ -430,6 +488,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'); @@ -437,7 +503,7 @@ export const donorsCountPerDateByMonthAndYear = async ( query.cache( `donorsCountPerDateByMonthAndYear-${fromDate || ''}-${toDate || ''}-${ networkId || 'all' - }`, + } - ${onlyEndaoment || 'all'}`, 300000, ); diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index f972f47a3..9a0d0dea7 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -295,6 +295,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( @@ -335,6 +336,11 @@ export class DonationResolver { query.andWhere('projects.verified = true'); } + if (onlyEndaoment) { + query + .leftJoin('projects.organization', 'organization') + .andWhere('organization."label" = \'endaoment\''); + } return await query.getRawMany(); } catch (e) { logger.error('totalDonationsPerCategory query error', e); @@ -349,6 +355,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( @@ -360,6 +367,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); const totalPerMonthAndYear = await donationsTotalAmountPerDateRangeByMonth( @@ -367,6 +375,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); return { @@ -386,6 +395,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( @@ -397,6 +407,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); const totalPerMonthAndYear = await donationsTotalNumberPerDateRangeByMonth( @@ -404,6 +415,7 @@ export class DonationResolver { toDate, networkId, onlyVerified, + onlyEndaoment, ); return { @@ -434,17 +446,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, From 8953798eadfab4959780556b06ceaf83f1fd64bf Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Wed, 4 Sep 2024 10:08:04 +0530 Subject: [PATCH 02/17] chore: implementing coderabbitai suggestion to remove string literal --- src/resolvers/donationResolver.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/resolvers/donationResolver.ts b/src/resolvers/donationResolver.ts index 9a0d0dea7..ca174c602 100644 --- a/src/resolvers/donationResolver.ts +++ b/src/resolvers/donationResolver.ts @@ -71,6 +71,7 @@ import { DraftDonation, } from '../entities/draftDonation'; import { nonZeroRecurringDonationsByProjectId } from '../repositories/recurringDonationRepository'; +import { ORGANIZATION_LABELS } from '../entities/organization'; const draftDonationEnabled = process.env.ENABLE_DRAFT_DONATION === 'true'; @@ -339,7 +340,9 @@ export class DonationResolver { if (onlyEndaoment) { query .leftJoin('projects.organization', 'organization') - .andWhere('organization."label" = \'endaoment\''); + .andWhere('organization."label" = :label', { + label: ORGANIZATION_LABELS.ENDAOMENT, + }); } return await query.getRawMany(); } catch (e) { From f9090c47e1cfca79a43cab2984b4354d84cae11a Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Mon, 9 Sep 2024 21:49:58 +0530 Subject: [PATCH 03/17] test: add test cases to fetch only Endaoment projects --- package-lock.json | 4 +- src/repositories/donationRepository.test.ts | 468 ++++++++++++++++++++ src/resolvers/donationResolver.test.ts | 399 +++++++++++++++++ test/graphqlQueries.ts | 36 +- 4 files changed, 891 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index a3a11ed57..d7e750829 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "giveth-graphql-api", - "version": "1.25.3", + "version": "1.25.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "giveth-graphql-api", - "version": "1.25.3", + "version": "1.25.0", "hasInstallScript": true, "license": "ISC", "dependencies": { diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index bdbe07598..ec3ce6c9f 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, @@ -28,6 +33,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'; describe('createDonation test cases', createDonationTestCases); @@ -60,7 +66,469 @@ 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); + +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'); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 30, + }), + SEED_DATA.FIRST_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 30, + }), + SEED_DATA.THIRD_USER.id, + SEED_DATA.FIRST_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, + }), + SEED_DATA.SECOND_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, + }); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(221, 'days').toDate(), + valueUsd: 30, + }), + SEED_DATA.FIRST_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(221, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_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'); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_USER.id, + endaomentProject.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + endaomentProject.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 40, + }), + SEED_DATA.SECOND_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, + }); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(445, 'days').toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(445, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_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 donationValueToNonEndaomentinUSD1 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + const donationValueToNonEndaomentinUSD2 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 40, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + const donationValueToNonEndaomentinUSD3 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + const donationValueToEndaomentinUSD1 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart.toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_USER.id, + endaomentProject.id, + ); + const donationValueToEndaomentinUSD2 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart1month.toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + endaomentProject.id, + ); + + const donationValueToEndaomentinUSD3 = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: donationStart2month.toDate(), + valueUsd: 40, + }), + SEED_DATA.SECOND_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 donationValueToNonEndaomentinUSD = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(66, 'days').toDate(), + valueUsd: 30, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + + const donationValueToEndaomentinUSD = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(66, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_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; diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 8f8f879ad..bb983b334 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -162,6 +162,112 @@ 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; + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(45, 'days').toDate(), + valueUsd: donationValueToEndaomentinUSD, + }), + SEED_DATA.SECOND_USER.id, + endaomentProject.id, + ); + + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(45, 'days').toDate(), + valueUsd: donationValueToNonEndaomentinUSD, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_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() { @@ -215,6 +321,106 @@ 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, + }); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(202, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_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.FIRST_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() { @@ -302,6 +508,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.FIRST_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.FIRST_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + anonymous: true, + }), + user.id, + SEED_DATA.FIRST_PROJECT.id, + ); + await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(510, 'days').toDate(), + valueUsd: 20, + anonymous: true, + }), + undefined, + SEED_DATA.FIRST_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() { @@ -621,6 +948,78 @@ 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 donationToNonEndaoment = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(250, 'days').toDate(), + valueUsd: 20, + }), + SEED_DATA.SECOND_USER.id, + SEED_DATA.FIRST_PROJECT.id, + ); + const donationToEndaoment = await saveDonationDirectlyToDb( + createDonationData({ + status: DONATION_STATUS.VERIFIED, + createdAt: moment().add(250, 'days').toDate(), + valueUsd: 10, + }), + SEED_DATA.SECOND_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/test/graphqlQueries.ts b/test/graphqlQueries.ts index c05b84047..32f484e35 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 = ` From 779f2b6e777eaac874d7100591e40818a5fb1702 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Mon, 9 Sep 2024 23:15:17 +0530 Subject: [PATCH 04/17] chore: change the second Project to first Project --- src/resolvers/donationResolver.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index bb983b334..1227cbae7 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -212,7 +212,7 @@ function totalDonationsPerCategoryPerDateTestCases() { valueUsd: donationValueToNonEndaomentinUSD, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const afterUpdateAllDonationResponse = await axios.post(graphqlUrl, { @@ -344,7 +344,7 @@ function totalDonationsNumberPerDateTestCases() { valueUsd: 20, }), SEED_DATA.FIRST_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( createDonationData({ @@ -534,7 +534,7 @@ function donorsCountPerDateTestCases() { valueUsd: 20, }), user.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -555,7 +555,7 @@ function donorsCountPerDateTestCases() { anonymous: true, }), undefined, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( createDonationData({ @@ -565,7 +565,7 @@ function donorsCountPerDateTestCases() { anonymous: true, }), user.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( createDonationData({ @@ -575,7 +575,7 @@ function donorsCountPerDateTestCases() { anonymous: true, }), undefined, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const donationsResponse = await axios.post(graphqlUrl, { From bc28379686cab2f855de1d34dc726b6dd2b24c7b Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Mon, 9 Sep 2024 23:47:59 +0530 Subject: [PATCH 05/17] chore: change the second Project to first Project --- src/repositories/donationRepository.test.ts | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index ec3ce6c9f..d97b08560 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -108,7 +108,7 @@ function donorsCountPerDateByMonthAndYearTestCase() { valueUsd: 30, }), SEED_DATA.FIRST_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -118,7 +118,7 @@ function donorsCountPerDateByMonthAndYearTestCase() { valueUsd: 20, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -128,7 +128,7 @@ function donorsCountPerDateByMonthAndYearTestCase() { valueUsd: 30, }), SEED_DATA.THIRD_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( createDonationData({ @@ -197,7 +197,7 @@ function donorsCountPerDateTestCases() { valueUsd: 30, }), SEED_DATA.FIRST_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -246,7 +246,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { valueUsd: 30, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -256,7 +256,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { valueUsd: 20, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -266,7 +266,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { valueUsd: 30, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( createDonationData({ @@ -339,7 +339,7 @@ function donationsNumberPerDateRangeTestCases() { valueUsd: 30, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -388,7 +388,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { valueUsd: 30, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const donationValueToNonEndaomentinUSD2 = await saveDonationDirectlyToDb( createDonationData({ @@ -397,7 +397,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { valueUsd: 40, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const donationValueToNonEndaomentinUSD3 = await saveDonationDirectlyToDb( @@ -407,7 +407,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { valueUsd: 30, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const donationValueToEndaomentinUSD1 = await saveDonationDirectlyToDb( createDonationData({ @@ -493,7 +493,7 @@ function donationsTotalAmountPerDateRangeTestCases() { valueUsd: 30, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const donationValueToEndaomentinUSD = await saveDonationDirectlyToDb( From 433858bd142477dd759f6ad4e38c63dfd6bbc459 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Tue, 10 Sep 2024 00:01:52 +0530 Subject: [PATCH 06/17] chore: change the second Project to first Project --- src/resolvers/donationResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index 1227cbae7..a80586fe3 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -963,7 +963,7 @@ function donationsUsdAmountTestCases() { valueUsd: 20, }), SEED_DATA.SECOND_USER.id, - SEED_DATA.FIRST_PROJECT.id, + SEED_DATA.SECOND_PROJECT.id, ); const donationToEndaoment = await saveDonationDirectlyToDb( createDonationData({ From 1311d39799fcb1fdb8013bc417d3cb5f8e5d5965 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Tue, 10 Sep 2024 06:50:32 +0530 Subject: [PATCH 07/17] chore: change the second user to new user since it is interfering with the pre-existing test cases --- src/repositories/donationRepository.test.ts | 44 ++++++++++++--------- src/resolvers/donationResolver.test.ts | 14 ++++--- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index d97b08560..0d79e4ac7 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -100,6 +100,7 @@ function donorsCountPerDateByMonthAndYearTestCase() { 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({ @@ -117,7 +118,7 @@ function donorsCountPerDateByMonthAndYearTestCase() { createdAt: donationStart1month.toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -155,7 +156,7 @@ function donorsCountPerDateByMonthAndYearTestCase() { createdAt: donationStart2month.toDate(), valueUsd: 40, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const expectedReturnForAllProjects: number[] = [2, 2, 2]; @@ -189,6 +190,7 @@ function donorsCountPerDateTestCases() { slug: String(new Date().getTime()), organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); await saveDonationDirectlyToDb( createDonationData({ @@ -206,7 +208,7 @@ function donorsCountPerDateTestCases() { createdAt: moment().add(221, 'days').toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const fromDate = moment().add(220, 'days').format('YYYY/MM/DD'); @@ -238,6 +240,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { 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({ @@ -245,7 +248,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { createdAt: donationStart.toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -255,7 +258,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { createdAt: donationStart1month.toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -265,7 +268,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { createdAt: donationStart2month.toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); await saveDonationDirectlyToDb( @@ -274,7 +277,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { createdAt: donationStart.toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); await saveDonationDirectlyToDb( @@ -283,7 +286,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { createdAt: donationStart1month.toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); @@ -293,7 +296,7 @@ function donationsTotalNumberPerDateRangeByMonthTestCase() { createdAt: donationStart2month.toDate(), valueUsd: 40, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const expectedReturnForAllProjects: number[] = [2, 2, 2]; @@ -331,6 +334,7 @@ function donationsNumberPerDateRangeTestCases() { slug: String(new Date().getTime()), organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); await saveDonationDirectlyToDb( createDonationData({ @@ -338,7 +342,7 @@ function donationsNumberPerDateRangeTestCases() { createdAt: moment().add(445, 'days').toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -348,7 +352,7 @@ function donationsNumberPerDateRangeTestCases() { createdAt: moment().add(445, 'days').toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const fromDate = moment().add(444, 'days').format('YYYY/MM/DD'); @@ -380,6 +384,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { 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({ @@ -387,7 +392,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { createdAt: donationStart.toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); const donationValueToNonEndaomentinUSD2 = await saveDonationDirectlyToDb( @@ -396,7 +401,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { createdAt: donationStart1month.toDate(), valueUsd: 40, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -406,7 +411,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { createdAt: donationStart2month.toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); const donationValueToEndaomentinUSD1 = await saveDonationDirectlyToDb( @@ -415,7 +420,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { createdAt: donationStart.toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const donationValueToEndaomentinUSD2 = await saveDonationDirectlyToDb( @@ -424,7 +429,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { createdAt: donationStart1month.toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); @@ -434,7 +439,7 @@ function donationsTotalAmountPerDateRangeByMonthTestCases() { createdAt: donationStart2month.toDate(), valueUsd: 40, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const expectedReturnForAllProjects = [ @@ -485,6 +490,7 @@ function donationsTotalAmountPerDateRangeTestCases() { slug: String(new Date().getTime()), organizationLabel: ORGANIZATION_LABELS.ENDAOMENT, }); + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); const donationValueToNonEndaomentinUSD = await saveDonationDirectlyToDb( createDonationData({ @@ -492,7 +498,7 @@ function donationsTotalAmountPerDateRangeTestCases() { createdAt: moment().add(66, 'days').toDate(), valueUsd: 30, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -502,7 +508,7 @@ function donationsTotalAmountPerDateRangeTestCases() { createdAt: moment().add(66, 'days').toDate(), valueUsd: 20, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); const fromDate = moment().add(65, 'days').format('YYYY/MM/DD'); diff --git a/src/resolvers/donationResolver.test.ts b/src/resolvers/donationResolver.test.ts index a80586fe3..645984ae2 100644 --- a/src/resolvers/donationResolver.test.ts +++ b/src/resolvers/donationResolver.test.ts @@ -194,6 +194,7 @@ function totalDonationsPerCategoryPerDateTestCases() { }); const donationValueToEndaomentinUSD = 20; const donationValueToNonEndaomentinUSD = 30; + const user = await saveUserDirectlyToDb(generateRandomEtheriumAddress()); await saveDonationDirectlyToDb( createDonationData({ @@ -201,7 +202,7 @@ function totalDonationsPerCategoryPerDateTestCases() { createdAt: moment().add(45, 'days').toDate(), valueUsd: donationValueToEndaomentinUSD, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); @@ -211,7 +212,7 @@ function totalDonationsPerCategoryPerDateTestCases() { createdAt: moment().add(45, 'days').toDate(), valueUsd: donationValueToNonEndaomentinUSD, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); @@ -328,13 +329,15 @@ function totalDonationsNumberPerDateTestCases() { 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, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); await saveDonationDirectlyToDb( @@ -956,13 +959,14 @@ function donationsUsdAmountTestCases() { 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, }), - SEED_DATA.SECOND_USER.id, + user.id, SEED_DATA.SECOND_PROJECT.id, ); const donationToEndaoment = await saveDonationDirectlyToDb( @@ -971,7 +975,7 @@ function donationsUsdAmountTestCases() { createdAt: moment().add(250, 'days').toDate(), valueUsd: 10, }), - SEED_DATA.SECOND_USER.id, + user.id, endaomentProject.id, ); From 8d30b1ebe785f98fad5adffc1e143aa9f681a636 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 12 Sep 2024 13:44:00 -0500 Subject: [PATCH 08/17] add isImported And categories to project tab --- src/server/adminJs/tabs/projectsTab.ts | 79 +++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/src/server/adminJs/tabs/projectsTab.ts b/src/server/adminJs/tabs/projectsTab.ts index d0cf6fbe2..fdbbcab40 100644 --- a/src/server/adminJs/tabs/projectsTab.ts +++ b/src/server/adminJs/tabs/projectsTab.ts @@ -55,6 +55,7 @@ import { User } from '../../../entities/user'; import { refreshProjectEstimatedMatchingView } from '../../../services/projectViewsService'; import { extractAdminJsReferrerUrlParams } from '../adminJs'; import { relateManyProjectsToQfRound } from '../../../repositories/qfRoundRepository2'; +import { Category } from '../../../entities/category'; // add queries depending on which filters were selected export const buildProjectsQuery = ( @@ -486,6 +487,15 @@ export const fillSocialProfileAndQfRounds: After< const projectUpdates = await findProjectUpdatesByProjectId(projectId); const project = await findProjectById(projectId); const adminJsBaseUrl = process.env.SERVER_URL; + let categories; + if (project) { + const categoryIds = project!.categories.map(cat => cat.id); + categories = await Category + .createQueryBuilder('category') + .where('category.id IN (:...ids)', { ids: categoryIds }) + .orderBy('category.name', 'ASC') + .getMany(); + } response.record = { ...record, params: { @@ -499,6 +509,10 @@ export const fillSocialProfileAndQfRounds: After< adminJsBaseUrl, }, }; + + if (categories) { + response.record.params.categories = categories.map(cat => `${cat.id} - ${cat.name}`); + } return response; }; @@ -660,7 +674,7 @@ export const projectsTab = { id: { isVisible: { list: false, - filter: false, + filter: true, show: true, edit: false, }, @@ -831,12 +845,34 @@ export const projectsTab = { edit: false, }, }, + categoryIds: { + type: 'reference', + isArray: true, + reference: 'Category', + isVisible: { + list: false, + filter: false, + show: true, + edit: true, + }, + availableValues: async (_record) => { + const categories = await Category + .createQueryBuilder('category') + .where('category.isActive = :isActive', { isActive: true }) + .orderBy('category.name', 'ASC') + .getMany(); + return categories.map(category => ({ + value: category.id, + label: `${category.id} - ${category.name}`, + })); + }, + }, isImported: { isVisible: { list: false, filter: true, show: true, - edit: false, + edit: true, }, }, totalReactions: { @@ -924,6 +960,19 @@ export const projectsTab = { isVisible: false, isAccessible: ({ currentAdmin }) => canAccessProjectAction({ currentAdmin }, ResourceActions.NEW), + before: async (request) => { + if (request.payload.categories) { + request.payload.categories = (request.payload.categories as string[]).map(id => ({ id: parseInt(id, 10) })); + } + return request; + }, + after: async (response) => { + const { record, request } = response; + if (request.payload.categoryIds) { + await saveCategories(record.params.id, request.payload.categoryIds); + } + return response; + }, }, bulkDelete: { isVisible: false, @@ -1014,6 +1063,9 @@ export const projectsTab = { // We put these status changes in payload, so in after hook we would know to send notification for users request.payload.statusChanges = statusChanges.join(','); } + if (request.payload.categories) { + request.payload.categories = (request.payload.categories as string[]).map(id => ({ id: parseInt(id, 10) })); + } return request; }, after: async ( @@ -1155,6 +1207,7 @@ export const projectsTab = { refreshUserProjectPowerView(), refreshProjectFuturePowerView(), refreshProjectPowerView(), + saveCategories(project!.id, request?.payload?.categoryIds || []) ]); return request; }, @@ -1350,3 +1403,25 @@ export const projectsTab = { }, }, }; + +async function saveCategories(projectId: number, categoryIds: string[]) { + if (categoryIds?.length === 0) return; + + const project = await Project + .createQueryBuilder('project') + .leftJoinAndSelect('project.categories', 'category') + .where('project.id = :id', { id: projectId }) + .getOne(); + + if (!project) { + throw new Error('Project not found'); + } + + const categories = await Category + .createQueryBuilder('category') + .where('category.id IN (:...ids)', { ids: categoryIds }) + .getMany(); + + project.categories = categories; + await project.save(); +} From 1d33d4093195a808443e87172f88deafa9a7307a Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Tue, 17 Sep 2024 09:03:49 +0530 Subject: [PATCH 09/17] update branch --- src/repositories/donationRepository.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/repositories/donationRepository.test.ts b/src/repositories/donationRepository.test.ts index fc9f18612..589cf4318 100644 --- a/src/repositories/donationRepository.test.ts +++ b/src/repositories/donationRepository.test.ts @@ -91,7 +91,8 @@ describe( ); describe('donorsCountPerDate() test cases', donorsCountPerDateTestCases); -describe('getSumOfGivbackEligibleDonationsForSpecificRound() test cases', +describe( + 'getSumOfGivbackEligibleDonationsForSpecificRound() test cases', getSumOfGivbackEligibleDonationsForSpecificRoundTestCases, ); From 3d10bbf1976107b36ba04524e8a7f3d4ad9462dd Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 18 Sep 2024 01:58:40 -0500 Subject: [PATCH 10/17] add categories to show and edit forms in adminjs for projects --- src/entities/project.ts | 1 + .../tabs/components/ProjectCategories.tsx | 28 +++++++++ src/server/adminJs/tabs/projectsTab.ts | 61 ++++++++++++------- 3 files changed, 68 insertions(+), 22 deletions(-) create mode 100644 src/server/adminJs/tabs/components/ProjectCategories.tsx diff --git a/src/entities/project.ts b/src/entities/project.ts index c0b02bcf6..ae768c263 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -217,6 +217,7 @@ export class Project extends BaseEntity { @Field(_type => [Category], { nullable: true }) @ManyToMany(_type => Category, category => category.projects, { nullable: true, + eager: true, }) @JoinTable() categories: Category[]; diff --git a/src/server/adminJs/tabs/components/ProjectCategories.tsx b/src/server/adminJs/tabs/components/ProjectCategories.tsx new file mode 100644 index 000000000..27a6e8893 --- /dev/null +++ b/src/server/adminJs/tabs/components/ProjectCategories.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { withTheme } from 'styled-components'; +import { Section, Label } from '@adminjs/design-system'; + +const ProjectUpdates = props => { + const categories = props?.record?.params?.categories; + return ( +
+ +
+ {categories?.map(category => { + return ( +
+
+
+ +

{category.name || ''} - Id: {category.id}

+
+
+ ); + })} +
+
+
+ ); +}; + +export default withTheme(ProjectUpdates); diff --git a/src/server/adminJs/tabs/projectsTab.ts b/src/server/adminJs/tabs/projectsTab.ts index fdbbcab40..3e8ad00dd 100644 --- a/src/server/adminJs/tabs/projectsTab.ts +++ b/src/server/adminJs/tabs/projectsTab.ts @@ -447,6 +447,13 @@ export const addProjectsToQfRound = async ( }; }; + export const extractCategoryIds = (payload: any) => { + if (!payload) return; + return Object.keys(payload) + .filter(key => key.startsWith('categoryIds.')) + .map(key => payload[key]); +} + export const addSingleProjectToQfRound = async ( context: AdminJsContextInterface, request: AdminJsRequestInterface, @@ -489,10 +496,10 @@ export const fillSocialProfileAndQfRounds: After< const adminJsBaseUrl = process.env.SERVER_URL; let categories; if (project) { - const categoryIds = project!.categories.map(cat => cat.id); categories = await Category .createQueryBuilder('category') - .where('category.id IN (:...ids)', { ids: categoryIds }) + .innerJoin('category.projects', 'projects') + .where('projects.id = :id', { id: project.id }) .orderBy('category.name', 'ASC') .getMany(); } @@ -511,7 +518,8 @@ export const fillSocialProfileAndQfRounds: After< }; if (categories) { - response.record.params.categories = categories.map(cat => `${cat.id} - ${cat.name}`); + response.record.params.categoryIds = categories; + response.record.params.categories = categories; } return response; }; @@ -855,6 +863,9 @@ export const projectsTab = { show: true, edit: true, }, + components: { + show: adminJs.bundle('./components/ProjectCategories'), + }, availableValues: async (_record) => { const categories = await Category .createQueryBuilder('category') @@ -968,9 +979,12 @@ export const projectsTab = { }, after: async (response) => { const { record, request } = response; - if (request.payload.categoryIds) { - await saveCategories(record.params.id, request.payload.categoryIds); - } + const project = await Project.findOne({ + where: { id: request?.record?.id }, + }); + const categoryIds = extractCategoryIds(request.record.params); + await saveCategories(project!, categoryIds || []); + return response; }, }, @@ -1001,6 +1015,13 @@ export const projectsTab = { } const project = await findProjectById(Number(request.payload.id)); + if (project) { + await Category.query(` + DELETE FROM project_categories_category + WHERE "projectId" = $1 + `, [project.id]); + } + if ( project && Number(request?.payload?.statusId) !== project?.status?.id @@ -1063,9 +1084,7 @@ export const projectsTab = { // We put these status changes in payload, so in after hook we would know to send notification for users request.payload.statusChanges = statusChanges.join(','); } - if (request.payload.categories) { - request.payload.categories = (request.payload.categories as string[]).map(id => ({ id: parseInt(id, 10) })); - } + return request; }, after: async ( @@ -1203,11 +1222,13 @@ export const projectsTab = { }); } } + const categoryIds = extractCategoryIds(request.record.params); + await Promise.all([ refreshUserProjectPowerView(), refreshProjectFuturePowerView(), refreshProjectPowerView(), - saveCategories(project!.id, request?.payload?.categoryIds || []) + saveCategories(project!, categoryIds || []), ]); return request; }, @@ -1404,18 +1425,14 @@ export const projectsTab = { }, }; -async function saveCategories(projectId: number, categoryIds: string[]) { - if (categoryIds?.length === 0) return; - - const project = await Project - .createQueryBuilder('project') - .leftJoinAndSelect('project.categories', 'category') - .where('project.id = :id', { id: projectId }) - .getOne(); - - if (!project) { - throw new Error('Project not found'); - } +async function saveCategories(project: Project, categoryIds?: string[]) { + if (!project) return; + if (!categoryIds || categoryIds?.length === 0) return; + + await Category.query(` + DELETE FROM project_categories_category + WHERE "projectId" = $1 + `, [project.id]); const categories = await Category .createQueryBuilder('category') From 4b8d3082933aa9d2483d561a4f4018e3a684eeba Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 18 Sep 2024 19:17:28 -0500 Subject: [PATCH 11/17] fix eslint --- .../CustomQfRoundMultiUpdateComponent.tsx | 4 +- .../tabs/components/ProjectCategories.tsx | 4 +- src/server/adminJs/tabs/projectsTab.ts | 63 ++++++++++--------- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx b/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx index 644d6ad82..c86aa78ac 100644 --- a/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx +++ b/src/server/adminJs/tabs/components/CustomQfRoundMultiUpdateComponent.tsx @@ -26,9 +26,7 @@ const RecordInput = ({ index, record, updateRecord, removeRecord }) => ( - updateRecord(index, 'matchingFund', e.target.value) - } + onChange={e => updateRecord(index, 'matchingFund', e.target.value)} required /> diff --git a/src/server/adminJs/tabs/components/ProjectCategories.tsx b/src/server/adminJs/tabs/components/ProjectCategories.tsx index 27a6e8893..5706131cb 100644 --- a/src/server/adminJs/tabs/components/ProjectCategories.tsx +++ b/src/server/adminJs/tabs/components/ProjectCategories.tsx @@ -14,7 +14,9 @@ const ProjectUpdates = props => {
-

{category.name || ''} - Id: {category.id}

+

+ {category.name || ''} - Id: {category.id} +

); diff --git a/src/server/adminJs/tabs/projectsTab.ts b/src/server/adminJs/tabs/projectsTab.ts index 3e8ad00dd..a61aedfa6 100644 --- a/src/server/adminJs/tabs/projectsTab.ts +++ b/src/server/adminJs/tabs/projectsTab.ts @@ -447,12 +447,12 @@ export const addProjectsToQfRound = async ( }; }; - export const extractCategoryIds = (payload: any) => { - if (!payload) return; - return Object.keys(payload) - .filter(key => key.startsWith('categoryIds.')) - .map(key => payload[key]); -} +export const extractCategoryIds = (payload: any) => { + if (!payload) return; + return Object.keys(payload) + .filter(key => key.startsWith('categoryIds.')) + .map(key => payload[key]); +}; export const addSingleProjectToQfRound = async ( context: AdminJsContextInterface, @@ -496,12 +496,11 @@ export const fillSocialProfileAndQfRounds: After< const adminJsBaseUrl = process.env.SERVER_URL; let categories; if (project) { - categories = await Category - .createQueryBuilder('category') - .innerJoin('category.projects', 'projects') - .where('projects.id = :id', { id: project.id }) - .orderBy('category.name', 'ASC') - .getMany(); + categories = await Category.createQueryBuilder('category') + .innerJoin('category.projects', 'projects') + .where('projects.id = :id', { id: project.id }) + .orderBy('category.name', 'ASC') + .getMany(); } response.record = { ...record, @@ -866,12 +865,11 @@ export const projectsTab = { components: { show: adminJs.bundle('./components/ProjectCategories'), }, - availableValues: async (_record) => { - const categories = await Category - .createQueryBuilder('category') + availableValues: async _record => { + const categories = await Category.createQueryBuilder('category') .where('category.isActive = :isActive', { isActive: true }) .orderBy('category.name', 'ASC') - .getMany(); + .getMany(); return categories.map(category => ({ value: category.id, label: `${category.id} - ${category.name}`, @@ -971,18 +969,20 @@ export const projectsTab = { isVisible: false, isAccessible: ({ currentAdmin }) => canAccessProjectAction({ currentAdmin }, ResourceActions.NEW), - before: async (request) => { + before: async request => { if (request.payload.categories) { - request.payload.categories = (request.payload.categories as string[]).map(id => ({ id: parseInt(id, 10) })); + request.payload.categories = ( + request.payload.categories as string[] + ).map(id => ({ id: parseInt(id, 10) })); } return request; }, - after: async (response) => { - const { record, request } = response; + after: async response => { + const { request } = response; const project = await Project.findOne({ where: { id: request?.record?.id }, }); - const categoryIds = extractCategoryIds(request.record.params); + const categoryIds = extractCategoryIds(request.record.params); await saveCategories(project!, categoryIds || []); return response; @@ -1016,10 +1016,13 @@ export const projectsTab = { const project = await findProjectById(Number(request.payload.id)); if (project) { - await Category.query(` + await Category.query( + ` DELETE FROM project_categories_category WHERE "projectId" = $1 - `, [project.id]); + `, + [project.id], + ); } if ( @@ -1084,7 +1087,7 @@ export const projectsTab = { // We put these status changes in payload, so in after hook we would know to send notification for users request.payload.statusChanges = statusChanges.join(','); } - + return request; }, after: async ( @@ -1222,7 +1225,7 @@ export const projectsTab = { }); } } - const categoryIds = extractCategoryIds(request.record.params); + const categoryIds = extractCategoryIds(request.record.params); await Promise.all([ refreshUserProjectPowerView(), @@ -1429,13 +1432,15 @@ async function saveCategories(project: Project, categoryIds?: string[]) { if (!project) return; if (!categoryIds || categoryIds?.length === 0) return; - await Category.query(` + await Category.query( + ` DELETE FROM project_categories_category WHERE "projectId" = $1 - `, [project.id]); + `, + [project.id], + ); - const categories = await Category - .createQueryBuilder('category') + const categories = await Category.createQueryBuilder('category') .where('category.id IN (:...ids)', { ids: categoryIds }) .getMany(); From 70079874a0f0c0187c05c8d4cf941da8ca879769 Mon Sep 17 00:00:00 2001 From: Cherik Date: Thu, 19 Sep 2024 12:04:43 +0330 Subject: [PATCH 12/17] add best match sort option --- src/entities/project.ts | 1 + src/repositories/projectRepository.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/entities/project.ts b/src/entities/project.ts index 711e1334b..ab6f6a8a8 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -77,6 +77,7 @@ export enum SortingField { InstantBoosting = 'InstantBoosting', ActiveQfRoundRaisedFunds = 'ActiveQfRoundRaisedFunds', EstimatedMatching = 'EstimatedMatching', + BestMatch = 'BestMatch', } export enum FilterField { diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index 1edcc8485..a17703158 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -252,6 +252,8 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { .addOrderBy('project.verified', 'DESC'); // Secondary sorting condition } break; + case SortingField.BestMatch: + break; default: query From 61524cdd05f1cf1488a0b99cccb196931650e258 Mon Sep 17 00:00:00 2001 From: Cherik Date: Thu, 19 Sep 2024 12:06:00 +0330 Subject: [PATCH 13/17] update addSearchQuery to prioritize the title --- src/resolvers/projectResolver.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index bafb581a1..74915f8ac 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -328,19 +328,22 @@ export class ProjectResolver { // .addSelect('similarity(project.description, :searchTerm)', 'desc_slm') // .addSelect('similarity(project.impactLocation, :searchTerm)', 'loc_slm') // .setParameter('searchTerm', searchTerm) + .addSelect( + `(CASE + WHEN project.title %> :searchTerm THEN 1 + ELSE 2 + END)`, + 'title_priority', + ) .andWhere( new Brackets(qb => { - qb.where('project.title %> :searchTerm ', { - searchTerm, - }) - .orWhere('project.description %> :searchTerm ', { - searchTerm, - }) - .orWhere('project.impactLocation %> :searchTerm', { - searchTerm, - }); + qb.where('project.title %> :searchTerm', { searchTerm }) + .orWhere('project.description %> :searchTerm', { searchTerm }) + .orWhere('project.impactLocation %> :searchTerm', { searchTerm }); }), ) + .orderBy('title_priority', 'ASC') + .setParameter('searchTerm', searchTerm) ); } From 93f13b6f523164084c519a5c1c10e431940631a6 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Thu, 19 Sep 2024 23:57:08 +0530 Subject: [PATCH 14/17] Add Stellar to QFRound --- src/server/adminJs/tabs/qfRoundTab.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/adminJs/tabs/qfRoundTab.ts b/src/server/adminJs/tabs/qfRoundTab.ts index 5555f9cb1..867a8aa56 100644 --- a/src/server/adminJs/tabs/qfRoundTab.ts +++ b/src/server/adminJs/tabs/qfRoundTab.ts @@ -211,6 +211,7 @@ const availableNetworkValues = [ label: 'MORDOR ETC TESTNET', }, { value: NETWORK_IDS.OPTIMISM_SEPOLIA, label: 'OPTIMISM SEPOLIA' }, + { value: NETWORK_IDS.STELLAR_MAINNET, label: 'STELLAR MAINNET'}, { value: NETWORK_IDS.CELO, label: 'CELO' }, { value: NETWORK_IDS.CELO_ALFAJORES, From 69dd31aa117afe34f3f7a8258eea13e1f26dbff6 Mon Sep 17 00:00:00 2001 From: HrithikSampson Date: Fri, 20 Sep 2024 00:12:41 +0530 Subject: [PATCH 15/17] run linter --- .DS_Store | Bin 10244 -> 10244 bytes src/server/adminJs/tabs/qfRoundTab.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.DS_Store b/.DS_Store index 58d2ae234dbf26fd958abf4c7102880099651500..eff6557793dcc5cf2574f09fa92dd702139f0659 100644 GIT binary patch delta 35 rcmZn(XbG6$&nUSuU^hRbc#AW{1XcrHnS`IV%gj%%FGM^(G3eD delta 161 zcmZn(XbG6$&nUYwU^hRb>|`E+>Uvg&B!*IkOokkWl$>Zfkx#>WHo)g@B5J3?v)YI{>wBZWCbS-^{M?i)C}82s1MPglH;y diff --git a/src/server/adminJs/tabs/qfRoundTab.ts b/src/server/adminJs/tabs/qfRoundTab.ts index 867a8aa56..2e7197d28 100644 --- a/src/server/adminJs/tabs/qfRoundTab.ts +++ b/src/server/adminJs/tabs/qfRoundTab.ts @@ -211,7 +211,7 @@ const availableNetworkValues = [ label: 'MORDOR ETC TESTNET', }, { value: NETWORK_IDS.OPTIMISM_SEPOLIA, label: 'OPTIMISM SEPOLIA' }, - { value: NETWORK_IDS.STELLAR_MAINNET, label: 'STELLAR MAINNET'}, + { value: NETWORK_IDS.STELLAR_MAINNET, label: 'STELLAR MAINNET' }, { value: NETWORK_IDS.CELO, label: 'CELO' }, { value: NETWORK_IDS.CELO_ALFAJORES, From d0728d8cbd5c674e9490abc5c2d0a321af73f890 Mon Sep 17 00:00:00 2001 From: Carlos Date: Thu, 19 Sep 2024 21:11:19 -0500 Subject: [PATCH 16/17] remove eager from project categories in entity --- src/entities/project.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/entities/project.ts b/src/entities/project.ts index 711e1334b..f10daa91b 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -217,7 +217,6 @@ export class Project extends BaseEntity { @Field(_type => [Category], { nullable: true }) @ManyToMany(_type => Category, category => category.projects, { nullable: true, - eager: true, }) @JoinTable() categories: Category[]; From 3ee817a9cc0823e05bcf91d2bccbd6073953f9ef Mon Sep 17 00:00:00 2001 From: Mohammad Ranjbar Z Date: Tue, 24 Sep 2024 11:33:41 +0330 Subject: [PATCH 17/17] Add isGivbackEligible filter --- src/entities/project.ts | 1 + .../projectResolver.allProject.test.ts | 95 ++++++++++++++++++- 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/entities/project.ts b/src/entities/project.ts index fee212f20..8a1a386c0 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -82,6 +82,7 @@ export enum SortingField { export enum FilterField { Verified = 'verified', + IsGivbackEligible = 'isGivbackEligible', AcceptGiv = 'givingBlocksId', AcceptFundOnGnosis = 'acceptFundOnGnosis', AcceptFundOnMainnet = 'acceptFundOnMainnet', diff --git a/src/resolvers/projectResolver.allProject.test.ts b/src/resolvers/projectResolver.allProject.test.ts index e7235cd42..533e61209 100644 --- a/src/resolvers/projectResolver.allProject.test.ts +++ b/src/resolvers/projectResolver.allProject.test.ts @@ -220,9 +220,10 @@ function allProjectsTestCases() { ); assert.isTrue(firstProjectIsOlder); }); - it('should return projects, filter by verified, true', async () => { + + it('should return projects, filter by verified, true #1', async () => { // There is two verified projects so I just need to create a project with verified: false and listed:true - await saveProjectDirectlyToDb({ + const unverifiedProject = await saveProjectDirectlyToDb({ ...createProjectData(), title: String(new Date().getTime()), slug: String(new Date().getTime()), @@ -239,7 +240,97 @@ function allProjectsTestCases() { result.data.data.allProjects.projects.forEach(project => assert.isTrue(project.verified), ); + + // should not include unverified project in the response + assert.notExists( + result.data.data.allProjects.projects.find( + project => Number(project.id) === unverifiedProject.id, + ), + ); + }); + it('should return projects, filter by verified, true #2', async () => { + const verified = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + verified: true, + qualityScore: 0, + }); + const result = await axios.post(graphqlUrl, { + query: fetchMultiFilterAllProjectsQuery, + variables: { + filters: ['Verified'], + sortingBy: SortingField.Newest, + }, + }); + assert.isNotEmpty(result.data.data.allProjects.projects); + result.data.data.allProjects.projects.forEach(project => + assert.isTrue(project.verified), + ); + + // should not include unverified project in the response + assert.exists( + result.data.data.allProjects.projects.find( + project => Number(project.id) === verified.id, + ), + ); }); + + it('should return projects, filter by isGivbackEligible, true #1', async () => { + // There is two isGivbackEligible projects so I just need to create a project with isGivbackEligible: false and listed:true + const notGivbackEligibleProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + isGivbackEligible: false, + qualityScore: 0, + }); + const result = await axios.post(graphqlUrl, { + query: fetchMultiFilterAllProjectsQuery, + variables: { + filters: ['IsGivbackEligible'], + }, + }); + assert.isNotEmpty(result.data.data.allProjects.projects); + result.data.data.allProjects.projects.forEach(project => + assert.isTrue(project.isGivbackEligible), + ); + + // should not include unisGivbackEligible project in the response + assert.notExists( + result.data.data.allProjects.projects.find( + project => Number(project.id) === notGivbackEligibleProject.id, + ), + ); + }); + it('should return projects, filter by isGivbackEligible, true #2', async () => { + const givbackEligibleProject = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + isGivbackEligible: true, + qualityScore: 0, + }); + const result = await axios.post(graphqlUrl, { + query: fetchMultiFilterAllProjectsQuery, + variables: { + filters: ['IsGivbackEligible'], + sortingBy: SortingField.Newest, + }, + }); + assert.isNotEmpty(result.data.data.allProjects.projects); + result.data.data.allProjects.projects.forEach(project => + assert.isTrue(project.isGivbackEligible), + ); + + // should not include unisGivbackEligible project in the response + assert.exists( + result.data.data.allProjects.projects.find( + project => Number(project.id) === givbackEligibleProject.id, + ), + ); + }); + it('should return projects, filter by acceptGiv, true', async () => { await saveProjectDirectlyToDb({ ...createProjectData(),