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

Fix: Make our app automatically aware of new Colony Contributors #3801

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ const UserSelect: FC<UserSelectProps> = ({
}}
isLoading={usersOptions.isLoading}
className="z-sidebar"
showEmptyContent={false}
shouldReturnAddresses
/>
)}
Expand Down
2 changes: 2 additions & 0 deletions src/components/v5/shared/SearchSelect/SearchSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ const SearchSelect = React.forwardRef<HTMLDivElement, SearchSelectProps>(
icon={Binoculars}
title={{ id: 'actionSidebar.emptyTitle' }}
description={{ id: 'actionSidebar.emptyDescription' }}
isDropdown
className="!p-0"
/>
)}
</div>
Expand Down
21 changes: 16 additions & 5 deletions src/context/MemberContext/MemberContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
} from '~constants/index.ts';
import { useSearchContext } from '~context/SearchContext/SearchContext.ts';
import {
useOnCreateColonyContributorSubscription,
useOnUpdateColonySubscription,
useSearchColonyContributorsQuery,
} from '~gql';
Expand Down Expand Up @@ -119,7 +120,7 @@
data: memberSearchData,
loading,
fetchMore,
refetch,
refetch: refetchColonyContributors,
} = useSearchColonyContributorsQuery({
variables: {
colonyAddress,
Expand Down Expand Up @@ -153,23 +154,33 @@
},
});

const { data } = useOnUpdateColonySubscription();
const { data: newColonyUpdateResult } = useOnUpdateColonySubscription();

const newColonyUpdate =
newColonyUpdateResult?.onUpdateColony
?.lastUpdatedContributorsWithReputation;

const { data: newColonyContributorResult } =
useOnCreateColonyContributorSubscription();

const newColonyContributor =
newColonyContributorResult?.onCreateColonyContributor?.contributorAddress;

useEffect(() => {
let timeout;
// When the colony first loads, the reputation is updated asynchronously. This means that the currently
// cached reputation might be out of date. If this is the case, we should refetch.
if (data?.onUpdateColony?.lastUpdatedContributorsWithReputation) {
if (newColonyUpdate || newColonyContributor) {
// It looks hacky, but we need the timeout to ensure that opensearch has been updated before we refetch.
timeout = setTimeout(refetch, 2000);
timeout = setTimeout(refetchColonyContributors, 2000);
}

return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [data?.onUpdateColony?.lastUpdatedContributorsWithReputation, refetch]);
}, [newColonyUpdate, newColonyContributor, refetchColonyContributors]);

