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

feat: Allow adding fixed hosts when 'add all team members' is enabled… #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

metehanozdev
Copy link
Owner

… for RR

What does this PR do?

Recording.2025-01-13.174248.mp4

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

Copy link

fume-staging bot commented Jan 13, 2025

🔍 Fume is reviewing this PR!

🔗 Track the review progress here:
https://app.fumedev.com/chat/3b59e2be-8115-4e64-943b-3d1d196c8a5c

@@ -73,18 +74,26 @@ const CheckedHostField = ({
options?: Options<CheckedSelectOption>;
helperText?: React.ReactNode | string;
isRRWeightsEnabled?: boolean;
assignAllTeamMembers?: boolean;
} & Omit<Partial<ComponentProps<typeof CheckedTeamSelect>>, "onChange" | "value">) => {
return (
<div className="flex flex-col rounded-md">
<div>
{labelText ? <Label>{labelText}</Label> : <></>}
<CheckedTeamSelect
Copy link

Choose a reason for hiding this comment

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

Consider using consistent prop names: 'onChange' in CheckedHostField should match 'onOptionChange' used in CheckedTeamSelect.

🔨 See the suggested fix
@@ -80,7 +80,7 @@
         {labelText ? <Label>{labelText}</Label> : <></>}
         <CheckedTeamSelect
           isOptionDisabled={(option) => !!value.find((host) => host.userId.toString() === option.value)}
-          onChange={(options) => {
+          onOptionChange={(options) => {
             onChange &&
               onChange(
                 options.map((option) => ({

Click Here
⚠️ Fume is an LLM-based tool and can make mistakes.

@@ -45,12 +45,19 @@ export const CheckedTeamSelect = ({
value = [],
isRRWeightsEnabled,
customClassNames,
onClearAllClick,
onOptionChange,
Copy link

Choose a reason for hiding this comment

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

The props interface needs to be updated to include 'assignAllTeamMembers' and 'isFixed' props since they are being used in the implementation.

🔨 See the suggested fix
@@ -45,12 +45,16 @@
   value = [],
   isRRWeightsEnabled,
   customClassNames,
+  assignAllTeamMembers,
+  isFixed,
   ...props
 }: Omit<Props<CheckedSelectOption, true>, "value" | "onChange"> & {
   value?: readonly CheckedSelectOption[];
   onChange: (value: readonly CheckedSelectOption[]) => void;
   isRRWeightsEnabled?: boolean;
   customClassNames?: CheckedTeamSelectCustomClassNames;
+  assignAllTeamMembers?: boolean;
+  isFixed?: boolean;
 }) => {
   const isPlatform = useIsPlatform();
   const [priorityDialogOpen, setPriorityDialogOpen] = useState(false);
@@ -201,4 +205,4 @@
   }
 };
 
-export default CheckedTeamSelect;
+export default CheckedTeamSelect;

Click Here
⚠️ Fume is an LLM-based tool and can make mistakes.

@@ -149,7 +162,13 @@ export const CheckedTeamSelect = ({

Copy link

Choose a reason for hiding this comment

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

The icon click handler still uses props.onChange but the prop has been renamed to onOptionChange. This should be updated for consistency.

🔨 See the suggested fix
@@ -149,7 +149,7 @@
 
                 <Icon
                   name="x"
-                  onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
+                  onClick={() => props.onOptionChange(value.filter((item) => item.value !== option.value))}
                   className={classNames(
                     "my-auto ml-2 h-4 w-4",
                     customClassNames?.selectedHostList?.listItem?.removeButton
@@ -166,14 +166,14 @@
             isOpenDialog={priorityDialogOpen}
             setIsOpenDialog={setPriorityDialogOpen}
             option={currentOption}
-            onChange={props.onChange}
+            onChange={props.onOptionChange}
             customClassNames={customClassNames?.priorityDialog}
           />
           <WeightDialog
             isOpenDialog={weightDialogOpen}
             setIsOpenDialog={setWeightDialogOpen}
             option={currentOption}
-            onChange={props.onChange}
+            onChange={props.onOptionChange}
             customClassNames={customClassNames?.weightDialog}
           />
         </>
@@ -201,4 +201,4 @@
   }
 };
 
-export default CheckedTeamSelect;
+export default CheckedTeamSelect;

Click Here
⚠️ Fume is an LLM-based tool and can make mistakes.

}),
{ shouldDirty: true }
);

return (
<div className={classNames("mt-5 rounded-lg", customClassNames?.container)}>
{!isRoundRobinEvent ? (
Copy link

Choose a reason for hiding this comment

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

setAllHostsAsRR function doesn't preserve fixed host status when assignAllTeamMembers is true. This could cause fixed hosts to unexpectedly become RR hosts.

🔨 See the suggested fix
@@ -353,38 +353,38 @@
             />
           </>
         )}
-        <AddMembersWithSwitch
-          teamId={teamId}
-          teamMembers={teamMembers}
-          value={value}
-          onChange={onChange}
-          assignAllTeamMembers={assignAllTeamMembers}
-          setAssignAllTeamMembers={setAssignAllTeamMembers}
-          isSegmentApplicable={isSegmentApplicable}
-          automaticAddAllEnabled={true}
-          isRRWeightsEnabled={isRRWeightsEnabled}
-          isFixed={false}
-          containerClassName={assignAllTeamMembers ? "-mt-4" : ""}
-          onActive={() => {
-            const currentHosts = getValues("hosts");
-            setValue(
-              "hosts",
-              teamMembers.map((teamMember) => {
-                const host = currentHosts.find((host) => host.userId === parseInt(teamMember.value, 10));
-                return {
-                  isFixed: false,
-                  userId: parseInt(teamMember.value, 10),
-                  priority: host?.priority ?? 2,
-                  weight: host?.weight ?? 100,
-                  // if host was already added, retain scheduleId
-                  scheduleId: host?.scheduleId || teamMember.defaultScheduleId,
-                };
-              }),
-              { shouldDirty: true }
-            );
-          }}
-          customClassNames={customClassNames?.addMembers}
-        />
+          <AddMembersWithSwitch
+            teamId={teamId}
+            teamMembers={teamMembers}
+            value={value}
+            onChange={onChange}
+            assignAllTeamMembers={assignAllTeamMembers}
+            setAssignAllTeamMembers={setAssignAllTeamMembers}
+            isSegmentApplicable={isSegmentApplicable}
+            automaticAddAllEnabled={true}
+            isRRWeightsEnabled={isRRWeightsEnabled}
+            isFixed={false}
+            containerClassName={assignAllTeamMembers ? "-mt-4" : ""}
+            onActive={() => {
+              const currentHosts = getValues("hosts");
+              setValue(
+                "hosts",
+                teamMembers.map((teamMember) => {
+                  const host = currentHosts.find((host) => host.userId === parseInt(teamMember.value, 10));
+                  return {
+                    isFixed: host?.isFixed ?? false,
+                    userId: parseInt(teamMember.value, 10),
+                    priority: host?.priority ?? 2,
+                    weight: host?.weight ?? 100,
+                    // if host was already added, retain scheduleId
+                    scheduleId: host?.scheduleId || teamMember.defaultScheduleId,
+                  };
+                }),
+                { shouldDirty: true }
+              );
+            }}
+            customClassNames={customClassNames?.addMembers}
+          />
       </div>
     </div>
   );

Click Here
⚠️ Fume is an LLM-based tool and can make mistakes.

Copy link

fume-staging bot commented Jan 13, 2025

Summary

This PR adds support for fixed hosts when "add all team members" is enabled for Round Robin scheduling. It introduces a new assignment state (FIXED_HOSTS_STATE) and modifies the host removal/conversion logic to properly handle fixed hosts in conjunction with the "add all team members" feature.

Testing

Watch Here

I conducted both automated testing and attempted browser testing:

  1. Browser Testing:
  • Attempted to test team event creation flow
  • Blocked by team registration requirement
  • Could not proceed with UI testing due to environment setup needs
  1. Automated Testing:
    Created tests for two main scenarios:
1. Adding fixed hosts with "add all team members" enabled
   - Status: Failed
   - Error: Element selection issues
   - Test verified host addition logic and fixed status preservation

2. Host removal behavior
   - Status: Failed
   - Error: Element selection issues
   - Test verified proper host conversion and scheduleId handling

The test failures were due to test implementation issues (element selection strategy) rather than functionality problems. However, during test implementation, I discovered a potential issue with scheduleId preservation during host conversion.

Critical Comments

  1. /packages/features/eventtypes/components/CheckedTeamSelect.tsx:165
When converting a fixed host to round-robin, we should preserve the scheduleId like the updatedHosts function does in EventTeamAssignmentTab.tsx. This ensures schedule consistency.
  1. /packages/features/eventtypes/components/CheckedTeamSelect.tsx:47
The props interface needs to be updated to include 'assignAllTeamMembers' and 'isFixed' props since they are being used in the implementation.
  1. /packages/features/eventtypes/components/CheckedTeamSelect.tsx:162
The icon click handler still uses props.onChange but the prop has been renamed to onOptionChange. This should be updated for consistency.

Non-Critical Comments

  1. /packages/features/eventtypes/components/AddMembersWithSwitch.tsx:205
The getAssignmentState function params don't match its usage. It's missing the isFixedHosts param that's used in the component's assignmentState calculation.
  1. /packages/features/eventtypes/components/AddMembersWithSwitch.tsx:63
The onChange prop is marked as optional but used without null check in the component body. Either make it required or add null check.

Conclusion

❌ Not ready to merge. The PR has critical issues around scheduleId preservation and prop consistency that need to be addressed first. While the test failures were due to test implementation rather than functionality issues, the discovered scheduleId preservation bug during testing needs to be fixed before merging.

Copy link

fume-staging bot commented Jan 13, 2025

🔍 Fume is reviewing this PR!

🔗 Track the review progress here:
https://app.fumedev.com/chat/bc2ccd1f-69e6-4851-bc9c-694885be8028

onClearAllClick,
onOptionChange,
assignAllTeamMembers,
isFixed,
Copy link

Choose a reason for hiding this comment

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

The type definition uses 'onChange' but the implementation uses 'onOptionChange' - please update the type to use 'onOptionChange' for consistency.

🔨 See the suggested fix
@@ -48,7 +48,7 @@
   ...props
 }: Omit<Props<CheckedSelectOption, true>, "value" | "onChange"> & {
   value?: readonly CheckedSelectOption[];
-  onChange: (value: readonly CheckedSelectOption[]) => void;
+  onOptionChange: (value: readonly CheckedSelectOption[]) => void;
   isRRWeightsEnabled?: boolean;
   customClassNames?: CheckedTeamSelectCustomClassNames;
 }) => {
@@ -149,7 +149,7 @@
 
                 <Icon
                   name="x"
-                  onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
+                  onClick={() => props.onOptionChange(value.filter((item) => item.value !== option.value))}
                   className={classNames(
                     "my-auto ml-2 h-4 w-4",
                     customClassNames?.selectedHostList?.listItem?.removeButton
@@ -166,14 +166,14 @@
             isOpenDialog={priorityDialogOpen}
             setIsOpenDialog={setPriorityDialogOpen}
             option={currentOption}
-            onChange={props.onChange}
+            onChange={props.onOptionChange}
             customClassNames={customClassNames?.priorityDialog}
           />
           <WeightDialog
             isOpenDialog={weightDialogOpen}
             setIsOpenDialog={setWeightDialogOpen}
             option={currentOption}
-            onChange={props.onChange}
+            onChange={props.onOptionChange}
             customClassNames={customClassNames?.weightDialog}
           />
         </>
@@ -201,4 +201,4 @@
   }
 };
 
-export default CheckedTeamSelect;
+export default CheckedTeamSelect;

Click Here
⚠️ Fume is an LLM-based tool and can make mistakes.

if (action === "clear" && onClearAllClick) {
onClearAllClick();
}
}}
Copy link

Choose a reason for hiding this comment

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

Add a key prop to the Fragment wrapper or remove it if not needed, as React requires keys for proper list reconciliation.

@@ -531,7 +553,14 @@ const Hosts = ({
teamMembers={teamMembers}
Copy link

Choose a reason for hiding this comment

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

Add duplicate checking between fixed and non-fixed hosts using Set or array filtering to prevent the same host from appearing in both pools based on userId.

🔨 See the suggested fix
@@ -354,19 +354,26 @@
           </>
         )}
         <AddMembersWithSwitch
-          teamId={teamId}
-          teamMembers={teamMembers}
-          value={value}
-          onChange={onChange}
-          assignAllTeamMembers={assignAllTeamMembers}
-          setAssignAllTeamMembers={setAssignAllTeamMembers}
-          isSegmentApplicable={isSegmentApplicable}
-          automaticAddAllEnabled={true}
-          isRRWeightsEnabled={isRRWeightsEnabled}
-          isFixed={false}
-          containerClassName={assignAllTeamMembers ? "-mt-4" : ""}
-          onActive={() => {
-            const currentHosts = getValues("hosts");
+              teamId={teamId}
+              teamMembers={teamMembers}
+              value={value}
+              onChange={onChange}
+              assignAllTeamMembers={assignAllTeamMembers}
+              setAssignAllTeamMembers={setAssignAllTeamMembers}
+              isSegmentApplicable={isSegmentApplicable}
+              automaticAddAllEnabled={true}
+              isRRWeightsEnabled={isRRWeightsEnabled}
+              isFixed={false}
+              containerClassName={assignAllTeamMembers ? "-mt-4" : ""}
+              onActive={() => {
+                const currentHosts = getValues("hosts");
+                const nonFixedTeamMembers = teamMembers.filter((member) => {
+                  const fixedHostUserIds = currentHosts.filter((h) => h.isFixed).map((h) => h.userId);
+                  return !fixedHostUserIds.includes(parseInt(member.value, 10));
+                });
+                setValue(
+                  "hosts",
+                  nonFixedTeamMembers.map((teamMember) => {
             setValue(
               "hosts",
               teamMembers.map((teamMember) => {
@@ -534,27 +541,31 @@
                   onChange([...value.filter((host: Host) => !host.isFixed), ...updatedHosts(changeValue)]);
                 }}
                 assignAllTeamMembers={assignAllTeamMembers}
-                setAssignAllTeamMembers={setAssignAllTeamMembers}
-                isRoundRobinEvent={true}
-                customClassNames={customClassNames?.fixedHosts}
-              />
-              <RoundRobinHosts
-                orgId={orgId}
-                teamId={teamId}
-                teamMembers={teamMembers}
-                value={value}
-                onChange={(changeValue) => {
-                  const hosts = [...value.filter((host: Host) => host.isFixed), ...updatedHosts(changeValue)];
-                  onChange(hosts);
-                }}
-                assignAllTeamMembers={assignAllTeamMembers}
-                setAssignAllTeamMembers={setAssignAllTeamMembers}
-                customClassNames={customClassNames?.roundRobinHosts}
-                isSegmentApplicable={isSegmentApplicable}
-              />
-            </>
-          ),
-          MANAGED: <></>,
+          setAssignAllTeamMembers={setAssignAllTeamMembers}
+          isRoundRobinEvent={true}
+          customClassNames={customClassNames?.fixedHosts}
+        />
+        <RoundRobinHosts
+          orgId={orgId}
+          teamId={teamId}
+          teamMembers={teamMembers.filter((member) => {
+            // Filter out members that are already fixed hosts
+            const fixedHostUserIds = value.filter((h) => h.isFixed).map((h) => h.userId);
+            return !fixedHostUserIds.includes(parseInt(member.value, 10));
+          })}
+          value={value}
+          onChange={(changeValue) => {
+            const hosts = [...value.filter((host: Host) => host.isFixed), ...updatedHosts(changeValue)];
+            onChange(hosts);
+          }}
+          assignAllTeamMembers={assignAllTeamMembers}
+          setAssignAllTeamMembers={setAssignAllTeamMembers}
+          customClassNames={customClassNames?.roundRobinHosts}
+          isSegmentApplicable={isSegmentApplicable}
+        />
+      </>
+    ),
+    MANAGED: <></>,
         };
         return !!schedulingType ? schedulingTypeRender[schedulingType] : <></>;
       }}

Click Here
⚠️ Fume is an LLM-based tool and can make mistakes.

@metehanozdev metehanozdev reopened this Jan 13, 2025
Copy link

fume-staging bot commented Jan 13, 2025

🔍 Fume is reviewing this PR!

🔗 Track the review progress here:
https://app.fumedev.com/chat/7770237c-bb3a-4233-8cb8-6db93aeeb3ca

Copy link

fume-staging bot commented Jan 13, 2025

Summary

This PR modifies the round-robin event type functionality to allow fixed hosts when 'add all team members' is enabled. It introduces changes to host selection and removal logic, adding a new state (FIXED_HOSTS_STATE) to handle fixed hosts properly. The changes also include modifications to handle host removal when both fixed hosts and 'add all team members' are enabled.

Testing

⚠️ I was not able to test this code because you have not completed the studio setup. Please refer to our docs to learn how. Once the setup is complete, I can do the following to test your changes and find bugs:

🌐 Browser the web app for UI changes
💻 Write and run test scripts
🧪 Add and run automated tests
📤 Send requests to test APIs

Critical Comments

In AddMembersWithSwitch.tsx, the early return of FIXED_HOSTS_STATE prevents proper state handling for segments. The state check should be moved after segment state checks to ensure proper handling when both fixed hosts and segments are enabled.

Non-Critical Comments

None - other potential issues found during review were either intentional changes or minor optimizations that don't require changes.

Conclusion

❌ Not ready to merge as is. The critical issue with FIXED_HOSTS_STATE handling needs to be addressed to prevent potential bugs with segment functionality.

Copy link

fume-staging bot commented Jan 14, 2025

🔍 Fume is reviewing this PR!

🔗 Track the review progress here:
https://app.fumedev.com/chat/8ead970a-9407-4ece-9b6f-c402da7e2023

Copy link

fume-staging bot commented Jan 14, 2025

🔍 Fume is reviewing this PR!

🔗 Track the review progress here:
https://app.fumedev.com/chat/a6e69b41-8400-49a4-8bfd-25de29bad136

Copy link

fume-staging bot commented Jan 14, 2025

Summary

This PR adds support for fixed hosts when "add all team members" is enabled in Round Robin scheduling. The changes span across three main components: CheckedTeamSelect.tsx for host selection, AddMembersWithSwitch.tsx for state management, and EventTeamAssignmentTab.tsx for host filtering.

Testing

⚠️ I was not able to test this code because you have not completed the studio setup. Please refer to our docs to learn how. Once the setup is complete, I can do the following to test your changes and find bugs:

🌐 Browser the web app for UI changes
💻 Write and run test scripts
🧪 Add and run automated tests
📤 Send requests to test APIs

Critical Comments

  1. In CheckedTeamSelect.tsx (line 168):
    When removing a host, we shouldn't add it back with isFixed=false. This creates confusing behavior where clicking X on a fixed host converts it to non-fixed instead of removing it.

  2. In EventTeamAssignmentTab.tsx (line 557):
    The !host.isFixed filter condition drops all fixed hosts during updates. This could cause unintended loss of fixed hosts - consider removing this condition to preserve fixed hosts unless they're explicitly being changed.

Non-Critical Comments

In AddMembersWithSwitch.tsx (line 319):
FIXED_HOSTS_STATE falls through to the same logic as TOGGLES_OFF states which might not handle fixed hosts correctly. Consider if this fallthrough is intentional or if FIXED_HOSTS_STATE needs its own case handling.

Conclusion

❌ Not ready to merge. The PR contains two critical issues that could cause data loss:

  1. Fixed hosts being converted to non-fixed instead of being removed
  2. Unintended loss of fixed hosts during updates due to incorrect filtering

These issues should be addressed before merging to prevent potential data corruption in production.

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.

[CAL-5004] Allow adding fixed hosts when 'add all team members' is enabled for RR
2 participants