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: Automatically propagate user permission updates #3881

Merged
merged 2 commits into from
Dec 13, 2024
Merged
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 @@ -21,6 +21,7 @@ import {
TEAM_FIELD_NAME,
TITLE_FIELD_NAME,
} from '~v5/common/ActionSidebar/consts.ts';
import { UserRoleModifier } from '~v5/common/ActionSidebar/partials/forms/ManagePermissionsForm/consts.ts';
import { useDecisionMethod } from '~v5/common/CompletedAction/hooks.ts';
import UserInfoPopover from '~v5/shared/UserInfoPopover/index.ts';
import UserPopover from '~v5/shared/UserPopover/index.ts';
Expand All @@ -41,7 +42,10 @@ import {
TeamFromRow,
} from '../rows/index.ts';

import { transformActionRolesToColonyRoles } from './utils.ts';
import {
getIsPermissionsRemoval,
transformActionRolesToColonyRoles,
} from './utils.ts';

const displayName = 'v5.common.CompletedAction.partials.SetUserRoles';

Expand Down Expand Up @@ -71,6 +75,8 @@ const SetUserRoles = ({ action }: Props) => {
blockNumber,
colonyAddress,
rolesAreMultiSig,
motionData,
multiSigData,
} = action;
const areRolesMultiSig = !!rolesAreMultiSig;

