Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PRD] | [Issue #2529] Organization Hierarchy Cache #2566

Merged
merged 45 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e8d882f
Refactor DaoHierarchyNode and DAONodeInfoCard components for improved…
Da-Colon Nov 22, 2024
7e54387
Add HierarchyDAOInfo cache key and interface for improved caching str…
Da-Colon Nov 22, 2024
7696e74
Implement caching for DAO hierarchy nodes to enhance performance and …
Da-Colon Nov 22, 2024
31bb81d
Refactor DaoHierarchyNode to use DaoHierarchyInfo type and update cac…
Da-Colon Nov 22, 2024
d882324
Merge branch 'develop' into issue/2529-cache-it
Da-Colon Nov 22, 2024
ef12621
Refactor DaoHierarchyNode to use transformNode function for improved …
Da-Colon Nov 22, 2024
232a875
Merge branch 'develop' into issue/2529-cache-it
Da-Colon Nov 22, 2024
55a4cd4
add cache version
Da-Colon Nov 23, 2024
5707413
add new DAOInfo typing
Da-Colon Nov 26, 2024
638640e
refactor: update DAOInfo structure and improve type handling in SafeC…
Da-Colon Nov 26, 2024
7fa5c29
refactor: replace nodeHierarchy with subgraphInfo in DAO components a…
Da-Colon Nov 26, 2024
ebe87c1
refactor: remove unused hooks related to Fractal modules and DAO loading
Da-Colon Nov 26, 2024
3582df2
refactor: update useFractalNode to use subgraph queries and improve D…
Da-Colon Nov 26, 2024
0ca063c
refactor: rename fractalModules to daoModules and update related refe…
Da-Colon Nov 26, 2024
3c35f84
remove duplicate link component
Da-Colon Nov 26, 2024
e62aecb
refactor: simplify props structure in DAONodeInfoCard component
Da-Colon Nov 26, 2024
1f540b6
refactor: update useDaoInfoStore to utilize subgraphInfo and adjust r…
Da-Colon Nov 27, 2024
bd13066
refactor: remove unused setDecentModules call in useUpdateSafeData hook
Da-Colon Nov 27, 2024
b0ec5db
refactor: enhance useFractalNode to integrate safeAPI and decentModul…
Da-Colon Nov 27, 2024
24fc95a
refactor: update DaoHierarchyNode to improve data loading and error h…
Da-Colon Nov 27, 2024
779f296
remove comment, let it ride for now
Da-Colon Nov 27, 2024
2c0fdd7
Merge branch 'issue/2529-cache-it' into breakout-issue/typing-dao-info
Da-Colon Nov 27, 2024
20eee2d
clean up error UI
Da-Colon Nov 27, 2024
db056a2
refactor: replace Flex with Box in DAONodeInfoCard and improve layout…
Da-Colon Nov 27, 2024
51a2c84
refactor: inline props structure in DAONodeInfoCard component
Da-Colon Nov 27, 2024
aa11d66
feat: add voting strategies to DAO hierarchy and update DAONodeInfoCard
Da-Colon Nov 28, 2024
62fb574
fix: handle null node response in DaoHierarchyNode component
Da-Colon Nov 28, 2024
dd821a1
refactor: update layout of voting strategies in DAONodeInfoCard compo…
Da-Colon Nov 28, 2024
680e8dd
refactor: rename modules to modulesAddresses for clarity in DaoInfoSt…
Da-Colon Nov 28, 2024
588301d
refactor: rename daoModules to modules for consistency across compone…
Da-Colon Nov 28, 2024
4fe6a61
refactor: replace FractalModuleData with DecentModule for improved cl…
Da-Colon Nov 28, 2024
41bfc90
refactor: remove unused lookupModules import from useUpdateSafeData hook
Da-Colon Nov 28, 2024
a5fa7f2
Merge branch 'breakout-issue/typing-dao-info' of github.com:decentdao…
Da-Colon Nov 28, 2024
21f4de6
refactor: replace FractalModuleData with DecentModule in DaoHierarchy…
Da-Colon Nov 28, 2024
77881bf
refactor: rename modules variable to decentModules for improved clari…
Da-Colon Nov 28, 2024
782c53b
Merge branch 'breakout-issue/typing-dao-info' of github.com:decentdao…
Da-Colon Nov 28, 2024
730c57c
refactor: update DAO-related types to handle null values for improved…
Da-Colon Nov 29, 2024
003be47
refactor: update safeAddress handling to ensure null safety across ho…
Da-Colon Nov 29, 2024
ed0f426
refactor: remove null type from parentAddress for improved type safet…
Da-Colon Nov 29, 2024
5a9faf6
Merge branch 'breakout-issue/typing-dao-info' of github.com:decentdao…
Da-Colon Nov 30, 2024
f698f95
Merge pull request #2579 from decentdao/issue/2530-hierarchy-voting-s…
Da-Colon Dec 2, 2024
9b9bb42
Merge pull request #2574 from decentdao/breakout-issue/typing-dao-info
Da-Colon Dec 2, 2024
6a47c16
refactor: filter governanceTypes to remove duplicates in DaoHierarchy…
Da-Colon Dec 2, 2024
7d36a2e
Merge branch 'develop' of github.com:decentdao/decent-interface into …
Da-Colon Dec 2, 2024
67d1c1e
refactor: update daoSnapshotENS to subgraphInfo in ProposalsHome comp…
Da-Colon Dec 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 88 additions & 21 deletions src/components/DaoHierarchy/DaoHierarchyNode.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import { Flex, Icon } from '@chakra-ui/react';
import { Center, Flex, Icon, Link } from '@chakra-ui/react';
import { ArrowElbowDownRight } from '@phosphor-icons/react';
import { useEffect, useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import { Address } from 'viem';
import { useChainId } from 'wagmi';
import { DAO_ROUTES } from '../../constants/routes';
import { useLoadDAONode } from '../../hooks/DAO/loaders/useLoadDAONode';
import { CacheKeys } from '../../hooks/utils/cache/cacheDefaults';
import { setValue, getValue } from '../../hooks/utils/cache/useLocalStorage';
import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider';
import { useDaoInfoStore } from '../../store/daoInfo/useDaoInfoStore';
import { DaoInfo, WithError } from '../../types';
import { DaoHierarchyInfo, DaoInfo, WithError } from '../../types';
import { DAONodeInfoCard, NODE_HEIGHT_REM } from '../ui/cards/DAONodeInfoCard';
import { BarLoader } from '../ui/loaders/BarLoader';

function transformNode(node: DaoInfo, safeAddress: Address): DaoHierarchyInfo {
return {
daoName: node.daoName,
safeAddress: safeAddress,
nodeHierarchy: {
parentAddress: node.nodeHierarchy.parentAddress,
childNodes: node.nodeHierarchy.childNodes.map(child => {
if (!child.safe) {
throw new Error('Child node does not have a safe');
}
return {
safeAddress: child.safe.address,
};
}),
},
};
}

/**
* A recursive component that displays a "hierarchy" of DAOInfoCards.
Expand All @@ -24,62 +49,104 @@ export function DaoHierarchyNode({
depth: number;
}) {
const { safe: currentSafe } = useDaoInfoStore();
const [fractalNode, setNode] = useState<DaoInfo>();
const [hierarchyNode, setHierarchyNode] = useState<DaoHierarchyInfo>();
const { addressPrefix } = useNetworkConfig();
const chainId = useChainId();
const { loadDao } = useLoadDAONode();

useEffect(() => {
if (safeAddress) {
const cachedNode = getValue({
cacheName: CacheKeys.HIERARCHY_DAO_INFO,
chainId,
daoAddress: safeAddress,
});
if (cachedNode) {
setHierarchyNode(cachedNode);
return;
}
loadDao(safeAddress).then(_node => {
const errorNode = _node as WithError;
if (!errorNode.error) {
const fnode = _node as DaoInfo;
setNode(fnode);
} else if (errorNode.error === 'errorFailedSearch') {
setNode({
daoName: null,
safe: null,
fractalModules: [],
nodeHierarchy: {
parentAddress: null,
childNodes: [],
const theNode = transformNode(fnode, safeAddress);
setValue(
{
cacheName: CacheKeys.HIERARCHY_DAO_INFO,
chainId,
daoAddress: safeAddress,
},
});
theNode,
);
setHierarchyNode(theNode);
} else if (errorNode.error === 'errorFailedSearch') {
setHierarchyNode(undefined);
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!hierarchyNode) {
// node hasn't loaded yet
return (
<Flex
w="full"
minH="full"
>
<Center w="100%">
<BarLoader />
</Center>
</Flex>
);
}

const isCurrentViewingDAO =
currentSafe?.address.toLowerCase() === hierarchyNode.safeAddress.toLocaleLowerCase();
return (
<Flex
flexDirection="column"
alignItems="stretch"
gap="1.25rem"
width="100%"
>
<DAONodeInfoCard node={fractalNode} />
<Link
Da-Colon marked this conversation as resolved.
Show resolved Hide resolved
as={RouterLink}
to={DAO_ROUTES.dao.relative(addressPrefix, hierarchyNode.safeAddress)}
_hover={{ textDecoration: 'none', cursor: isCurrentViewingDAO ? 'default' : 'pointer' }}
onClick={event => {
if (isCurrentViewingDAO) {
event.preventDefault();
}
}}
>
<DAONodeInfoCard
node={{
daoAddress: hierarchyNode.safeAddress,
daoName: hierarchyNode?.daoName ?? hierarchyNode.safeAddress,
daoSnapshotENS: hierarchyNode?.daoSnapshotENS,
}}
isCurrentViewingDAO={isCurrentViewingDAO}
/>
</Link>

{/* CHILD NODES */}
{fractalNode?.nodeHierarchy.childNodes.map(childNode => {
if (!childNode.safe) {
return null;
}
{hierarchyNode?.nodeHierarchy.childNodes.map(childNode => {
return (
<Flex
minH={`${NODE_HEIGHT_REM}rem`}
key={childNode.safe.address}
key={childNode.safeAddress}
gap="1.25rem"
>
<Icon
as={ArrowElbowDownRight}
my={`${NODE_HEIGHT_REM / 2.5}rem`}
ml="0.5rem"
boxSize="32px"
color={currentSafe?.address === childNode.safe.address ? 'celery-0' : 'neutral-6'}
color={currentSafe?.address === childNode.safeAddress ? 'celery-0' : 'neutral-6'}
/>

<DaoHierarchyNode
safeAddress={childNode.safe.address}
safeAddress={childNode.safeAddress}
depth={depth + 1}
/>
</Flex>
Expand Down
65 changes: 25 additions & 40 deletions src/components/ui/cards/DAONodeInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { Center, Flex, FlexProps, Link, Text, VStack } from '@chakra-ui/react';
import { Flex, Link, Text, VStack } from '@chakra-ui/react';
import { Link as RouterLink } from 'react-router-dom';
import { Address } from 'viem';
import { DAO_ROUTES } from '../../../constants/routes';
import { useAccountFavorites } from '../../../hooks/DAO/loaders/useFavorites';
import { useGetAccountName } from '../../../hooks/utils/useGetAccountName';
import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider';
import { useDaoInfoStore } from '../../../store/daoInfo/useDaoInfoStore';
import { DaoInfo } from '../../../types';
import { SnapshotButton } from '../badges/Snapshot';
import { FavoriteIcon } from '../icons/FavoriteIcon';
import AddressCopier from '../links/AddressCopier';
import { BarLoader } from '../loaders/BarLoader';

export const NODE_HEIGHT_REM = 6.75;
export const NODE_MARGIN_TOP_REM = 1.25;

export interface InfoProps extends FlexProps {
node?: DaoInfo;
interface DAONodeInfoCardProps {
node: {
daoAddress: Address;
daoName: string;
daoSnapshotENS?: string;
};
isCurrentViewingDAO: boolean;
}

/**
Expand All @@ -28,39 +30,22 @@ export interface InfoProps extends FlexProps {
* Simply using the useFractal() hook to get info will end up with the current DAO's
* context being displayed in ALL the node cards in a hierarchy, which is incorrect.
*/
export function DAONodeInfoCard({ node, ...rest }: InfoProps) {
const { safe: currentSafe } = useDaoInfoStore();
export function DAONodeInfoCard(props: DAONodeInfoCardProps) {
const {
node: { daoAddress, daoName, daoSnapshotENS },
isCurrentViewingDAO,
} = props;
const { addressPrefix } = useNetworkConfig();
// for non Fractal Safes
const displayedAddress = node?.safe?.address;
const { displayName } = useGetAccountName(displayedAddress);
const { toggleFavorite, isFavorite } = useAccountFavorites();

// node hasn't loaded yet
if (!node || !displayedAddress) {
return (
<Flex
w="full"
minH="full"
{...rest}
>
<Center w="100%">
<BarLoader />
</Center>
</Flex>
);
}

const isCurrentDAO = displayedAddress === currentSafe?.address;
const daoName = node.daoName || displayName;
const { toggleFavorite, isFavorite } = useAccountFavorites();

return (
<Link
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect this Link wrapping to be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I removed the one i this component

as={RouterLink}
to={DAO_ROUTES.dao.relative(addressPrefix, displayedAddress)}
_hover={{ textDecoration: 'none', cursor: isCurrentDAO ? 'default' : 'pointer' }}
to={DAO_ROUTES.dao.relative(addressPrefix, daoAddress)}
_hover={{ textDecoration: 'none', cursor: isCurrentViewingDAO ? 'default' : 'pointer' }}
onClick={event => {
if (isCurrentDAO) {
if (isCurrentViewingDAO) {
event.preventDefault();
}
}}
Expand All @@ -69,7 +54,7 @@ export function DAONodeInfoCard({ node, ...rest }: InfoProps) {
minH={`${NODE_HEIGHT_REM}rem`}
bg="neutral-2"
_hover={
!isCurrentDAO
!isCurrentViewingDAO
? {
bg: 'neutral-3',
border: '1px solid',
Expand All @@ -80,8 +65,8 @@ export function DAONodeInfoCard({ node, ...rest }: InfoProps) {
p="1.5rem"
width="100%"
borderRadius="0.75rem"
border={isCurrentDAO ? '4px solid' : '1px'}
borderColor={isCurrentDAO ? 'neutral-4' : 'transparent'}
border={isCurrentViewingDAO ? '4px solid' : '1px'}
borderColor={isCurrentViewingDAO ? 'neutral-4' : 'transparent'}
>
<VStack
gap="0.5rem"
Expand All @@ -98,17 +83,17 @@ export function DAONodeInfoCard({ node, ...rest }: InfoProps) {

{/* FAVORITE ICON */}
<FavoriteIcon
isFavorite={isFavorite(displayedAddress)}
toggleFavoriteCallback={() => toggleFavorite(displayedAddress, daoName)}
isFavorite={isFavorite(daoAddress)}
toggleFavoriteCallback={() => toggleFavorite(daoAddress, daoName)}
data-testid="DAOInfo-favorite"
/>

{/* SNAPSHOT ICON LINK */}
{node.daoSnapshotENS && <SnapshotButton snapshotENS={node.daoSnapshotENS} />}
{daoSnapshotENS && <SnapshotButton snapshotENS={daoSnapshotENS} />}
</Flex>

{/* DAO ADDRESS */}
<AddressCopier address={displayedAddress} />
<AddressCopier address={daoAddress} />
</VStack>
</Flex>
</Link>
Expand Down
11 changes: 10 additions & 1 deletion src/hooks/utils/cache/cacheDefaults.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Address } from 'viem';
import { AzoriusProposal } from '../../../types';
import { AzoriusProposal, DaoHierarchyInfo } from '../../../types';

export interface IStorageValue {
// the value to store, 1 character to minimize cache size
Expand Down Expand Up @@ -33,6 +33,7 @@ export enum CacheKeys {
PROPOSAL_CACHE = 'Proposal',
MIGRATION = 'Migration',
IPFS_HASH = 'IPFS Hash',
HIERARCHY_DAO_INFO = 'Hierarchy DAO Info',
// indexDB keys
DECODED_TRANSACTION_PREFIX = 'decode_trans_',
MULTISIG_METADATA_PREFIX = 'm_m_',
Expand Down Expand Up @@ -70,9 +71,15 @@ export interface IPFSHashCacheKey extends CacheKey {
hash: string;
chainId: number;
}
export interface HierarchyDAOInfoCacheKey extends CacheKey {
cacheName: CacheKeys.HIERARCHY_DAO_INFO;
chainId: number;
daoAddress: Address;
}

export type CacheKeyType =
| FavoritesCacheKey
| HierarchyDAOInfoCacheKey
| MasterCacheKey
| ProposalCacheKey
| AverageBlockTimeCacheKey
Expand All @@ -97,6 +104,7 @@ type CacheKeyToValueMap = {
[CacheKeys.AVERAGE_BLOCK_TIME]: number;
[CacheKeys.MIGRATION]: number;
[CacheKeys.IPFS_HASH]: string;
[CacheKeys.HIERARCHY_DAO_INFO]: DaoHierarchyInfo;
};

export type CacheValueType<T extends CacheKeyType> = T extends { cacheName: infer U }
Expand All @@ -114,6 +122,7 @@ export const CACHE_VERSIONS: { [key: string]: number } = Object.freeze({
[CacheKeys.MASTER_COPY]: 1,
[CacheKeys.PROPOSAL_CACHE]: 1,
[CacheKeys.AVERAGE_BLOCK_TIME]: 1,
[CacheKeys.HIERARCHY_DAO_INFO]: 1,
});

/**
Expand Down
12 changes: 12 additions & 0 deletions src/types/fractal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,18 @@ export interface DaoInfo extends SubgraphDAOInfo {
isModulesLoaded?: boolean;
}

export interface DaoHierarchyInfo {
daoName: string | null;
nodeHierarchy: {
parentAddress: Address | null;
childNodes: {
safeAddress: Address;
}[];
Da-Colon marked this conversation as resolved.
Show resolved Hide resolved
};
safeAddress: Address;
daoSnapshotENS?: string;
}

export interface FractalModuleData {
moduleAddress: Address;
moduleType: FractalModuleType;
Expand Down