Skip to content

feat(client): when adding members to an engine by name or email displayed already added #1211

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

Merged
merged 10 commits into from
Jun 10, 2025

Conversation

aditiiisinhaa
Copy link
Contributor

Description

In the Add members modal, if a user tries to add someone based name or email that already has access to the system then it display user with statement 'Already Added'.

Changes Made

In the add members modal Engine of Function and App, when someone search for the users unknowingly that the user have already been added by someone else and want to add them, then it will display user with statement 'Already Added' and the already added user are disabled to add.

How to Test

For Function

  1. Open Function -> Choose Function -> Access Control -> Add Members
  2. Search for user with name or email.

For App

  1. Open Settings -> App Settings -> Select any app -> Add Members
  2. Search for user with name or email.

…ayed already added
@aditiiisinhaa aditiiisinhaa self-assigned this May 23, 2025
@aditiiisinhaa aditiiisinhaa requested a review from a team as a code owner May 23, 2025 13:24
Copy link

@CodiumAI-Agent /describe

Copy link

@CodiumAI-Agent /review

Copy link

@CodiumAI-Agent /improve

@CodiumAI-Agent
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Unused Import

The has import from mobx is not referenced anywhere in the file. Remove it to avoid lint errors and keep the code clean.

import { has } from 'mobx';
Duplicate Fetch Logic

The two useEffect hooks for fetching project and engine users share almost identical logic. Extract the common code into a helper or custom hook to reduce duplication and improve maintainability.

useEffect(() => {
    if (!open || type !== 'APP') return;

    let cancelled = false;
    setSearchLoading(true);
    const fetchProjectUsers = async () => {
        try {
            const [noCred, cred] = await Promise.all([
                monolithStore.getProjectUsersNoCredentials(
                    adminMode,
                    id,
                    AUTOCOMPLETE_LIMIT,
                    offset,
                    debouncedSearch || '',
                ),
                monolithStore.getProjectUsers(
                    adminMode,
                    id,
                    debouncedSearch || '',
                    '', // permission
                    offset,
                    AUTOCOMPLETE_LIMIT,
                ),
            ]);
            const all = [...(noCred?.data || []), ...(cred?.members || [])];
            const unique = Array.from(
                new Map(all.map((u) => [u.id, u])).values(),
            );
            if (!cancelled) {
                if (unique.length < AUTOCOMPLETE_LIMIT)
                    setInfiniteOn(false);
                if (
                    renderedMembers.length >= AUTOCOMPLETE_LIMIT &&
                    offset > 0
                ) {
                    setRenderedMembers((prev) => [...prev, ...unique]);
                } else {
                    setRenderedMembers(unique);
                }
                setSearchLoading(false);
            }
        } catch (e) {
            if (!cancelled) {
                notification.add({
                    color: 'error',
                    message: String(e),
                });
                setSearchLoading(false);
            }
        }
    };

    fetchProjectUsers();
    return () => {
        cancelled = true;
    };
    // eslint-disable-next-line
}, [open, debouncedSearch, offset, adminMode, id, type]);
// Fetch and combine members for engine types
useEffect(() => {
    if (
        !open ||
        !(
            type === 'DATABASE' ||
            type === 'STORAGE' ||
            type === 'MODEL' ||
            type === 'VECTOR' ||
            type === 'FUNCTION'
        )
    ) {
        return;
    }

    let cancelled = false;
    setSearchLoading(true);

    const fetchEngineUsers = async () => {
        try {
            const [noCred, cred] = await Promise.all([
                monolithStore.getEngineUsersNoCredentials(
                    adminMode,
                    id,
                    AUTOCOMPLETE_LIMIT,
                    offset,
                    debouncedSearch || '',
                ),
                monolithStore.getEngineUsers(
                    adminMode,
                    id,
                    debouncedSearch || '',
                    '', // permission
                    offset,
                    AUTOCOMPLETE_LIMIT,
                ),
            ]);
            const all = [...(noCred?.data || []), ...(cred?.members || [])];
            const unique = Array.from(
                new Map(all.map((u) => [u.id, u])).values(),
            );
            if (!cancelled) {
                if (unique.length < AUTOCOMPLETE_LIMIT)
                    setInfiniteOn(false);
                if (
                    renderedMembers.length >= AUTOCOMPLETE_LIMIT &&
                    offset > 0
                ) {
                    setRenderedMembers((prev) => [...prev, ...unique]);
                } else {
                    setRenderedMembers(unique);
                }
                setSearchLoading(false);
            }
        } catch (e) {
            if (!cancelled) {
                notification.add({
                    color: 'error',
                    message: String(e),
                });
                setSearchLoading(false);
            }
        }
    };

    fetchEngineUsers();
    return () => {
        cancelled = true;
    };
    // eslint-disable-next-line
}, [open, debouncedSearch, offset, adminMode, id, type]);
Permission Check