Expand Down Expand Up @@ -115,6 +121,9 @@ const SetUserRoles = ({ action }: Props) => {

const dbPermissionsNew = transformActionRolesToColonyRoles(
historicRoles?.getColonyHistoricRole || roles,
{
isMotion: !!motionData || !!multiSigData,
},
);

const { name: dbRoleNameNew, role: dbRoleForDomainNew } = getRole(
Expand All @@ -137,7 +146,9 @@ const SetUserRoles = ({ action }: Props) => {
[ACTION_TYPE_FIELD_NAME]: Action.ManagePermissions,
member: recipientAddress,
authority: roleAuthority,
role: dbRoleForDomainNew,
role: getIsPermissionsRemoval(roles)
? UserRoleModifier.Remove
: dbRoleForDomainNew,
[TEAM_FIELD_NAME]: fromDomain?.nativeId,
[DECISION_METHOD_FIELD_NAME]: decisionMethod,
[DESCRIPTION_FIELD_NAME]: annotation?.message,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,42 @@ import {
type GetColonyHistoricRoleRolesQuery,
type ColonyActionRoles,
} from '~gql';
import { removeObjectFields } from '~utils/objects/index.ts';

export const getIsPermissionsRemoval = (
roles:
| GetColonyHistoricRoleRolesQuery['getColonyHistoricRole']
| ColonyActionRoles,
) => {
const finalRoles = removeObjectFields(roles, ['__typename']);

if (!finalRoles) {
return false;
}

return Object.values(finalRoles).every((role) => role === false);
};

export const transformActionRolesToColonyRoles = (
roles:
| GetColonyHistoricRoleRolesQuery['getColonyHistoricRole']
| ColonyActionRoles,
args?: {
isMotion?: boolean;
},
): ColonyRole[] => {
if (!roles) return [];
const finalRoles = removeObjectFields(roles, ['__typename']);

if (!finalRoles) return [];

let roleKeys = Object.keys(finalRoles);

const roleKeys = Object.keys(roles).filter((key) => roles[key]);
if (getIsPermissionsRemoval(finalRoles) || args?.isMotion) {
roleKeys = roleKeys.filter((key) => finalRoles[key]);
}

const colonyRoles: ColonyRole[] = roleKeys
.filter((key) => roles[key] !== null)
.filter((key) => finalRoles[key] !== null)
.map((key) => {
const match = key.match(/role_(\d+)/); // Extract the role number
if (match && match[1]) {
Expand Down
22 changes: 9 additions & 13 deletions src/context/AppContext/AppContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import React, {
useEffect,
} from 'react';

import {
GetUserByAddressDocument,
type GetUserByAddressQuery,
type GetUserByAddressQueryVariables,
} from '~gql';
import { useGetUserByAddressLazyQuery } from '~gql';
import useAsyncFunction from '~hooks/useAsyncFunction.ts';
import useJoinedColonies from '~hooks/useJoinedColonies.ts';
import usePrevious from '~hooks/usePrevious.ts';
Expand All @@ -31,6 +27,8 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
// and the first render is important here
const [walletConnecting, setWalletConnecting] = useState(true);

const [getUserByAddress] = useGetUserByAddressLazyQuery();

const {
joinedColonies,
loading: joinedColoniesLoading,
Expand All @@ -44,13 +42,11 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
if (!shouldBackgroundUpdate) {
setUserLoading(true);
}
const apolloClient = getContext(ContextModule.ApolloClient);
const { data } = await apolloClient.query<
GetUserByAddressQuery,
GetUserByAddressQueryVariables
>({
query: GetUserByAddressDocument,
variables: { address: utils.getAddress(address) },

const { data } = await getUserByAddress({
variables: {
address: utils.getAddress(address),
},
fetchPolicy: 'network-only',
});
const [currentUser] = data?.getUserByAddress?.items || [];
Expand All @@ -66,7 +62,7 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
}
}
},
[],
[getUserByAddress],
);

const updateWallet = useCallback(() => {
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 @@ -18,6 +18,7 @@ import {
import { useSearchContext } from '~context/SearchContext/SearchContext.ts';
import {
useOnCreateColonyContributorSubscription,
useOnUpdateColonyContributorSubscription,
useOnUpdateColonySubscription,
useSearchColonyContributorsQuery,
} from '~gql';
Expand Down Expand Up @@ -160,17 +161,27 @@ const MemberContextProvider: FC<PropsWithChildren> = ({ children }) => {
newColonyUpdateResult?.onUpdateColony
?.lastUpdatedContributorsWithReputation;

const { data: newColonyContributorResult } =
const { data: createColonyContributorSubscription } =
useOnCreateColonyContributorSubscription();

const newColonyContributor =
newColonyContributorResult?.onCreateColonyContributor?.contributorAddress;
const newColonyContributorAdded =
createColonyContributorSubscription?.onCreateColonyContributor
?.contributorAddress;

const { data: updateColonyContributorSubscription } =
useOnUpdateColonyContributorSubscription();

const newColonyContributorRolesUpdate =
updateColonyContributorSubscription?.onUpdateColonyContributor?.roles;

const newColonyContributorUpdate =
newColonyContributorAdded || newColonyContributorRolesUpdate;

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 (newColonyUpdate || newColonyContributor) {
if (newColonyUpdate || newColonyContributorUpdate) {
// It looks hacky, but we need the timeout to ensure that opensearch has been updated before we refetch.
timeout = setTimeout(refetchColonyContributors, 2000);
}
Expand All @@ -180,7 +191,7 @@ const MemberContextProvider: FC<PropsWithChildren> = ({ children }) => {
clearTimeout(timeout);
}
};
}, [newColonyUpdate, newColonyContributor, refetchColonyContributors]);
}, [newColonyContributorUpdate, newColonyUpdate, refetchColonyContributors]);

const allMembers = useMemo(
() =>
Expand Down
39 changes: 39 additions & 0 deletions src/graphql/generated.ts
Copy link
Contributor

Choose a reason for hiding this comment

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

@rumzledz please try running codegen again, as we shouldn't be removing the expenditureFunding and expenditureId for the ColonyMultiSig model

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apologies for this @mmioana , I'm not sure what happened. I just ran it and it generated these for me 😓 But I shall generate it again 👌

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All right pushed! 🚀 @mmioana

Original file line number Diff line number Diff line change
Expand Up @@ -10782,6 +10782,11 @@ export type OnCreateColonyContributorSubscriptionVariables = Exact<{ [key: strin

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

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


export type OnUpdateColonyContributorSubscription = { __typename?: 'Subscription', onUpdateColonyContributor?: { __typename?: 'ColonyContributor', contributorAddress: string, 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 } | null };

export type GetMembersCountQueryVariables = Exact<{
filter: SearchableColonyContributorFilterInput;
nextToken?: InputMaybe<Scalars['String']>;
Expand Down Expand Up @@ -14420,6 +14425,40 @@ export function useOnCreateColonyContributorSubscription(baseOptions?: Apollo.Su
}
export type OnCreateColonyContributorSubscriptionHookResult = ReturnType<typeof useOnCreateColonyContributorSubscription>;
export type OnCreateColonyContributorSubscriptionResult = Apollo.SubscriptionResult<OnCreateColonyContributorSubscription>;
export const OnUpdateColonyContributorDocument = gql`
subscription OnUpdateColonyContributor {
onUpdateColonyContributor {
contributorAddress
roles {
items {
...ContributorRoles
}
}
}
}
${ContributorRolesFragmentDoc}`;

/**
* __useOnUpdateColonyContributorSubscription__
*
* To run a query within a React component, call `useOnUpdateColonyContributorSubscription` and pass it any options that fit your needs.
* When your component renders, `useOnUpdateColonyContributorSubscription` 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 } = useOnUpdateColonyContributorSubscription({
* variables: {
* },
* });
*/
export function useOnUpdateColonyContributorSubscription(baseOptions?: Apollo.SubscriptionHookOptions<OnUpdateColonyContributorSubscription, OnUpdateColonyContributorSubscriptionVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSubscription<OnUpdateColonyContributorSubscription, OnUpdateColonyContributorSubscriptionVariables>(OnUpdateColonyContributorDocument, options);
}
export type OnUpdateColonyContributorSubscriptionHookResult = ReturnType<typeof useOnUpdateColonyContributorSubscription>;
export type OnUpdateColonyContributorSubscriptionResult = Apollo.SubscriptionResult<OnUpdateColonyContributorSubscription>;
export const GetMembersCountDocument = gql`
query GetMembersCount($filter: SearchableColonyContributorFilterInput!, $nextToken: String) {
searchColonyContributors(
Expand Down
11 changes: 11 additions & 0 deletions src/graphql/queries/contributors.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ subscription OnCreateColonyContributor {
}
}

subscription OnUpdateColonyContributor {
onUpdateColonyContributor {
contributorAddress
roles {
items {
...ContributorRoles
}
}
}
}

query GetMembersCount(
$filter: SearchableColonyContributorFilterInput!
$nextToken: String
Expand Down
31 changes: 31 additions & 0 deletions src/utils/objects/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { type OptionalValue } from '~types';

export const excludeTypenameKey = <T>(
objectWithTypename: T & { __typename?: string },
) => {
Expand All @@ -17,3 +19,32 @@ export const getProperty = <T>(obj: T, key: string, defaultValue?: any) => {
export const getObjectKeys = <T extends object>(obj: T): Array<keyof T> => {
return Object.keys(obj) as Array<keyof T>;
};

/**
* Removes specified fields from an object and returns the updated object.
*
* @param {OptionalValue<T>} obj - The object to modify. Returns `null` if this is `null` or `undefined`.
* @param {K[]} fields - The fields to remove from the object.
*
* @example
* removeObjectFields({ a: 1, b: 2 }, ['b']); // { a: 1 }
*/
export const removeObjectFields = <
T extends Record<string, any>,
K extends keyof T,
>(
obj: OptionalValue<T>,
fields: K[],
) => {
if (!obj) {
return null;
}

const rest = { ...obj };

fields.forEach((field) => {
delete rest[field];
});

return rest as Omit<T, K>;
};
Loading