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

⚡ Improved cluster-status #310

Merged
merged 1 commit into from
Oct 3, 2024
Merged

Conversation

abdheshnayak
Copy link
Member

@abdheshnayak abdheshnayak commented Oct 3, 2024

  • improved cluster status fetching ( still needs to use websocket for more better performance)

Summary by Sourcery

Improve cluster status fetching by introducing a new hook useClusterStatusV3 and a ClusterStatusProvider for better performance and real-time updates. This replaces the previous version and enhances the overall efficiency of cluster status management.

New Features:

  • Introduce a new hook useClusterStatusV3 to manage cluster status with improved performance and real-time updates.

Enhancements:

  • Replace the existing cluster status fetching mechanism with a more efficient version using useClusterStatusV3.
  • Add a new ClusterStatusProvider to wrap components and provide cluster status context.

Copy link

sourcery-ai bot commented Oct 3, 2024

Reviewer's Guide by Sourcery

This pull request improves the cluster status fetching mechanism by introducing a new hook useClusterStatusV3 and updating the environment resources view to use this new implementation. The changes aim to enhance performance and provide more accurate cluster status information.

Sequence diagram for Cluster Status Fetching

sequenceDiagram
    actor User
    participant UI
    participant useClusterStatusV3
    participant API
    User->>UI: View Environment Resources
    UI->>useClusterStatusV3: Fetch Cluster Status
    useClusterStatusV3->>API: getClusterStatus(name)
    API-->>useClusterStatusV3: Return lastOnlineAt
    useClusterStatusV3-->>UI: Update Cluster Status
    UI-->>User: Display Updated Status
Loading

File-Level Changes

Change Details Files
Introduced a new cluster status fetching mechanism
  • Created a new hook useClusterStatusV3 for improved cluster status fetching
  • Implemented a new ClusterStatusProvider component
  • Added a new GraphQL query consoleGetClusterStatus for fetching individual cluster status
  • Updated the findClusterStatusv3 function to determine cluster online status
src/apps/console/hooks/use-cluster-status-v3.tsx
src/apps/console/server/gql/queries/cluster-queries.ts
gql-queries-generator/doc/queries.graphql
src/apps/console/hooks/use-cluster-status.tsx
Updated environment resources view to use the new cluster status mechanism
  • Replaced useClusterStatusV2 with useClusterStatusV3 in the ListView component
  • Updated the rendering logic for cluster status badges
  • Implemented debounced logging of cluster status for debugging purposes
src/apps/console/routes/_main+/$account+/environments/environment-resources-v2.tsx
Integrated the new ClusterStatusProvider into the application
  • Wrapped the application with ClusterStatusProvider in the root component
  • Updated imports to include the new ClusterStatusProvider
src/apps/console/root.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@abdheshnayak abdheshnayak merged commit 6138282 into release-v1.0.8 Oct 3, 2024
4 checks passed
@abdheshnayak abdheshnayak deleted the improve/cluster-status branch October 3, 2024 12:50
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @abdheshnayak - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 3 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 2 issues found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

};
});
} catch (e) {
console.log('error', e);
Copy link

Choose a reason for hiding this comment

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

suggestion: Improve error handling in useClusterStatusV3 hook

The current error handling simply logs to the console. Consider implementing more robust error handling, such as updating the UI to reflect the error state or retrying the request.

          setError(e);
          if (retryCount < 3) {
            setTimeout(() => fetchData(retryCount + 1), 1000 * (retryCount + 1));
          } else {
            notifyError('Failed to fetch cluster status', e);
          }

[key: string]: number;
}>({});

const addToWatchList = (clusterNames: string[]) => {
Copy link

Choose a reason for hiding this comment

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

suggestion: Simplify watchlist management logic

The addToWatchList and removeFromWatchList functions are quite complex. Consider simplifying this logic, possibly by using a Set instead of a counter-based system. This would make the code more readable and potentially more efficient.

const watchList = new Set<string>();

const addToWatchList = (clusterNames: string[]) => {
  clusterNames.forEach(name => watchList.add(name));
  setWatchList(new Set(watchList));
};

}
};