The code disables options based on option.permission, but it’s unclear if that property always exists or correctly indicates an already-added user. Verify the data shape and property name to ensure the disable logic is reliable.

getOptionDisabled={(option) => !!option.permission}
renderOption={(props, option) => {
    const { ...optionProps } = props;
    const hasPermission = !!option.permission;
    return (
        <li key={option.id} {...optionProps}>
            <div
                style={{
                    maxWidth: '85%',
                    overflow: 'hidden',
                }}
            >
                <MembersAddOverlayUser
                    name={option.name}
                    id={option.id}
                    email={option.email}
                    type={option.type}
                />
            </div>

            {hasPermission && (
                <div
                    style={{
                        whiteSpace: 'nowrap',
                        display: 'inline-block',
                        fontSize: '12px',
                    }}
                >
                    Already Added
                </div>
            )}

@CodiumAI-Agent
Copy link

CodiumAI-Agent commented May 23, 2025

PR Code Suggestions ✨

Latest suggestions up to 4f6416e

CategorySuggestion                                                                                                                                    Impact
Possible issue
Add effect cleanup

Add a cleanup function in the effect to set cancelled to true when the component
unmounts or dependencies change. This prevents state updates on unmounted components
and avoids potential memory leaks.

packages/client/src/components/settings/MembersAddOverlay.tsx [206-285]

 useEffect(() => {
     if (!open) return;
 
     let cancelled = false;
     setSearchLoading(true);
 
     const fetchUsers = async () => { /* ... */ };
 
     fetchUsers();
+
+    return () => {
+        cancelled = true;
+    };
 }, [open, debouncedSearch, offset, adminMode, id, type]);
Suggestion importance[1-10]: 8

__

Why: The effect defines cancelled but lacks a cleanup to set it on unmount, so adding it prevents state updates on unmounted components and avoids memory leaks.

Medium
Use functional state update

Use a functional update for setRenderedMembers to remove the direct reference to
renderedMembers. This ensures the logic always operates on the latest state without
stale closures.

packages/client/src/components/settings/MembersAddOverlay.tsx [264-272]

 if (!cancelled) {
+    setRenderedMembers((prev) => {
+        const next = prev.length >= AUTOCOMPLETE_LIMIT && offset > 0
+            ? [...prev, ...all]
+            : all;
+        return next;
+    });
     if (all.length < AUTOCOMPLETE_LIMIT) setInfiniteOn(false);
-    if (renderedMembers.length >= AUTOCOMPLETE_LIMIT && offset > 0) {
-        setRenderedMembers((prev) => [...prev, ...all]);
-    } else {
-        setRenderedMembers(all);
-    }
     setSearchLoading(false);
 }
Suggestion importance[1-10]: 6

__

Why: Referencing renderedMembers directly within the effect can lead to stale closures; using a functional update ensures the latest state is used when updating.

Low

Previous suggestions

Suggestions up to commit 56347d8
CategorySuggestion                                                                                                                                    Impact
Possible issue
Use functional update for state

Avoid referencing the outer renderedMembers state in the effect to prevent
stale-value bugs; consolidate to a single functional state update that handles both
initial loads and pagination.

packages/client/src/components/settings/MembersAddOverlay.tsx [239-245]

-if (
-    renderedMembers.length >= AUTOCOMPLETE_LIMIT &&
-    offset > 0
-) {
-    setRenderedMembers((prev) => [...prev, ...unique]);
-} else {
-    setRenderedMembers(unique);
-}
+setRenderedMembers((prev) => {
+    if (offset > 0 && prev.length >= AUTOCOMPLETE_LIMIT) {
+        return [...prev, ...unique];
+    }
+    return unique;
+});
Suggestion importance[1-10]: 7

__

Why: Consolidating to a functional state update avoids stale references to renderedMembers and prevents potential bugs.

Medium
General
Remove stale commented code

Remove this commented-out getMembers.refresh() call since it is no longer relevant
and clutters the code.

packages/client/src/components/settings/MembersAddOverlay.tsx [425]

-// getMembers.refresh();
 
+
Suggestion importance[1-10]: 2

__

Why: This commented-out call is no longer used and removing it improves readability, but it has minimal functional impact.

Low

@@ -31,6 +31,7 @@ import { useAPI, useDebounceValue, useRootStore, useSettings } from '@/hooks';
import { MembersAddOverlayUser } from './MembersAddOverlayUser';
import { SETTINGS_ROLE } from './settings.types';
import { permissionPriorityMapper } from '@/utility/general';
import { has } from 'mobx';
Copy link
Contributor

Choose a reason for hiding this comment

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

this is not being used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I've removed it.

),
]);
const all = [...(noCred?.data || []), ...(cred?.members || [])];
const unique = Array.from(
Copy link
Contributor

Choose a reason for hiding this comment

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

the items are always going to be unique so we don't need it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've removed it, kindly review it once.

return () => {
cancelled = true;
};
// eslint-disable-next-line
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need to disable this?

// eslint-disable-next-line
}, [open, debouncedSearch, offset, adminMode, id, type]);
// Fetch and combine members for engine types
useEffect(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of adding 2 useEffects, you can check whether the type is app or not. If the type is app, then the API calls need to go to getProjectUsersNoCredentials and getProjectUsers. If the type is not app, then the API calls need to go to getEngineUsersNoCredentials and getEngineUsers. The rest of the code is the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I've made the required changes, kindly review it.

@@ -348,7 +422,7 @@ export const MembersAddOverlay = (props: MembersAddOverlayProps) => {
success = true;

// refresh the members
getMembers.refresh();
// getMembers.refresh();
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove unused code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I've removed the unused code.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
@aditiiisinhaa
Copy link
Contributor Author

aditiiisinhaa commented Jun 3, 2025

Here are the screenshots of how it appears after the changes:

For Open Function:
FunctionAddMember

For App:
AppAddMember

anurag91jain and others added 5 commits June 3, 2025 16:59

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
…hub.com/SEMOSS/semoss-ui into AddingMemberToEngineDisplayAlreadyAdded
@anurag91jain anurag91jain requested a review from johbaxter June 9, 2025 05:34
@anurag91jain anurag91jain merged commit 84eba19 into dev Jun 10, 2025
3 checks passed
@anurag91jain anurag91jain deleted the AddingMemberToEngineDisplayAlreadyAdded branch June 10, 2025 04:47
Copy link

@CodiumAI-Agent /update_changelog

@CodiumAI-Agent
Copy link

Changelog updates: 🔄

2025-06-10 *

Added

  • Display “Already Added” status and disable selection for users who already have access in the Add Members modal.

to commit the new content to the CHANGELOG.md file, please type:
'/update_changelog --pr_update_changelog.push_changelog_changes=true'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

When adding members to an engine by name or email, display if person already has access
6 participants