Repository: meshery/meshery
Pull request #14508: remove routing for workspace modal
Author: login: amitamrutiya
Labels: name: component/ui
description: User Interface
Created: 2025-04-24T04:55:53Z | Merged: 2025-04-24T06:17:54Z

**Notes for Reviewers**

- This PR fixes #

**[Signed commits](https://github.com/meshery/meshery/blob/master/CONTRIBUTING.md#signing-off-on-commits-developer-certificate-of-origin)**
- [ ] Yes, I signed my commits.

<!--
Thank you for contributing to Meshery! 

Contributing Conventions:

1. Include descriptive PR titles with [<component-name>] prepended.
2. Build and test your changes before submitting a PR. 
3. Sign your commits.
4. Include before and after screenshots/terminal output.

By following the community's contribution conventions upfront, the review process will 
be accelerated and your PR merged more quickly.
-->



Diff:
diff --git a/ui/components/Lifecycle/Workspaces/WorkspaceDataTable.js b/ui/components/Lifecycle/Workspaces/WorkspaceDataTable.js
index 6a8386ad8087..678e0776a6ca 100644
--- a/ui/components/Lifecycle/Workspaces/WorkspaceDataTable.js
+++ b/ui/components/Lifecycle/Workspaces/WorkspaceDataTable.js
@@ -26,7 +26,6 @@ import {
   Slide,
 } from '@layer5/sistent';
 import { useLegacySelector } from 'lib/store';
-import { useRouter } from 'next/router';
 import { useEffect, useState } from 'react';
 import { iconSmall } from 'css/icons.styles';
 import WorkSpaceContentDataTable from './WorkSpaceContentDataTable';
@@ -42,6 +41,7 @@ const WorkspaceDataTable = ({
   handleRowClick,
   setColumnVisibility,
   search,
+  viewType,
 }) => {
   let colViews = [
     ['id', 'na'],
@@ -59,8 +59,7 @@ const WorkspaceDataTable = ({
   const [pageSize, setPageSize] = useState(10);
   const [sortOrder, setSortOrder] = useState('updated_at desc');
   const org_id = useLegacySelector((state) => state.get('organization'))?.id;
-  const router = useRouter();
-  const viewType = router.query.view === 'table' ? 'table' : 'grid';
+
   const theme = useTheme();
 
   const { data: workspaces } = useGetWorkspacesQuery({
@@ -249,7 +248,10 @@ const WorkspaceDataTable = ({
       },
     },
   ];
+
+  // Window dimensions for responsive column visibility
   const { width } = useWindowDimensions();
+
   useEffect(() => {
     let showCols = updateVisibleColumns(colViews, width);
     const initialVisibility = {};
@@ -257,7 +259,7 @@ const WorkspaceDataTable = ({
       initialVisibility[col.name] = showCols[col.name];
     });
     setColumnVisibility(initialVisibility);
-  }, []);
+  }, [width]);
 
   const options = {
     filter: false,
diff --git a/ui/components/Lifecycle/Workspaces/index.js b/ui/components/Lifecycle/Workspaces/index.js
index f9d25a72ef22..2c11adc401ed 100644
--- a/ui/components/Lifecycle/Workspaces/index.js
+++ b/ui/components/Lifecycle/Workspaces/index.js
@@ -21,7 +21,7 @@ import {
 } from '@layer5/sistent';
 import { EmptyState } from '../General';
 import AddIconCircleBorder from '../../../assets/icons/AddIconCircleBorder';
-import { useEffect, useRef, useState } from 'react';
+import { useContext, useRef, useState } from 'react';
 import {
   useAssignTeamToWorkspaceMutation,
   useCreateWorkspaceMutation,
@@ -47,9 +47,9 @@ import { CreateButtonWrapper } from './styles';
 import WorkspaceGridView from './WorkspaceGridView';
 import RightArrowIcon from '@/assets/icons/RightArrowIcon';
 import { useGetUsersForOrgQuery, useRemoveUserFromTeamMutation } from '@/rtk-query/user';
-import { useRouter } from 'next/router';
 import WorkspaceDataTable from './WorkspaceDataTable';
 import { iconMedium } from 'css/icons.styles';
+import { WorkspaceSwitcherContext } from '@/components/SpacesSwitcher/WorkspaceSwitcher';
 
 export const WORKSPACE_ACTION_TYPES = {
   CREATE: 'create',
@@ -105,7 +105,6 @@ const columnList = [
 
 const Workspaces = () => {
   const theme = useTheme();
-  const router = useRouter();
   const [workspaceModal, setWorkspaceModal] = useState({
     open: false,
     schema: {},
@@ -119,19 +118,16 @@ const Workspaces = () => {
   const [actionType, setActionType] = useState('');
   const [initialData, setInitialData] = useState({});
   const [editWorkspaceId, setEditWorkspaceId] = useState('');
-  const [selectedWorkspace, setSelectedWorkspace] = useState({
-    id: router.query.id || '',
-    name: router.query.name || '',
+  let [selectedWorkspace, setSelectedWorkspace] = useState({
+    id: '',
+    name: '',
   });
-
-  useEffect(() => {
-    if (router.query.id) {
-      setSelectedWorkspace({
-        id: router.query.id,
-        name: router.query.name,
-      });
-    }
-  }, [router.query.id, router.query.name]);
+  const workspaceSwitcherContext = useContext(WorkspaceSwitcherContext);
+  if (workspaceSwitcherContext.selectedWorkspace.id) {
+    selectedWorkspace = workspaceSwitcherContext.selectedWorkspace;
+    setSelectedWorkspace = workspaceSwitcherContext.setSelectedWorkspace;
+  }
+  const [viewType, setViewType] = useState(selectedWorkspace.id ? 'table' : 'grid');
 
   const [teamsModal, setTeamsModal] = useState({
     open: false,
@@ -151,14 +147,6 @@ const Workspaces = () => {
       id: workspaceId,
       name: workspaceName,
     });
-    router.push(
-      {
-        pathname: router.pathname,
-        query: { ...router.query, view: 'table', id: workspaceId, name: workspaceName },
-      },
-      undefined,
-      { shallow: true },
-    );
   };
   const ref = useRef(null);
   const bulkDeleteRef = useRef(null);
@@ -197,7 +185,6 @@ const Workspaces = () => {
       .catch((error) => handleError(`Workspace Create Error: ${error?.data}`));
     handleWorkspaceModalClose();
   };
-  const viewType = router.query.view === 'table' ? 'table' : 'grid';
 
   const handleEditWorkspace = ({ organization, name, description }) => {
     updateWorkspace({
@@ -363,15 +350,7 @@ const Workspaces = () => {
 
     setPage(0);
     setSelectedWorkspace({ id: '', name: '' });
-
-    router.push(
-      {
-        pathname: router.pathname,
-        query: { ...router.query, view: val },
-      },
-      undefined,
-      { shallow: true },
-    );
+    setViewType(val);
   };
 
   const [columnVisibility, setColumnVisibility] = useState({});
@@ -489,6 +468,7 @@ const Workspaces = () => {
                 selectedWorkspace={selectedWorkspace}
                 setColumnVisibility={setColumnVisibility}
                 search={search}
+                viewType={viewType}
               />
             )}
           </>
diff --git a/ui/components/SpacesSwitcher/WorkspaceSwitcher.js b/ui/components/SpacesSwitcher/WorkspaceSwitcher.js
index e46c85cda4cf..a4b6d1cab760 100644
--- a/ui/components/SpacesSwitcher/WorkspaceSwitcher.js
+++ b/ui/components/SpacesSwitcher/WorkspaceSwitcher.js
@@ -18,7 +18,6 @@ import { useLegacySelector } from '../../lib/store';
 import { StyledSelect } from './SpaceSwitcher';
 import { useGetWorkspacesQuery } from '@/rtk-query/workspace';
 import { iconMedium } from 'css/icons.styles';
-import { useRouter } from 'next/router';
 
 export const HoverMenuItem = styled(MenuItem)(() => ({
   display: 'flex',
@@ -47,11 +46,14 @@ export const WorkspaceSwitcherContext = React.createContext({
   open: false,
   openModal: () => {},
   closeModal: () => {},
+  selectedWorkspace: { id: '', name: '' },
+  setSelectedWorkspace: () => {},
 });
 
 function WorkspaceSwitcher({ open }) {
-  const [defaultWorkspace, setDefaultWorkspace] = useState(null);
+  const [_defaultWorkspace, setDefaultWorkspace] = useState(null);
   const [workspaceModal, setWorkspaceModal] = useState(false);
+  const [selectedWorkspace, setSelectedWorkspace] = useState({ id: '', name: '' });
   const orgId = useLegacySelector((state) => state.get('organization'))?.id;
   const { data: workspacesData, isError: isWorkspacesError } = useGetWorkspacesQuery(
     {
@@ -65,6 +67,7 @@ function WorkspaceSwitcher({ open }) {
       skip: !orgId ? true : false,
     },
   );
+
   useEffect(() => {
     if (workspacesData && workspacesData.workspaces?.length > 0) {
       const defaultWorkspace = workspacesData.workspaces[0];
@@ -72,8 +75,6 @@ function WorkspaceSwitcher({ open }) {
     }
   }, [workspacesData]);
 
-  const router = useRouter();
-
   const handleChangeWorkspace = (e) => {
     if (!workspacesData || !workspacesData.workspaces) return;
 
@@ -81,21 +82,10 @@ function WorkspaceSwitcher({ open }) {
       (workspace) => workspace.id === e.target.value,
     );
     setDefaultWorkspace(selectedWorkspace);
+    setSelectedWorkspace({ id: selectedWorkspace.id, name: selectedWorkspace.name });
     setWorkspaceModal(true);
-    router.push(
-      {
-        pathname: router.pathname,
-        query: {
-          ...router.query,
-          view: 'table',
-          id: selectedWorkspace.id,
-          name: selectedWorkspace.name,
-        },
-      },
-      undefined,
-      { shallow: true },
-    );
   };
+
   const theme = useTheme();
   return (
     <NoSsr>
@@ -105,10 +95,9 @@ function WorkspaceSwitcher({ open }) {
           openModal: () => setWorkspaceModal(true),
           closeModal: () => {
             setWorkspaceModal(false);
-            if (router.query.id) {
-              router.back();
-            }
           },
+          selectedWorkspace,
+          setSelectedWorkspace,
         }}
       >
         {!isWorkspacesError && workspacesData && workspacesData.workspaces && (
@@ -128,9 +117,9 @@ function WorkspaceSwitcher({ open }) {
                       <Grid item xs={12} data-cy="mesh-adapter-url">
                         <StyledSelect
                           size="small"
-                          value={defaultWorkspace?.id || ''}
+                          value={_defaultWorkspace?.id || ''}
                           onChange={(e) => {
-                            if (e.target.value !== defaultWorkspace?.id) {
+                            if (e.target.value !== _defaultWorkspace?.id) {
                               handleChangeWorkspace(e); // only call for new selection
                             }
                           }}
@@ -157,7 +146,7 @@ function WorkspaceSwitcher({ open }) {
                               key={works.id}
                               value={works.id}
                               onClick={() => {
-                                if (works.id === defaultWorkspace?.id) {
+                                if (works.id === _defaultWorkspace?.id) {
                                   handleChangeWorkspace({ target: { value: works.id } });
                                 }
                               }}
@@ -180,9 +169,6 @@ function WorkspaceSwitcher({ open }) {
         <Modal
           closeModal={() => {
             setWorkspaceModal(false);
-            if (router.query.id) {
-              router.back();
-            }
           }}
           open={workspaceModal}
           maxWidth="xl"


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @amitamrutiya, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
This pull request focuses on removing routing logic related to the workspace modal and integrating the `WorkspaceSwitcherContext` to manage workspace selection and view type. The changes primarily affect `WorkspaceDataTable.js` and `index.js` (under `ui/components/Lifecycle/Workspaces/`), as well as `WorkspaceSwitcher.js` (under `ui/components/SpacesSwitcher/`). The goal is to streamline workspace handling and eliminate the need for URL-based routing for workspace selection.

### Highlights
* **Routing Removal**: Removes `next/router` usage in `WorkspaceDataTable.js` and `index.js`, eliminating URL-based routing for workspace selection.
* **Context Integration**: Integrates `WorkspaceSwitcherContext` in `WorkspaceDataTable.js` and `index.js` to manage the selected workspace and view type.
* **State Management**: Uses `WorkspaceSwitcherContext` to manage the selected workspace and view type, replacing the previous reliance on URL parameters.
* **Component Updates**: Updates `WorkspaceDataTable` to receive `viewType` as a prop and removes the internal logic for determining it from the router.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **ui/components/Lifecycle/Workspaces/WorkspaceDataTable.js**
  * Replaces `useRouter` with `useContext` to access `WorkspaceSwitcherContext`.
  * Removes logic related to reading the view type from the router query.
  * Adds `viewType` as a prop.
  * Updates the `useEffect` hook that sets column visibility to depend on `width`.
* **ui/components/Lifecycle/Workspaces/index.js**
  * Replaces `useRouter` with `useContext` to access `WorkspaceSwitcherContext`.
  * Removes logic related to reading workspace ID and name from the router query.
  * Initializes `selectedWorkspace` state and updates it using the context.
  * Removes `router.push` calls that were used to update the URL.
  * Adds `viewType` state and uses it to determine the current view.
  * Passes `viewType` as a prop to `WorkspaceDataTable`.
* **ui/components/SpacesSwitcher/WorkspaceSwitcher.js**
  * Removes `useRouter` import.
  * Adds `selectedWorkspace` and `setSelectedWorkspace` to the `WorkspaceSwitcherContext` value.
  * Updates `handleChangeWorkspace` to set the selected workspace in the context.
  * Removes `router.push` calls in `handleChangeWorkspace` and `closeModal`.
  * Renames `defaultWorkspace` to `_defaultWorkspace` to avoid naming conflict with context value.
  * Adds logic to set the selected workspace in the context when a workspace is selected.

</details>


<details>

<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.

</details>

<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

> The router's gone away,
> Context takes its place today,
> State is managed well,
> No URLs to tell,
> A cleaner, simpler way.


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T04:56:03Z
user: login: gemini-code-assist[bot]
body: ## Code Review
The pull request aims to remove routing logic for the workspace modal and leverage the `WorkspaceSwitcherContext` for managing workspace selection. This is a good step towards centralizing state management. However, there are a few areas that could be improved for clarity and efficiency.

### Summary of Findings
* **Redundant State Updates**: In `Workspaces.js`, the `setSelectedWorkspace` state update within the `workspaceSwitcherContext.selectedWorkspace.id` condition seems redundant, as the context itself should handle the state update. This could lead to unnecessary re-renders and potential inconsistencies.
* **Inconsistent Naming**: In `WorkspaceSwitcher.js`, there's a mix of using `defaultWorkspace` and `_defaultWorkspace`. It's important to maintain consistency in naming conventions to avoid confusion.
* **Missing Dependency in useEffect**: In `WorkspaceDataTable.js`, the `useEffect` hook that updates column visibility depends on `width`. While `width` is now correctly included in the dependency array, it's crucial to ensure that `updateVisibleColumns` function is optimized to handle frequent updates to avoid performance issues.
* **Potential Performance Bottleneck**: The `WorkspaceSwitcherContext` is being used to manage the selected workspace. Ensure that components consuming this context are optimized to prevent unnecessary re-renders when the selected workspace changes.

### Merge Readiness
The pull request is not quite ready for merging. The redundant state updates and potential performance bottlenecks should be addressed before merging. I am unable to directly approve the pull request, and other reviewers should review and approve this code before merging.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T04:56:20Z
user: login: aabidsofi19
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T11:10:38Z
user: login: amitamrutiya
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T11:15:26Z

Review comments:
diff_hunk: @@ -119,19 +118,16 @@ const Workspaces = () => {
   const [actionType, setActionType] = useState('');
   const [initialData, setInitialData] = useState({});
   const [editWorkspaceId, setEditWorkspaceId] = useState('');
-  const [selectedWorkspace, setSelectedWorkspace] = useState({
-    id: router.query.id || '',
-    name: router.query.name || '',
+  let [selectedWorkspace, setSelectedWorkspace] = useState({
+    id: '',
+    name: '',
   });
-
-  useEffect(() => {
-    if (router.query.id) {
-      setSelectedWorkspace({
-        id: router.query.id,
-        name: router.query.name,
-      });
-    }
-  }, [router.query.id, router.query.name]);
+  const workspaceSwitcherContext = useContext(WorkspaceSwitcherContext);
+  if (workspaceSwitcherContext.selectedWorkspace.id) {
+    selectedWorkspace = workspaceSwitcherContext.selectedWorkspace;
+    setSelectedWorkspace = workspaceSwitcherContext.setSelectedWorkspace;
+  }
path: ui/components/Lifecycle/Workspaces/index.js
user: login: aabidsofi19
body: why are we making state a variable , and not using the setting . this will cause bugs and wrong behavior
created_at: 2025-04-24T11:10:18Z
author_association: MEMBER
diff_hunk: @@ -119,19 +118,16 @@ const Workspaces = () => {
   const [actionType, setActionType] = useState('');
   const [initialData, setInitialData] = useState({});
   const [editWorkspaceId, setEditWorkspaceId] = useState('');
-  const [selectedWorkspace, setSelectedWorkspace] = useState({
-    id: router.query.id || '',
-    name: router.query.name || '',
+  let [selectedWorkspace, setSelectedWorkspace] = useState({
+    id: '',
+    name: '',
   });
-
-  useEffect(() => {
-    if (router.query.id) {
-      setSelectedWorkspace({
-        id: router.query.id,
-        name: router.query.name,
-      });
-    }
-  }, [router.query.id, router.query.name]);
+  const workspaceSwitcherContext = useContext(WorkspaceSwitcherContext);
+  if (workspaceSwitcherContext.selectedWorkspace.id) {
+    selectedWorkspace = workspaceSwitcherContext.selectedWorkspace;
+    setSelectedWorkspace = workspaceSwitcherContext.setSelectedWorkspace;
+  }
path: ui/components/Lifecycle/Workspaces/index.js
user: login: amitamrutiya
body: I made this because we don't have context setup if we are not come from modal and directly access the workspace page. Still this is wrong behavior. I'll improve logic for that.
created_at: 2025-04-24T11:15:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-24T04:59:24Z
body: <!-- meshmap-snapshot: do not delete/edit this line -->
[<img src="https://raw.githubusercontent.com/layer5labs/meshery-extensions-packages/master/action-assets/2025@4/dea82811-74ba-474f-b9e4-426e7401707elHws0nI=.png">](https://raw.githubusercontent.com/layer5labs/meshery-extensions-packages/master/action-assets/2025@4/dea82811-74ba-474f-b9e4-426e7401707elHws0nI=.png) _[View in catalog](https://cloud.layer5.io/catalog/content/design/6352d5eb-0048-458b-a356-1d0bf41296e7), [Edit in playground](https://playground.meshery.io/extension/meshmap?mode=design&design=6352d5eb-0048-458b-a356-1d0bf41296e7) or learn how to [interpret Meshery Designs](https://cloud.layer5.io/academy/learning-paths/mastering-meshery/introduction-to-meshery?chapter=interpreting-meshery-designs)_
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-24T06:36:34Z
body:         
  **Commit SHA:** `d2849cd11b23322e77b9773de5934b000554031c`  

  ### END-TO-END TESTS

- Testing started at: April 24th 2025, 6:29:57 am

**📦 Test Result Summary**

- ✅ 59 passed
- ❌ 1 failed
- ⚠️ 2 flaked
- ⏩ 1 skipped

⌛ _Duration: 6 minutes and 18 seconds_

**Overall Result**: 👎 Some tests failed.



<details>
    <summary>[Show/Hide] Test Result Details</summary>
    <div markdown="1">

| Test | Browser | Test Case | Tags | Result |
| :---: | :---: | :--- | :---: | :---: |
| 1 | chromium-meshery-provider | Transition to disconnected state and then back to connected state | unstable | ⚠️ |
| 2 | chromium-meshery-provider | Transition to ignored state and then back to connected state | unstable | ⚠️ |
| 3 | chromium-meshery-provider | Transition to not found state and then back to connected state | unstable | ⚠️ |
| 4 | chromium-meshery-provider | Delete Kubernetes cluster connections | unstable | ⚠️ |
| 5 | chromium-local-provider | Add a cluster connection by uploading kubeconfig file | unstable | ⚠️ |
| 6 | chromium-local-provider | Transition to disconnected state and then back to connected state | unstable | ⚠️ |
| 7 | chromium-local-provider | Transition to ignored state and then back to connected state | unstable | ⚠️ |
| 8 | chromium-local-provider | Transition to not found state and then back to connected state | unstable | ⚠️ |
| 9 | chromium-local-provider | Delete Kubernetes cluster connections | unstable | ⚠️ |
| 10 | chromium-local-provider | Compare test of a performance profile with load generator &quot;fortio&quot; and service mesh &quot;None&quot; |  | ❌ |
| 11 | chromium-local-provider | Delete a performance profile with load generator &quot;fortio&quot; and service mesh &quot;None&quot; |  | ➖ |

</div>
</details>


<!-- To see the full report, please visit our CI/CD pipeline with reporter. -->
  
author_association: CONTRIBUTOR