Skip to content

Commit

Permalink
OCT-1725: Combine dozens of frontend requests into just a few (#346)
Browse files Browse the repository at this point in the history
## Description

## Definition of Done

1. [ ] Acceptance criteria are met.
2. [ ] PR is manually tested before the merge by developer(s).
    - [ ] Happy path is manually checked.
3. [ ] PR is manually tested by QA when their assistance is required
(1).
- [ ] Octant Areas & Test Cases are checked for impact and updated if
required (2).
4. [ ] Unit tests are added unless there is a reason to omit them.
5. [ ] Automated tests are added when required.
6. [ ] The code is merged.
7. [ ] Tech documentation is added / updated, reviewed and approved
(including mandatory approval by a code owner, should such exist for
changed files).
    - [ ] BE: Swagger documentation is updated.
8. [ ] When required by QA:
    - [ ] Deployed to the relevant environment.
    - [ ] Passed system tests.

---

(1) Developer(s) in coordination with QA decide whether it's required.
For small tickets introducing small changes QA assistance is most
probably not required.

(2) [Octant Areas & Test
Cases](https://docs.google.com/spreadsheets/d/1cRe6dxuKJV3a4ZskAwWEPvrFkQm6rEfyUCYwLTYw_Cc).
  • Loading branch information
jmikolajczyk authored Jul 29, 2024
2 parents a5985a4 + 04407ed commit dc2a3b0
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 73 deletions.
2 changes: 1 addition & 1 deletion client/src/api/calls/epochAllocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type Response = {
allocations: {
amount: string; // WEI
donor: string; // address
proposal: string; // proposal address
project: string; // project address
}[];
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next';
import MetricsGridTile from 'components/Metrics/MetricsGrid/MetricsGridTile';
import MetricsGridTileValue from 'components/Metrics/MetricsGrid/MetricsGridTileValue';
import useMetricsEpoch from 'hooks/helpers/useMetrcisEpoch';
import useProjectsDonors from 'hooks/queries/donors/useProjectsDonors';
import useCryptoValues from 'hooks/queries/useCryptoValues';
import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen';
import useMatchedProjectRewards from 'hooks/queries/useMatchedProjectRewards';
import useProjectsEpoch from 'hooks/queries/useProjectsEpoch';
import i18n from 'i18n';
import useSettingsStore from 'store/settings/store';
import getValueCryptoToDisplay from 'utils/getValueCryptoToDisplay';
Expand All @@ -22,6 +22,7 @@ const MetricsEpochGridBelowThreshold: FC<MetricsEpochGridBelowThresholdProps> =
}) => {
const { t } = useTranslation('translation', { keyPrefix: 'views.metrics' });
const { epoch, lastEpoch } = useMetricsEpoch();
const { data: projectsEpoch } = useProjectsEpoch(epoch);
const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen();
const { data: matchedProjectRewards } = useMatchedProjectRewards(
isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch,
Expand All @@ -34,12 +35,9 @@ const MetricsEpochGridBelowThreshold: FC<MetricsEpochGridBelowThresholdProps> =
},
}));
const { data: cryptoValues, error } = useCryptoValues(displayCurrency);
const { data: projectsDonors } = useProjectsDonors(
isDecisionWindowOpen && epoch === lastEpoch ? undefined : epoch,
);

const projectsBelowThreshold =
Object.keys(projectsDonors).length -
(projectsEpoch?.projectsAddresses.length || 0) -
(matchedProjectRewards?.filter(({ matched }) => matched !== 0n).length || 0);