const allMembers = useMemo(
() =>
Expand Down Expand Up @@ -250,7 +261,7 @@
loading: loading || followersCountLoading || contributorsCountLoading,
hasActiveFilter,
}),
[

Check warning on line 264 in src/context/MemberContext/MemberContextProvider.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useMemo has an unnecessary dependency: 'memberSearchData'. Either exclude it or remove the dependency array
membersByAddress,
filteredMembers,
verifiedMembers,
Expand Down
56 changes: 54 additions & 2 deletions src/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1157,10 +1157,12 @@ export type ColonyMultiSig = {
/** Extended user object for given executedBy */
executedByUser?: Maybe<User>;
/**
* In case of multiple multisig actions in a motion, when funding an expenditure, array containing
* In case of multiple funding actions in a multisig, when funding an expenditure, array containing
* the details of tokens and amounts to be funded
*/
expenditureFunding?: Maybe<Array<ExpenditureFundingItem>>;
/** Expenditure associated with the motion, if any */
expenditureId?: Maybe<Scalars['ID']>;
/** Whether the underlying action completed */
hasActionCompleted: Scalars['Boolean'];
/**
Expand Down Expand Up @@ -1611,6 +1613,7 @@ export type CreateColonyMultiSigInput = {
executedAt?: InputMaybe<Scalars['AWSDateTime']>;
executedBy?: InputMaybe<Scalars['ID']>;
expenditureFunding?: InputMaybe<Array<ExpenditureFundingItemInput>>;
expenditureId?: InputMaybe<Scalars['ID']>;
hasActionCompleted: Scalars['Boolean'];
id?: InputMaybe<Scalars['ID']>;
isDecision: Scalars['Boolean'];
Expand Down Expand Up @@ -2365,7 +2368,7 @@ export type Expenditure = {
ownerAddress: Scalars['ID'];
/** Array containing expenditure slots */
slots: Array<ExpenditureSlot>;
/** Indicates whether the splitPaymentClaimedNotification has been sent to prevent sending multiple notifications */
/** Only used for Split Payments, used to prevent sending multiple payment notifications */
splitPaymentPayoutClaimedNotificationSent?: Maybe<Scalars['Boolean']>;
/** Address of StagedExpenditure extension which set the expenditure as staged, if applicable */
stagedExpenditureAddress?: Maybe<Scalars['ID']>;
Expand Down Expand Up @@ -3295,6 +3298,7 @@ export type ModelColonyMultiSigConditionInput = {
createdAt?: InputMaybe<ModelStringInput>;
executedAt?: InputMaybe<ModelStringInput>;
executedBy?: InputMaybe<ModelIdInput>;
expenditureId?: InputMaybe<ModelIdInput>;
hasActionCompleted?: InputMaybe<ModelBooleanInput>;
isDecision?: InputMaybe<ModelBooleanInput>;
isExecuted?: InputMaybe<ModelBooleanInput>;
Expand Down Expand Up @@ -3322,6 +3326,7 @@ export type ModelColonyMultiSigFilterInput = {
createdAt?: InputMaybe<ModelStringInput>;
executedAt?: InputMaybe<ModelStringInput>;
executedBy?: InputMaybe<ModelIdInput>;
expenditureId?: InputMaybe<ModelIdInput>;
hasActionCompleted?: InputMaybe<ModelBooleanInput>;
id?: InputMaybe<ModelIdInput>;
isDecision?: InputMaybe<ModelBooleanInput>;
Expand Down Expand Up @@ -4374,6 +4379,7 @@ export type ModelSubscriptionColonyMultiSigFilterInput = {
createdAt?: InputMaybe<ModelSubscriptionStringInput>;
executedAt?: InputMaybe<ModelSubscriptionStringInput>;
executedBy?: InputMaybe<ModelSubscriptionIdInput>;
expenditureId?: InputMaybe<ModelSubscriptionIdInput>;
hasActionCompleted?: InputMaybe<ModelSubscriptionBooleanInput>;
id?: InputMaybe<ModelSubscriptionIdInput>;
isDecision?: InputMaybe<ModelSubscriptionBooleanInput>;
Expand Down Expand Up @@ -6674,6 +6680,7 @@ export type Query = {
getMotionTimeoutPeriods?: Maybe<GetMotionTimeoutPeriodsReturn>;
getMotionVoterRewards?: Maybe<ModelVoterRewardsHistoryConnection>;
getMultiSigByColonyAddress?: Maybe<ModelColonyMultiSigConnection>;
getMultiSigByExpenditureId?: Maybe<ModelColonyMultiSigConnection>;
getMultiSigByTransactionHash?: Maybe<ModelColonyMultiSigConnection>;
getMultiSigUserSignature?: Maybe<MultiSigUserSignature>;
getMultiSigUserSignatureByMultiSigId?: Maybe<ModelMultiSigUserSignatureConnection>;
Expand Down Expand Up @@ -7254,6 +7261,16 @@ export type QueryGetMultiSigByColonyAddressArgs = {
};


/** Root query type */
export type QueryGetMultiSigByExpenditureIdArgs = {
expenditureId: Scalars['ID'];
filter?: InputMaybe<ModelColonyMultiSigFilterInput>;
limit?: InputMaybe<Scalars['Int']>;
nextToken?: InputMaybe<Scalars['String']>;
sortDirection?: InputMaybe<ModelSortDirection>;
};


/** Root query type */
export type QueryGetMultiSigByTransactionHashArgs = {
filter?: InputMaybe<ModelColonyMultiSigFilterInput>;
Expand Down Expand Up @@ -9642,6 +9659,7 @@ export type UpdateColonyMultiSigInput = {
executedAt?: InputMaybe<Scalars['AWSDateTime']>;
executedBy?: InputMaybe<Scalars['ID']>;
expenditureFunding?: InputMaybe<Array<ExpenditureFundingItemInput>>;
expenditureId?: InputMaybe<Scalars['ID']>;
hasActionCompleted?: InputMaybe<Scalars['Boolean']>;
id: Scalars['ID'];
isDecision?: InputMaybe<Scalars['Boolean']>;
Expand Down Expand Up @@ -10759,6 +10777,11 @@ export type SearchColonyContributorsQueryVariables = Exact<{

export type SearchColonyContributorsQuery = { __typename?: 'Query', searchColonyContributors?: { __typename?: 'SearchableColonyContributorConnection', total?: number | null, nextToken?: string | null, items: Array<{ __typename?: 'ColonyContributor', contributorAddress: string, isVerified: boolean, hasPermissions?: boolean | null, hasReputation?: boolean | null, isWatching?: boolean | null, colonyReputationPercentage: number, type?: ContributorType | null, roles?: { __typename?: 'ModelColonyRoleConnection', items: Array<{ __typename?: 'ColonyRole', domainId: string, role_0?: boolean | null, role_1?: boolean | null, role_2?: boolean | null, role_3?: boolean | null, role_5?: boolean | null, role_6?: boolean | null, isMultiSig?: boolean | null, id: string, domain: { __typename?: 'Domain', id: string, nativeId: number, metadata?: { __typename?: 'DomainMetadata', name: string, color: DomainColor } | null } } | null> } | null, reputation?: { __typename?: 'ModelContributorReputationConnection', items: Array<{ __typename?: 'ContributorReputation', reputationPercentage: number, reputationRaw: string, domainId: string, id: string, domain: { __typename?: 'Domain', id: string, nativeId: number, metadata?: { __typename?: 'DomainMetadata', name: string, color: DomainColor } | null } } | null> } | null, user?: { __typename?: 'User', walletAddress: string, profile?: { __typename?: 'Profile', avatar?: string | null, bio?: string | null, displayName?: string | null, displayNameChanged?: string | null, email?: string | null, location?: string | null, thumbnail?: string | null, website?: string | null, preferredCurrency?: SupportedCurrencies | null, isAutoOfframpEnabled?: boolean | null, meta?: { __typename?: 'ProfileMetadata', metatransactionsEnabled?: boolean | null, decentralizedModeEnabled?: boolean | null, customRpc?: string | null } | null } | null } | null } | null> } | null };

export type OnCreateColonyContributorSubscriptionVariables = Exact<{ [key: string]: never; }>;


export type OnCreateColonyContributorSubscription = { __typename?: 'Subscription', onCreateColonyContributor?: { __typename?: 'ColonyContributor', contributorAddress: string } | null };

export type GetMembersCountQueryVariables = Exact<{
filter: SearchableColonyContributorFilterInput;
nextToken?: InputMaybe<Scalars['String']>;
Expand Down Expand Up @@ -14368,6 +14391,35 @@ export function useSearchColonyContributorsLazyQuery(baseOptions?: Apollo.LazyQu
export type SearchColonyContributorsQueryHookResult = ReturnType<typeof useSearchColonyContributorsQuery>;
export type SearchColonyContributorsLazyQueryHookResult = ReturnType<typeof useSearchColonyContributorsLazyQuery>;
export type SearchColonyContributorsQueryResult = Apollo.QueryResult<SearchColonyContributorsQuery, SearchColonyContributorsQueryVariables>;
export const OnCreateColonyContributorDocument = gql`
subscription OnCreateColonyContributor {
onCreateColonyContributor {
contributorAddress
}
}
`;

/**
* __useOnCreateColonyContributorSubscription__
*
* To run a query within a React component, call `useOnCreateColonyContributorSubscription` and pass it any options that fit your needs.
* When your component renders, `useOnCreateColonyContributorSubscription` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the subscription, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useOnCreateColonyContributorSubscription({
* variables: {
* },
* });
*/
export function useOnCreateColonyContributorSubscription(baseOptions?: Apollo.SubscriptionHookOptions<OnCreateColonyContributorSubscription, OnCreateColonyContributorSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<OnCreateColonyContributorSubscription, OnCreateColonyContributorSubscriptionVariables>(OnCreateColonyContributorDocument, options);
}
export type OnCreateColonyContributorSubscriptionHookResult = ReturnType<typeof useOnCreateColonyContributorSubscription>;
export type OnCreateColonyContributorSubscriptionResult = Apollo.SubscriptionResult<OnCreateColonyContributorSubscription>;
export const GetMembersCountDocument = gql`
query GetMembersCount($filter: SearchableColonyContributorFilterInput!, $nextToken: String) {
searchColonyContributors(
Expand Down
6 changes: 6 additions & 0 deletions src/graphql/queries/contributors.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ query SearchColonyContributors(
}
}

subscription OnCreateColonyContributor {
onCreateColonyContributor {
contributorAddress
}
}

query GetMembersCount(
$filter: SearchableColonyContributorFilterInput!
$nextToken: String
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/useGetColoniesMembersCount.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useEffect } from 'react';

import {
type SearchableColonyContributorFilterInput,
useGetMembersCountQuery,
useOnCreateColonyContributorSubscription,
} from '~gql';

/**
Expand Down Expand Up @@ -31,6 +34,7 @@ export const useGetColoniesMembersCount = (
data: contributorsCount,
fetchMore,
loading: contributorsCountLoading,
refetch: refetchMembersCount,
} = useGetMembersCountQuery({
variables: {
filter: { ...queryFilter },
Expand Down Expand Up @@ -71,6 +75,27 @@ export const useGetColoniesMembersCount = (
},
});

const { data: newColonyContributorResult } =
useOnCreateColonyContributorSubscription();

const newColonyContributor =
newColonyContributorResult?.onCreateColonyContributor?.contributorAddress;

useEffect(() => {
let timeout: NodeJS.Timeout;

if (newColonyContributor) {
// It looks hacky, but we need the timeout to ensure that opensearch has been updated before we refetch.
Copy link
Contributor

@iamsamgibbs iamsamgibbs Nov 28, 2024

Choose a reason for hiding this comment

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

I know this fixes such a small edge case that it doesn't really matter too much, but is a 2000 timeout reliable for opensearch having updated? Does it reliably take less than 2 seconds?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @iamsamgibbs ! I tried to do 1 second and was able to notice an issue in like 1 out of 10 times. But I never had an issue with 2, so I stuck with 2, which also aligns with the colony contributor refetch done on the MemberContextProvider. There’s also a little loading window before the user gets to the colony after joining so it shouldn’t be too much of an issue 😄

timeout = setTimeout(refetchMembersCount, 2000);
}

return () => {
if (timeout) {
clearTimeout(timeout);
}
};
}, [newColonyContributor, refetchMembersCount]);

const membersCount =
contributorsCount?.searchColonyContributors?.aggregateItems[0]?.result
?.__typename === 'SearchableAggregateBucketResult'
Expand Down
Loading