useEffect(() => {
Copy link

Choose a reason for hiding this comment

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

suggestion: Extract polling logic into a custom hook

Consider extracting the polling logic in the effect hook into a custom hook. This would improve reusability and separation of concerns. For example, you could create a usePolling hook that encapsulates the setTimeout and setInterval logic.

const usePolling = (callback, delay) => {
  useEffect(() => {
    const timer = setInterval(callback, delay);
    return () => clearInterval(timer);
  }, [callback, delay]);
};

usePolling(() => {
  caller(watchList);
}, 5000);

@@ -201,18 +203,18 @@ const GridView = ({ items = [], onAction }: IResource) => {

const ListView = ({ items, onAction }: IResource) => {
const { account } = useParams();
const { clusters } = useClusterStatusV2();
// const { clusters } = useClusterStatusV2();
const { clusters: clusterStatus } = useClusterStatusV3({
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider refactoring the cluster status rendering logic into a separate function.

The changes introduce unnecessary complexity in the cluster status rendering logic. While the new useClusterStatusV3 hook might offer benefits, the implementation can be simplified. Here's a suggestion to refactor the code:

const ListView = ({ items, onAction }: IResource) => {
  const { account } = useParams();
  const { clusters: clusterStatus } = useClusterStatusV3({
    clusterNames: items.map((i) => i.clusterName),
  });

  const renderClusterStatus = (item) => {
    if (item.clusterName === '') {
      return <ListItemV2 className="px-4xl" data="-" />;
    }
    if (item.isArchived) {
      return <Badge type="neutral">Archived</Badge>;
    }
    if (item.spec?.suspend) {
      return <Badge type="neutral">Suspended</Badge>;
    }

    const status = clusterStatus[item.clusterName];
    if (status === undefined) {
      return <LoadingSpinner />;
    }
    if (!findClusterStatusv3(status)) {
      return <Badge type="warning">Cluster Offline</Badge>;
    }
    return <SyncStatusV2 item={item} />;
  };

  // ... rest of the component

  return (
    <ListV2.Root
      // ... other props
      data={{
        // ... other data
        rows: items.map((i) => {
          const { name, id, updateInfo } = parseItem(i);
          const isClusterOnline = findClusterStatusv3(clusterStatus[i.clusterName]);

          return {
            columns: {
              // ... other columns
              status: {
                render: () => renderClusterStatus(i),
              },
              // ... other columns
            },
            // ... other properties
          };
        }),
      }}
    />
  );
};

const LoadingSpinner = () => (
  <div className="cursor-pointer w-fit">
    <span className="animate-spin relative flex items-center justify-center text-text-warning">
      <CircleNotch size={12} />
      <span className="absolute">
        <CircleFill size={8} />
      </span>
    </span>
  </div>
);

This refactoring:

  1. Extracts the cluster status rendering logic into a separate function renderClusterStatus, improving readability.
  2. Simplifies the conditional logic by using early returns.
  3. Moves the loading spinner into a separate component for reusability.
  4. Maintains all the new functionality while reducing nesting and complexity.

These changes make the code more maintainable and easier to understand while preserving the improved handling of cluster statuses.

removeFromWatchList: () => {},
});

const ClusterStatusProvider = ({ children }: ChildrenProps) => {
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider simplifying complex code sections to improve readability and maintainability.

While the complexity in this file may be justified for performance reasons, there are several areas where we can simplify the code without losing functionality:

  1. Simplify watchlist management:
    The addToWatchList and removeFromWatchList functions can be simplified using the spread operator and Object.fromEntries(). For example:

    const addToWatchList = (clusterNames: string[]) => {
      setWatchList(prevList => ({
        ...prevList,
        ...Object.fromEntries(clusterNames.filter(Boolean).map(name => [name, (prevList[name] || 0) + 1]))
      }));
    };
    
    const removeFromWatchList = (clusterNames: string[]) => {
      setWatchList(prevList => {
        const newList = { ...prevList };
        clusterNames.filter(Boolean).forEach(name => {
          if (newList[name]) {
            newList[name]--;
            if (newList[name] === 0) delete newList[name];
          }
        });
        return newList;
      });
    };
  2. Refactor the caller function:
    Replace the for loop with Promise.all() to improve readability and potentially performance:

    const caller = async (wl: { [key: string]: number }) => {
      const keys = Object.keys(wl);
      await Promise.all(keys.map(async (w) => {
        try {
          const { data: cluster } = await api.getClusterStatus({ name: w });
          setClusters(s => ({ ...s, [w]: cluster.lastOnlineAt }));
        } catch (e) {
          console.error('Error fetching cluster status:', e);
        }
      }));
    };
  3. Reconsider timer logic:
    The current implementation uses both setTimeout and setInterval. Consider using a single setInterval with error handling:

    useEffect(() => {
      const fetchData = async () => {
        await caller(watchList);
      };
    
      fetchData(); // Initial call
      const intervalId = setInterval(fetchData, 30 * 1000);
    
      return () => clearInterval(intervalId);
    }, [watchList]);

These changes will reduce complexity while maintaining the core functionality. Consider adding comments to explain the rationale behind the remaining complex parts, especially around the watchlist concept and the use of React context for state management.

Comment on lines +39 to +52
const resp = clusterNames.reduce((acc, curr) => {
if (!curr) {
return acc;
}
if (acc[curr]) {
acc[curr] += acc[curr];
} else {
acc[curr] = 1;
}

return acc;
}, s);

return resp;
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const resp = clusterNames.reduce((acc, curr) => {
if (!curr) {
return acc;
}
if (acc[curr]) {
acc[curr] += acc[curr];
} else {
acc[curr] = 1;
}
return acc;
}, s);
return resp;
return clusterNames.reduce((acc, curr) => {
if (!curr) {
return acc;
}
if (acc[curr]) {
acc[curr] += acc[curr];
} else {
acc[curr] = 1;
}
return acc;
}, s);


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

Comment on lines +108 to +124
const resp = clusterNames.reduce((acc, curr) => {
if (!curr) {
return acc;
}

if (acc[curr] && acc[curr] >= 1) {
acc[curr] -= acc[curr];
}

if (acc[curr] === 0) {
delete acc[curr];
}

return acc;
}, s);

return resp;
Copy link

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const resp = clusterNames.reduce((acc, curr) => {
if (!curr) {
return acc;
}
if (acc[curr] && acc[curr] >= 1) {
acc[curr] -= acc[curr];
}
if (acc[curr] === 0) {
delete acc[curr];
}
return acc;
}, s);
return resp;
return clusterNames.reduce((acc, curr) => {
if (!curr) {
return acc;
}
if (acc[curr] && acc[curr] >= 1) {
acc[curr] -= acc[curr];
}
if (acc[curr] === 0) {
delete acc[curr];
}
return acc;
}, s);


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

abdheshnayak added a commit that referenced this pull request Oct 28, 2024
tulsiojha pushed a commit that referenced this pull request Nov 1, 2024
abdheshnayak added a commit that referenced this pull request Nov 5, 2024
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.

1 participant