const ethBelowThresholdToDisplay = getValueCryptoToDisplay({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import uniqBy from 'lodash/uniqBy';
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -16,7 +17,7 @@ const MetricsEpochGridCurrentDonors: FC<MetricsEpochGridCurrentDonorsProps> = ({
const { epoch } = useMetricsEpoch();
const { data: epochAllocations } = useEpochAllocations(epoch);

const currentDonorsString = `${epochAllocations?.length || 0}`;
const currentDonorsString = uniqBy(epochAllocations, 'donor').length.toString();

return (
<MetricsGridTile
Expand Down
86 changes: 34 additions & 52 deletions client/src/hooks/queries/donors/useProjectsDonors.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { UseQueryResult, useQueries } from '@tanstack/react-query';
import { useCallback } from 'react';

import { apiGetProjectDonors } from 'api/calls/projectDonors';
import { QUERY_KEYS } from 'api/queryKeys';
import useCurrentEpoch from 'hooks/queries/useCurrentEpoch';
import useEpochAllocations from 'hooks/queries/useEpochAllocations';
import useIsDecisionWindowOpen from 'hooks/queries/useIsDecisionWindowOpen';
import useProjectsEpoch from 'hooks/queries/useProjectsEpoch';

import { ProjectDonor } from './types';
import { mapDataToProjectDonors } from './utils';

export default function useProjectsDonors(epoch?: number): {
data: { [key: string]: ProjectDonor[] };
Expand All @@ -17,57 +11,45 @@ export default function useProjectsDonors(epoch?: number): {
refetch: () => void;
} {
const { data: currentEpoch } = useCurrentEpoch();
const { data: projectsEpoch } = useProjectsEpoch(epoch);
const { data: isDecisionWindowOpen } = useIsDecisionWindowOpen();

// TODO OCT-1139 implement socket here.

const projectsDonorsResults: UseQueryResult<ProjectDonor[]>[] = useQueries({
queries: (projectsEpoch?.projectsAddresses || []).map(projectAddress => ({
enabled:
!!projectsEpoch &&
!!currentEpoch &&
currentEpoch > 1 &&
(isDecisionWindowOpen === true || epoch !== undefined),
queryFn: () => apiGetProjectDonors(projectAddress, epoch || currentEpoch! - 1),
queryKey: QUERY_KEYS.projectDonors(
projectAddress,
epoch ?? (isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!),
),
select: response => mapDataToProjectDonors(response),
})),
});

const refetchAll = useCallback(() => {
projectsDonorsResults.forEach(result => result.refetch());
}, [projectsDonorsResults]);
const epochToUse = epoch ?? (isDecisionWindowOpen ? currentEpoch! - 1 : currentEpoch!);

const {
data: epochAllocations,
refetch,
isFetching: isFetchingEpochAllocations,
isSuccess,
} = useEpochAllocations(epochToUse);

const projectsDonors =
epochAllocations?.reduce((acc, curr) => {
if (!acc[curr.project]) {
acc[curr.project] = [];
}

acc[curr.project].push({ address: curr.donor, amount: curr.amount });
acc[curr.project].sort((a, b) => {
if (a.amount > b.amount) {
return -1;
}
if (a.amount < b.amount) {
return 1;
}
return 0;
});

return acc;
}, {}) || {};

const isFetching =
isDecisionWindowOpen === undefined ||
projectsEpoch === undefined ||
projectsDonorsResults.length === 0 ||
projectsDonorsResults.some(
({ isFetching: isFetchingProjectsDonorsResult }) => isFetchingProjectsDonorsResult,
);
if (isFetching) {
return {
data: {},
isFetching,
isSuccess: false,
refetch: refetchAll,
};
}
currentEpoch === undefined || isDecisionWindowOpen === undefined || isFetchingEpochAllocations;

return {
data: (projectsDonorsResults || []).reduce((acc, curr, currentIndex) => {
return {
...acc,
[projectsEpoch?.projectsAddresses[currentIndex]]: curr.data,
};
}, {}),
isFetching: false,
data: projectsDonors,
isFetching,
// Ensures projectsDonorsResults is actually fetched with data, and not just an object with undefined values.
isSuccess: !projectsDonorsResults.some(element => !element.isSuccess),
refetch: refetchAll,
isSuccess,
refetch,
};
}
18 changes: 6 additions & 12 deletions client/src/hooks/queries/useEpochAllocations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { parseUnitsBigInt } from 'utils/parseUnitsBigInt';

type EpochAllocation = {
amount: bigint;
user: string;
donor: string;
project: string;
};

type EpochAllocations = EpochAllocation[];
Expand All @@ -20,17 +21,10 @@ export default function useEpochAllocations(
queryKey: QUERY_KEYS.epochAllocations(epoch),
select: response => {
return response.allocations.reduce((acc, curr) => {
const donorIdx = acc.findIndex(({ user }) => user === curr.donor);

if (donorIdx > -1) {
// eslint-disable-next-line operator-assignment
acc[donorIdx].amount = acc[donorIdx].amount + parseUnitsBigInt(curr.amount, 'wei');
} else {
acc.push({
amount: parseUnitsBigInt(curr.amount, 'wei'),
user: curr.donor,
});
}
acc.push({
...curr,
amount: parseUnitsBigInt(curr.amount, 'wei'),
});

return acc;
}, [] as EpochAllocations);
Expand Down
5 changes: 3 additions & 2 deletions client/src/hooks/queries/useProjectsIpfsWithRewards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@ export default function useProjectsIpfsWithRewards(epoch?: number): {
const totalValueOfAllocations =
projectMatchedProjectRewards?.sum ||
(isSuccessProjectsDonors
? projectsDonors[project.address].reduce((acc, curr) => acc + curr.amount, BigInt(0))
? projectsDonors[project.address]?.reduce((acc, curr) => acc + curr.amount, BigInt(0)) ||
BigInt(0)
: BigInt(0));
return {
numberOfDonors: isSuccessProjectsDonors ? projectsDonors[project.address].length : 0,
numberOfDonors: isSuccessProjectsDonors ? projectsDonors[project.address]?.length || 0 : 0,
percentage: projectMatchedProjectRewards?.percentage,
totalValueOfAllocations,
...project,
Expand Down

0 comments on commit dc2a3b0

Please sign in to comment.