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

Optimize scheduling logic #145

Merged
merged 4 commits into from
Nov 26, 2024
Merged

Conversation

cp-sidhdhi-p
Copy link
Collaborator

@cp-sidhdhi-p cp-sidhdhi-p commented Nov 25, 2024

Optimize scheduling logic based on tournament type
fix notification toggle not sync with data

Summary by CodeRabbit

  • New Features

    • Enhanced user notification settings toggle functionality for better user experience.
    • Improved match scheduling logic for various tournament types, simplifying the scheduling process.
    • Introduced new helper methods for specific match scheduling scenarios, improving code organization.
  • Bug Fixes

    • Resolved issues related to user notification settings handling, ensuring robust error management.
  • Refactor

    • Streamlined methods for knockout and round-robin matches, enhancing readability and maintainability.

Copy link

coderabbitai bot commented Nov 25, 2024

Walkthrough

The changes in this pull request involve modifications to the ProfileViewNotifier class and the MatchScheduler class. In ProfileViewNotifier, the method onToggleUserNotificationChange now checks for a valid user ID before toggling the user's notification settings. In MatchScheduler, several methods related to match scheduling have been simplified and restructured for clarity, including the introduction of new helper methods to encapsulate specific logic. These changes enhance the organization and maintainability of the code.

Changes

File Path Change Summary
khelo/lib/ui/flow/profile/profile_view_model.dart Modified onToggleUserNotificationChange to check for user ID validity and toggle notification settings accordingly.
khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart Restructured match scheduling methods to simplify logic, introduced new helper methods, and updated method signatures.

Possibly related PRs

  • Improve home screen content #118: The addition of the updated_at field in the MatchModel class is relevant as it relates to the management of match data, which is also a focus in the main PR's changes to user notification settings.
  • Implement tournament matches tab #123: This PR implements a new tab for tournament matches, which may interact with user notifications and settings related to matches, linking it to the changes in the main PR.
  • Show match group tag #134: The addition of new localization keys related to tournament matches and filtering aligns with the changes made in the main PR regarding user notifications and match settings.
  • Double out match scheduling #135: The introduction of a custom match type and modifications to match scheduling could relate to how user notifications are managed for different match types in the main PR.
  • Best of match scheduling #136: Changes to the TournamentType enum and the removal of the superOver type may impact how notifications are handled for different match types, connecting it to the main PR.
  • Fix scoreboard squad swapping issue #137: Enhancements in the MatchService for team and player management could relate to user notifications about match updates, linking it to the main PR.
  • Remove group option conditionally #138: The conditional removal of group options and validation for tournament dates may affect how user notifications are structured, connecting it to the changes in the main PR.

Suggested reviewers

  • cp-mayank

🐇 "In the meadow where bunnies play,
Notifications toggle, hip-hip-hooray!
Matches now flow with a clearer design,
Scheduling made easy, oh how divine!
With each little change, our code will gleam,
Hopping along, we chase the dream!" 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
khelo/lib/ui/flow/profile/profile_view_model.dart (1)

Line range hint 89-94: Consider handling potential race conditions

The current implementation might lead to state inconsistencies if multiple toggles are triggered rapidly, as the state update happens after the async operation.

Consider this more robust implementation:

   try {
     if (state.currentUser == null) return;
     state = state.copyWith(actionError: null);
+    
+    final newNotificationState = !state.enableUserNotification;
+    // Update local state first for immediate UI feedback
+    state = state.copyWith(enableUserNotification: newNotificationState);
 
     await _userService.updateUserNotificationSettings(
-        state.currentUser?.id ?? "INVALID ID", !state.enableUserNotification);
-    state = state.copyWith(
-      enableUserNotification: !state.enableUserNotification,
-      actionError: null,
-    );
+        state.currentUser!.id, newNotificationState);
   } catch (error) {
+    // Revert state on error
+    state = state.copyWith(
+      enableUserNotification: !state.enableUserNotification,
+      actionError: error
+    );
-    state = state.copyWith(actionError: error);
     debugPrint(
         "ProfileViewNotifier: error while update user notifications -> $error");
   }
khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (1)

637-655: Consider adding unit tests for handleSingleMiniRobinMatches

The new method handleSingleMiniRobinMatches encapsulates specific logic for mini round-robin scheduling. Adding unit tests will help ensure its correctness across different team counts and match scenarios.

