From 8c738619629f08ffaf4d81cb4d73c06d3104d409 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 20 Nov 2020 14:53:28 +0300 Subject: [PATCH] Open last active cluster after switching workspaces (#1444) * Save and restore lastActiveClusterId Signed-off-by: Alex Andreev * Activate clusters from workspaces page Signed-off-by: Alex Andreev * Fix saving last cluster while jumping from tray Signed-off-by: Alex Andreev * Adding workspace switch tests Signed-off-by: Alex Andreev * Remove console.log() Signed-off-by: Alex Andreev * Cleaning up Signed-off-by: Alex Andreev * Clean duplicated ClusterId definition Signed-off-by: Alex Andreev * Moving lastActiveClusterId field into WorkspaceModel Signed-off-by: Alex Andreev * fix extensionLoader error on dev environments where renderer might start early (#1447) Signed-off-by: Jari Kolehmainen * Add Search by Ip to Pod View (#1445) Signed-off-by: Pavel Ashevskii * Make BaseStore abstract (#1431) * make BaseStore abstract so that implementers are forced to decide how to store data Signed-off-by: Sebastian Malton * Enforce semicolons in eslint Signed-off-by: Panu Horsmalahti * Add a few missing folders to be linted. Signed-off-by: Panu Horsmalahti * Use @typescript-eslint/semi. Signed-off-by: Panu Horsmalahti * Allow extension cluster menus to have a parent (#1452) Signed-off-by: Jari Kolehmainen * fix SwitchCase indent rule in eslint (#1454) Signed-off-by: Sebastian Malton * Revert "fix SwitchCase indent rule in eslint (#1454)" This reverts commit 082774fe6eb7158db55389c786f8c9be89f66290. * Revert "Allow extension cluster menus to have a parent (#1452)" This reverts commit 622c45cd6d532a3e3ff3c49c7724bc0573ddb264. * Revert "Use @typescript-eslint/semi." This reverts commit 890fa5dd9e238dd173e7069c5b4a35e5c4967524. * Revert "Add a few missing folders to be linted." This reverts commit c7b24c29224646bdf3f0ee8a6c37db3c54694a02. * Revert "Enforce semicolons in eslint" This reverts commit ca67caea6097137c304d0a8f7737a34bacdeacf1. * Revert "Make BaseStore abstract (#1431)" This reverts commit 4b56ab7c611f6848de720e47b3b4f3f7f7433511. * Revert "Add Search by Ip to Pod View (#1445)" This reverts commit 4079214dc15c9e7fed8eb7b64bf021cc19a62b7f. * Revert "fix extensionLoader error on dev environments where renderer might start early (#1447)" This reverts commit 8a3613ac6f4aa9b8686a4a08abce6f351ffeed04. * Split workspace tests to smaller ones Signed-off-by: Alex Andreev * Missing semicolons Signed-off-by: Alex Andreev * Split workspace tests a bit more Signed-off-by: Alex Andreev * Adding extra click in Add Cluster button Signed-off-by: Alex Andreev * Adding more awaits to check running cluster Signed-off-by: Alex Andreev * Wait for minikube before running tests Signed-off-by: Alex Andreev Co-authored-by: Jari Kolehmainen Co-authored-by: pashevskii <53330707+pashevskii@users.noreply.github.com> Co-authored-by: Sebastian Malton Co-authored-by: Panu Horsmalahti --- integration/__tests__/app.tests.ts | 83 ++++++++++++++----- src/common/__tests__/cluster-store.test.ts | 1 + src/common/cluster-store.ts | 9 +- src/common/workspace-store.ts | 16 +++- src/main/tray.ts | 1 - .../components/+workspaces/workspace-menu.tsx | 17 +++- .../components/+workspaces/workspaces.tsx | 9 +- .../cluster-manager/clusters-menu.tsx | 1 + 8 files changed, 106 insertions(+), 31 deletions(-) diff --git a/integration/__tests__/app.tests.ts b/integration/__tests__/app.tests.ts index ace852904d..c91897ef27 100644 --- a/integration/__tests__/app.tests.ts +++ b/integration/__tests__/app.tests.ts @@ -35,6 +35,29 @@ describe("Lens integration tests", () => { await app.client.waitUntilTextExists("h1", "Welcome"); }; + const minikubeReady = (): boolean => { + // determine if minikube is running + let status = spawnSync("minikube status", { shell: true }); + if (status.status !== 0) { + console.warn("minikube not running"); + return false; + } + + // Remove TEST_NAMESPACE if it already exists + status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true }); + if (status.status === 0) { + console.warn(`Removing existing ${TEST_NAMESPACE} namespace`); + status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true }); + if (status.status !== 0) { + console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`); + return false; + } + console.log(status.stdout.toString()); + } + return true; + }; + const ready = minikubeReady(); + describe("app start", () => { beforeAll(appStart, 20000); @@ -73,28 +96,48 @@ describe("Lens integration tests", () => { }); }); - const minikubeReady = (): boolean => { - // determine if minikube is running - let status = spawnSync("minikube status", { shell: true }); - if (status.status !== 0) { - console.warn("minikube not running"); - return false; - } + describeif(ready)("workspaces", () => { + beforeAll(appStart, 20000); - // Remove TEST_NAMESPACE if it already exists - status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true }); - if (status.status === 0) { - console.warn(`Removing existing ${TEST_NAMESPACE} namespace`); - status = spawnSync(`minikube kubectl -- delete namespace ${TEST_NAMESPACE}`, { shell: true }); - if (status.status !== 0) { - console.warn(`Error removing ${TEST_NAMESPACE} namespace: ${status.stderr.toString()}`); - return false; + afterAll(async () => { + if (app && app.isRunning()) { + return util.tearDown(app); } - console.log(status.stdout.toString()); - } - return true; - }; - const ready = minikubeReady(); + }); + + it('creates new workspace', async () => { + await clickWhatsNew(app); + await app.client.click('#current-workspace .Icon'); + await app.client.click('a[href="/workspaces"]'); + await app.client.click('.Workspaces button.Button'); + await app.client.keys("test-workspace"); + await app.client.click('.Workspaces .Input.description input'); + await app.client.keys("test description"); + await app.client.click('.Workspaces .workspace.editing .Icon'); + await app.client.waitUntilTextExists(".workspace .name a", "test-workspace"); + }); + + it('adds cluster in default workspace', async () => { + await addMinikubeCluster(app); + await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started"); + await app.client.waitForExist(`iframe[name="minikube"]`); + await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active"); + }); + + it('adds cluster in test-workspace', async () => { + await app.client.click('#current-workspace .Icon'); + await app.client.click('.WorkspaceMenu li[title="test description"]'); + await addMinikubeCluster(app); + await app.client.waitUntilTextExists("pre.kube-auth-out", "Authentication proxy started"); + await app.client.waitForExist(`iframe[name="minikube"]`); + }); + + it('checks if default workspace has active cluster', async () => { + await app.client.click('#current-workspace .Icon'); + await app.client.click('.WorkspaceMenu > li:first-of-type'); + await app.client.waitForVisible(".ClustersMenu .ClusterIcon.active"); + }); + }); const addMinikubeCluster = async (app: Application) => { await app.client.click("div.add-cluster"); diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 693c2e352c..f0e03b1b12 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -65,6 +65,7 @@ describe("empty config", () => { it("sets active cluster", () => { clusterStore.setActive("foo"); expect(clusterStore.active.id).toBe("foo"); + expect(workspaceStore.currentWorkspace.lastActiveClusterId).toBe("foo"); }); }); diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 24c89d7ca9..4a5f358571 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -1,4 +1,4 @@ -import type { WorkspaceId } from "./workspace-store"; +import { workspaceStore } from "./workspace-store"; import path from "path"; import { app, ipcRenderer, remote, webFrame } from "electron"; import { unlink } from "fs-extra"; @@ -11,9 +11,10 @@ import { appEventBus } from "./event-bus"; import { dumpConfigYaml } from "./kube-helpers"; import { saveToAppFiles } from "./utils/saveToAppFiles"; import { KubeConfig } from "@kubernetes/client-node"; +import { subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; import _ from "lodash"; import move from "array-move"; -import { subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc"; +import type { WorkspaceId } from "./workspace-store"; export interface ClusterIconUpload { clusterId: string; @@ -142,7 +143,9 @@ export class ClusterStore extends BaseStore { @action setActive(id: ClusterId) { - this.activeCluster = this.clusters.has(id) ? id : null; + const clusterId = this.clusters.has(id) ? id : null; + this.activeCluster = clusterId; + workspaceStore.setLastActiveClusterId(clusterId); } @action diff --git a/src/common/workspace-store.ts b/src/common/workspace-store.ts index 67e1c4d22a..75ab36f19e 100644 --- a/src/common/workspace-store.ts +++ b/src/common/workspace-store.ts @@ -5,12 +5,13 @@ import { clusterStore } from "./cluster-store"; import { appEventBus } from "./event-bus"; import { broadcastMessage } from "../common/ipc"; import logger from "../main/logger"; +import type { ClusterId } from "./cluster-store"; export type WorkspaceId = string; export interface WorkspaceStoreModel { + workspaces: WorkspaceModel[]; currentWorkspace?: WorkspaceId; - workspaces: WorkspaceModel[] } export interface WorkspaceModel { @@ -18,6 +19,7 @@ export interface WorkspaceModel { name: string; description?: string; ownerRef?: string; + lastActiveClusterId?: ClusterId; } export interface WorkspaceState { @@ -30,6 +32,7 @@ export class Workspace implements WorkspaceModel, WorkspaceState { @observable description?: string; @observable ownerRef?: string; @observable enabled: boolean; + @observable lastActiveClusterId?: ClusterId; constructor(data: WorkspaceModel) { Object.assign(this, data); @@ -66,7 +69,8 @@ export class Workspace implements WorkspaceModel, WorkspaceState { id: this.id, name: this.name, description: this.description, - ownerRef: this.ownerRef + ownerRef: this.ownerRef, + lastActiveClusterId: this.lastActiveClusterId }); } } @@ -138,13 +142,12 @@ export class WorkspaceStore extends BaseStore { } @action - setActive(id = WorkspaceStore.defaultId, reset = true) { + setActive(id = WorkspaceStore.defaultId) { if (id === this.currentWorkspaceId) return; if (!this.getById(id)) { throw new Error(`workspace ${id} doesn't exist`); } this.currentWorkspaceId = id; - clusterStore.activeCluster = null; // fixme: handle previously selected cluster from current workspace } @action @@ -184,6 +187,11 @@ export class WorkspaceStore extends BaseStore { clusterStore.removeByWorkspaceId(id); } + @action + setLastActiveClusterId(clusterId?: ClusterId, workspaceId = this.currentWorkspaceId) { + this.getById(workspaceId).lastActiveClusterId = clusterId; + } + @action protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) { if (currentWorkspace) { diff --git a/src/main/tray.ts b/src/main/tray.ts index f84ea21896..f98c064bdd 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -95,7 +95,6 @@ export function createTrayMenu(windowManager: WindowManager): Menu { toolTip: clusterId, async click() { workspaceStore.setActive(workspace); - clusterStore.setActive(clusterId); windowManager.navigate(clusterViewURL({ params: { clusterId } })); } }; diff --git a/src/renderer/components/+workspaces/workspace-menu.tsx b/src/renderer/components/+workspaces/workspace-menu.tsx index 78e61f87aa..5cc29c8ae2 100644 --- a/src/renderer/components/+workspaces/workspace-menu.tsx +++ b/src/renderer/components/+workspaces/workspace-menu.tsx @@ -7,8 +7,11 @@ import { Trans } from "@lingui/macro"; import { Menu, MenuItem, MenuProps } from "../menu"; import { Icon } from "../icon"; import { observable } from "mobx"; -import { workspaceStore } from "../../../common/workspace-store"; +import { WorkspaceId, workspaceStore } from "../../../common/workspace-store"; import { cssNames } from "../../utils"; +import { navigate } from "../../navigation"; +import { clusterViewURL } from "../cluster-manager/cluster-view.route"; +import { landingURL } from "../+landing-page"; interface Props extends Partial { } @@ -17,6 +20,16 @@ interface Props extends Partial { export class WorkspaceMenu extends React.Component { @observable menuVisible = false; + activateWorkspace = (id: WorkspaceId) => { + const clusterId = workspaceStore.getById(id).lastActiveClusterId; + workspaceStore.setActive(id); + if (clusterId) { + navigate(clusterViewURL({ params: { clusterId } })); + } else { + navigate(landingURL()); + } + }; + render() { const { className, ...menuProps } = this.props; const { enabledWorkspacesList, currentWorkspace } = workspaceStore; @@ -38,7 +51,7 @@ export class WorkspaceMenu extends React.Component { key={workspaceId} title={description} active={workspaceId === currentWorkspace.id} - onClick={() => workspaceStore.setActive(workspaceId)} + onClick={() => this.activateWorkspace(workspaceId)} > {name} diff --git a/src/renderer/components/+workspaces/workspaces.tsx b/src/renderer/components/+workspaces/workspaces.tsx index 459f095cec..0f1d600137 100644 --- a/src/renderer/components/+workspaces/workspaces.tsx +++ b/src/renderer/components/+workspaces/workspaces.tsx @@ -13,6 +13,7 @@ import { Input } from "../input"; import { cssNames, prevDefault } from "../../utils"; import { Button } from "../button"; import { isRequired, InputValidator } from "../input/input_validators"; +import { clusterStore } from "../../../common/cluster-store"; @observer export class Workspaces extends React.Component { @@ -70,6 +71,12 @@ export class Workspaces extends React.Component { this.editingWorkspaces.set(id, toJS(workspace)); }; + activateWorkspace = (id: WorkspaceId) => { + const clusterId = workspaceStore.getById(id).lastActiveClusterId; + workspaceStore.setActive(id); + clusterStore.setActive(clusterId); + }; + clearEditing = (id: WorkspaceId) => { this.editingWorkspaces.delete(id); }; @@ -135,7 +142,7 @@ export class Workspaces extends React.Component { {!isEditing && ( - workspaceStore.setActive(workspaceId))}>{name} + this.activateWorkspace(workspaceId))}>{name} {isActive && (current)} {description} diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index 7f11cf395f..ab608815aa 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -77,6 +77,7 @@ export class ClustersMenu extends React.Component { ok: () => { if (clusterStore.activeClusterId === cluster.id) { navigate(landingURL()); + clusterStore.setActive(null); } clusterStore.removeById(cluster.id); },