1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Open last active cluster after switching workspaces (#1444)

* Save and restore lastActiveClusterId

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Activate clusters from workspaces page

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Fix saving last cluster while jumping from tray

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding workspace switch tests

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Remove console.log()

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Cleaning up

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Clean duplicated ClusterId definition

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Moving lastActiveClusterId field into WorkspaceModel

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* fix extensionLoader error on dev environments where renderer might start early (#1447)

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* Add Search by Ip to Pod View (#1445)

Signed-off-by: Pavel Ashevskii <pashevskii@mirantis.com>

* Make BaseStore abstract (#1431)

* make BaseStore abstract so that implementers are forced to decide how to store data

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Enforce semicolons in eslint

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>

* Add a few missing folders to be linted.

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>

* Use @typescript-eslint/semi.

Signed-off-by: Panu Horsmalahti <phorsmalahti@mirantis.com>

* Allow extension cluster menus to have a parent (#1452)

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix SwitchCase indent rule in eslint (#1454)

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Revert "fix SwitchCase indent rule in eslint (#1454)"

This reverts commit 082774fe6e.

* Revert "Allow extension cluster menus to have a parent (#1452)"

This reverts commit 622c45cd6d.

* Revert "Use @typescript-eslint/semi."

This reverts commit 890fa5dd9e.

* Revert "Add a few missing folders to be linted."

This reverts commit c7b24c2922.

* Revert "Enforce semicolons in eslint"

This reverts commit ca67caea60.

* Revert "Make BaseStore abstract (#1431)"

This reverts commit 4b56ab7c61.

* Revert "Add Search by Ip to Pod View (#1445)"

This reverts commit 4079214dc1.

* Revert "fix extensionLoader error on dev environments where renderer might start early (#1447)"

This reverts commit 8a3613ac6f.

* Split workspace tests to smaller ones

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Missing semicolons

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Split workspace tests a bit more

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding extra click in Add Cluster button

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Adding more awaits to check running cluster

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

* Wait for minikube before running tests

Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>

Co-authored-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
Co-authored-by: pashevskii <53330707+pashevskii@users.noreply.github.com>
Co-authored-by: Sebastian Malton <sebastian@malton.name>
Co-authored-by: Panu Horsmalahti <phorsmalahti@mirantis.com>
This commit is contained in:
Alex Andreev 2020-11-20 14:53:28 +03:00 committed by GitHub
parent 5fd1abe99d
commit 8c73861962
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 31 deletions

View File

@ -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");

View File

@ -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");
});
});

View File

@ -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<ClusterStoreModel> {
@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

View File

@ -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<WorkspaceStoreModel> {
}
@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<WorkspaceStoreModel> {
clusterStore.removeByWorkspaceId(id);
}
@action
setLastActiveClusterId(clusterId?: ClusterId, workspaceId = this.currentWorkspaceId) {
this.getById(workspaceId).lastActiveClusterId = clusterId;
}
@action
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
if (currentWorkspace) {

View File

@ -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 } }));
}
};

View File

@ -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<MenuProps> {
}
@ -17,6 +20,16 @@ interface Props extends Partial<MenuProps> {
export class WorkspaceMenu extends React.Component<Props> {
@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<Props> {
key={workspaceId}
title={description}
active={workspaceId === currentWorkspace.id}
onClick={() => workspaceStore.setActive(workspaceId)}
onClick={() => this.activateWorkspace(workspaceId)}
>
<Icon small material="layers"/>
<span className="workspace">{name}</span>

View File

@ -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 && (
<Fragment>
<span className="name flex gaps align-center">
<a href="#" onClick={prevDefault(() => workspaceStore.setActive(workspaceId))}>{name}</a>
<a href="#" onClick={prevDefault(() => this.activateWorkspace(workspaceId))}>{name}</a>
{isActive && <span> <Trans>(current)</Trans></span>}
</span>
<span className="description">{description}</span>

View File

@ -77,6 +77,7 @@ export class ClustersMenu extends React.Component<Props> {
ok: () => {
if (clusterStore.activeClusterId === cluster.id) {
navigate(landingURL());
clusterStore.setActive(null);
}
clusterStore.removeById(cluster.id);
},