Would you like assistance in generating unit tests for this method?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c477c68 and 6466d97.

📒 Files selected for processing (2)
  • khelo/lib/ui/flow/profile/profile_view_model.dart (1 hunks)
  • khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (4 hunks)
🔇 Additional comments (5)
khelo/lib/ui/flow/profile/profile_view_model.dart (1)

Line range hint 89-94: Verify consistent state management pattern

Let's verify if similar state management patterns are used consistently across the codebase for other async operations.

✅ Verification successful

Let me gather more information about the state management patterns in the codebase.


State management pattern is consistent with codebase practices

The verification shows that the state management pattern in the notification settings update is consistent with other async operations in the same file. The pattern consistently includes:

  • Setting actionError: null before operations
  • Using state.copyWith() for state updates
  • Proper error handling that sets actionError with the caught error
  • Debug logging in catch blocks

The implementation follows the established patterns seen in other async operations like sign out, URL launching, sharing, and other state updates in the same view model.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for similar state management patterns in async operations
ast-grep --pattern 'await $_service.$_($$$);
state = state.copyWith($$$);'

# Search for updateUserNotificationSettings usage
rg "updateUserNotificationSettings" -A 5

Length of output: 1061


Script:

#!/bin/bash
# Search for similar copyWith patterns in async operations with error handling
ast-grep --pattern 'try {
  await $_service.$_($$$);
  state = state.copyWith($$$);
} catch (error) {
  $$$
}'

# Search for other state updates in profile_view_model.dart
rg "state = state.copyWith" khelo/lib/ui/flow/profile/profile_view_model.dart -B 2 -A 2

Length of output: 2183

khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (4)

51-51: Updated method call to scheduleKnockOutMatches

The method scheduleMatchesByType now calls scheduleKnockOutMatches() without arguments, reflecting the updated method signature. Ensure that the internal state used by scheduleKnockOutMatches is properly initialized before this call.


65-90: Verify loop termination in scheduleKnockOutMatches

In the while (teamPool.length > 1) loop, please confirm that teamPool is being updated correctly within each iteration, particularly inside the handleSingleKnockoutPhase method. This ensures the loop will terminate and avoids potential infinite loops.


Line range hint 97-180: Review of scheduleMiniRoundRobinMatches logic

The implementation for scheduling mini round-robin matches appears comprehensive. The calculation of matchesPerTeam based on totalTeams is logical. Ensure that the sorting of teamPoints correctly reflects tie-breaker rules and ranking criteria.


187-282: Ensure correct advancement of teams in scheduleBoxLeagueMatches

The scheduleBoxLeagueMatches method introduces logic for box league scheduling. Please verify that the calculation of expectedQualifiers and team selection for advancing to subsequent rounds correctly align with the tournament rules.

khelo/lib/ui/flow/profile/profile_view_model.dart Outdated Show resolved Hide resolved
@canopas canopas deleted a comment from coderabbitai bot Nov 25, 2024
@canopas canopas deleted a comment from coderabbitai bot Nov 25, 2024
Copy link
Collaborator

@cp-mayank cp-mayank left a comment

Choose a reason for hiding this comment

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

Some time group add icons not show in group tag

Screenshot from 2024-11-25 15-36-44

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (5)

65-90: Consider extracting group progression logic and handling state mutations carefully.

While the refactoring improves readability, there are two concerns:

  1. Direct mutation of teamPool could lead to side effects. Consider using immutable operations.
  2. The group progression logic (lines 79-90) is repeated in other methods and could be extracted.
+ private MatchGroup getNextGroup(int expectedQualifier) {
+   if (expectedQualifier > 8) {
+     return currentGroup;
+   } else if (expectedQualifier > 4) {
+     return MatchGroup.quarterfinal;
+   } else if (expectedQualifier > 2) {
+     return MatchGroup.semifinal;
+   } else {
+     return MatchGroup.finals;
+   }
+ }

675-697: Add documentation for the box league implementation.

The box league logic is complex and would benefit from documentation explaining the algorithm and its constraints.

+ /// Handles the scheduling of matches for a single box in a box league tournament.
+ /// 
+ /// @param allMatches All matches in the tournament
+ /// @param matches Matches in the current box
+ /// @param teamPool Available teams
+ /// @param group Current match group
+ /// @param number Current round number
+ /// @param boxSize Size of each box (number of teams)
  void handleSingleBoxLeague(

699-714: Consider making boxSize configurable.

The box size logic is complex and the value is hardcoded in scheduleBoxLeagueMatches. Consider making it configurable through the tournament settings.


Line range hint 387-422: Add null safety checks for team data.

The code assumes team data exists without proper null checks. This could lead to runtime errors.

- MatchTeamModel team1Data = match.teams.first;
- MatchTeamModel team2Data = match.teams.last;
+ if (match.teams.length < 2) {
+   continue;
+ }
+ final team1Data = match.teams.first;
+ final team2Data = match.teams.last;

736-741: Optimize team ID comparisons.

The code makes multiple calls to access team IDs. Consider caching the values for better performance.

- element.team_ids.contains(p.first.id) &&
- element.team_ids.contains(p.last.id)
+ final elementTeamIds = element.team_ids;
+ elementTeamIds.contains(p.first.id) &&
+ elementTeamIds.contains(p.last.id)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a7f75d3 and df18a8b.

📒 Files selected for processing (1)
  • khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (13 hunks)
🔇 Additional comments (1)
khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (1)

655-673: LGTM! Good extraction of mini-robin matches logic.

The method effectively encapsulates the mini-robin tournament logic, improving code organization.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (4)

657-717: Add documentation for the new helper methods.

While the methods are well-structured, they would benefit from documentation explaining their purpose, parameters, and return values. This is particularly important for complex tournament logic.

+  /// Handles the scheduling of matches in a mini round-robin format.
+  /// 
+  /// Parameters:
+  /// - matches: Current matches in the tournament
+  /// - teamPool: Available teams for scheduling
+  /// - matchesPerTeam: Maximum number of matches per team
+  /// - group: Current match group
+  /// - number: Current round number
   void handleSingleMiniRobinMatches(
       List<MatchModel> matches,
       List<TeamModel> teamPool,
       int matchesPerTeam,
       MatchGroup group,
       int number) {

+  /// Handles the scheduling of matches in a box league format.
+  /// 
+  /// Parameters:
+  /// - allMatches: All matches in the tournament
+  /// - matches: Current matches in the group
+  /// - teamPool: Available teams for scheduling
+  /// - group: Current match group
+  /// - number: Current round number
+  /// - boxSize: Size of each box in the league
   void handleSingleBoxLeague(
     List<MatchModel> allMatches,
     List<MatchModel> matches,
     List<TeamModel> teamPool,
     MatchGroup group,
     int number,
     int boxSize,
   ) {

Line range hint 739-767: Refactor complex match handling logic.

The method has high cyclomatic complexity and mixes multiple responsibilities. Consider splitting it into smaller, more focused methods for better maintainability.

-  for (final p in pair) {
+  for (final p in pair) {
+    _handlePairMatches(p, groupMatches, group, number, winnerTeams);
+  }
+  return winnerTeams;
+}
+
+void _handlePairMatches(
+  List<TeamModel> pair,
+  List<MatchModel> groupMatches,
+  MatchGroup group,
+  int number,
+  List<String> winnerTeams,
+) {
   final matchesForPair = groupMatches
       .where((element) =>
-          element.team_ids.contains(p.first.id) &&
-          element.team_ids.contains(p.last.id))
+          element.team_ids.contains(pair.first.id) &&
+          element.team_ids.contains(pair.last.id))
       .toList();
-  if (matchesForPair.isEmpty) {
-    addMatches(groupMatches, [p], group, number);
-    addMatches(groupMatches, [p], group, number);
-  } else if (matchesForPair.length == 1) {
-    addMatches(groupMatches, [p], group, number);
-  } else {
+
+  _scheduleMatchesForPair(matchesForPair, pair, groupMatches, group, number);
+  _determineWinner(matchesForPair, pair, groupMatches, group, number, winnerTeams);
+}
+
+void _scheduleMatchesForPair(
+  List<MatchModel> matchesForPair,
+  List<TeamModel> pair,
+  List<MatchModel> groupMatches,
+  MatchGroup group,
+  int number,
+) {
+  switch (matchesForPair.length) {
+    case 0:
+      addMatches(groupMatches, [pair], group, number);
+      addMatches(groupMatches, [pair], group, number);
+      break;
+    case 1:
+      addMatches(groupMatches, [pair], group, number);
+      break;
+  }
+}
+
+void _determineWinner(
+  List<MatchModel> matchesForPair,
+  List<TeamModel> pair,
+  List<MatchModel> groupMatches,
+  MatchGroup group,
+  int number,
+  List<String> winnerTeams,
+) {
+  if (matchesForPair.length >= 2) {
     final finishedMatches =
         matchesForPair.where((element) => element.matchResult != null);
     if (finishedMatches.length >= 2) {
       final winner = getWinnerTeam(
         matchesForPair.toList(),
-        p.first.id,
-        p.last.id,
+        pair.first.id,
+        pair.last.id,
       );
       if (winner == null && matchesForPair.length == 2) {
-        addMatches(groupMatches, [p], group, number);
+        addMatches(groupMatches, [pair], group, number);
       } else if (winner != null) {
         winnerTeams.add(winner);
       }
     }
   }
-}

Line range hint 381-423: Optimize team points calculation performance.

The team points calculation has nested loops and redundant null checks. Consider optimizing the logic with early returns and guard clauses.

   for (final match in matches) {
-    if (match.matchResult == null) {
-      continue;
-    }
-    MatchResult result = match.matchResult!;
+    final result = match.matchResult;
+    if (result == null) continue;

     MatchTeamModel team1Data = match.teams.first;
     MatchTeamModel team2Data = match.teams.last;

-    TeamPoints team1Points =
-        teamPoints.firstWhere((team) => team.team.id == team1Data.team_id);
-    TeamPoints team2Points =
-        teamPoints.firstWhere((team) => team.team.id == team2Data.team_id);
+    // Create a map for O(1) lookup instead of O(n) firstWhere
+    final pointsMap = Map.fromEntries(
+      teamPoints.map((tp) => MapEntry(tp.team.id, tp))
+    );
+    
+    final team1Points = pointsMap[team1Data.team_id]!;
+    final team2Points = pointsMap[team2Data.team_id]!;

     // Update stats
     _updateTeamStats(team1Points, team1Data);
     _updateTeamStats(team2Points, team2Data);

     // Update points based on result
     _updatePointsBasedOnResult(result, team1Points, team2Points);
   }

Line range hint 1-811: Consider implementing the Strategy pattern for tournament types.

The current implementation handles different tournament types through switch statements and conditional logic. Consider implementing the Strategy pattern to encapsulate each tournament type's scheduling logic in its own class. This would improve maintainability and make it easier to add new tournament types in the future.

Example structure:

abstract class TournamentScheduler {
  GroupedMatchMap scheduleMatches();
}

class KnockoutTournamentScheduler implements TournamentScheduler {
  @override
  GroupedMatchMap scheduleMatches() {
    // Knockout-specific logic
  }
}

class RoundRobinTournamentScheduler implements TournamentScheduler {
  @override
  GroupedMatchMap scheduleMatches() {
    // Round-robin-specific logic
  }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between df18a8b and c112cce.

📒 Files selected for processing (1)
  • khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (13 hunks)
🔇 Additional comments (1)
khelo/lib/ui/flow/tournament/match_selection/match_scheduler.dart (1)

65-93: ⚠️ Potential issue

Add safeguard against potential infinite loop.

While the refactored code is cleaner, there's a risk of an infinite loop if teamPool is not properly reduced in each iteration. Consider adding a safety check or maximum iteration limit.

 GroupedMatchMap scheduleKnockOutMatches() {
   final GroupedMatchMap additionalScheduledMatches =
       Map.from(scheduledMatches);
   final List<TeamModel> teamPool = List.of(teams);

   MatchGroup currentGroup = MatchGroup.round;
   int currentRound = 1;
+  int maxIterations = 100;  // Reasonable maximum number of iterations
 
   while (teamPool.length > 1) {
+    if (maxIterations-- <= 0) {
+      throw Exception('Maximum iterations reached in scheduleKnockOutMatches');
+    }
     final group =
         additionalScheduledMatches.putIfAbsent(currentGroup, () => {1: []});

@cp-sidhdhi-p cp-sidhdhi-p merged commit d7069f1 into main Nov 26, 2024
2 checks passed
@cp-sidhdhi-p cp-sidhdhi-p deleted the sidhdhi/optimize-match-scheduling branch November 26, 2024 12:06
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.

2 participants