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 commit082774fe6e. * Revert "Allow extension cluster menus to have a parent (#1452)" This reverts commit622c45cd6d. * Revert "Use @typescript-eslint/semi." This reverts commit890fa5dd9e. * Revert "Add a few missing folders to be linted." This reverts commitc7b24c2922. * Revert "Enforce semicolons in eslint" This reverts commitca67caea60. * Revert "Make BaseStore abstract (#1431)" This reverts commit4b56ab7c61. * Revert "Add Search by Ip to Pod View (#1445)" This reverts commit4079214dc1. * Revert "fix extensionLoader error on dev environments where renderer might start early (#1447)" This reverts commit8a3613ac6f. * 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:
parent
5fd1abe99d
commit
8c73861962
@ -35,6 +35,29 @@ describe("Lens integration tests", () => {
|
|||||||
await app.client.waitUntilTextExists("h1", "Welcome");
|
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", () => {
|
describe("app start", () => {
|
||||||
beforeAll(appStart, 20000);
|
beforeAll(appStart, 20000);
|
||||||
|
|
||||||
@ -73,28 +96,48 @@ describe("Lens integration tests", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const minikubeReady = (): boolean => {
|
describeif(ready)("workspaces", () => {
|
||||||
// determine if minikube is running
|
beforeAll(appStart, 20000);
|
||||||
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
|
afterAll(async () => {
|
||||||
status = spawnSync(`minikube kubectl -- get namespace ${TEST_NAMESPACE}`, { shell: true });
|
if (app && app.isRunning()) {
|
||||||
if (status.status === 0) {
|
return util.tearDown(app);
|
||||||
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;
|
it('creates new workspace', async () => {
|
||||||
};
|
await clickWhatsNew(app);
|
||||||
const ready = minikubeReady();
|
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) => {
|
const addMinikubeCluster = async (app: Application) => {
|
||||||
await app.client.click("div.add-cluster");
|
await app.client.click("div.add-cluster");
|
||||||
|
|||||||
@ -65,6 +65,7 @@ describe("empty config", () => {
|
|||||||
it("sets active cluster", () => {
|
it("sets active cluster", () => {
|
||||||
clusterStore.setActive("foo");
|
clusterStore.setActive("foo");
|
||||||
expect(clusterStore.active.id).toBe("foo");
|
expect(clusterStore.active.id).toBe("foo");
|
||||||
|
expect(workspaceStore.currentWorkspace.lastActiveClusterId).toBe("foo");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { WorkspaceId } from "./workspace-store";
|
import { workspaceStore } from "./workspace-store";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { app, ipcRenderer, remote, webFrame } from "electron";
|
import { app, ipcRenderer, remote, webFrame } from "electron";
|
||||||
import { unlink } from "fs-extra";
|
import { unlink } from "fs-extra";
|
||||||
@ -11,9 +11,10 @@ import { appEventBus } from "./event-bus";
|
|||||||
import { dumpConfigYaml } from "./kube-helpers";
|
import { dumpConfigYaml } from "./kube-helpers";
|
||||||
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||||
import { KubeConfig } from "@kubernetes/client-node";
|
import { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import move from "array-move";
|
import move from "array-move";
|
||||||
import { subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
import type { WorkspaceId } from "./workspace-store";
|
||||||
|
|
||||||
export interface ClusterIconUpload {
|
export interface ClusterIconUpload {
|
||||||
clusterId: string;
|
clusterId: string;
|
||||||
@ -142,7 +143,9 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
setActive(id: ClusterId) {
|
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
|
@action
|
||||||
|
|||||||
@ -5,12 +5,13 @@ import { clusterStore } from "./cluster-store";
|
|||||||
import { appEventBus } from "./event-bus";
|
import { appEventBus } from "./event-bus";
|
||||||
import { broadcastMessage } from "../common/ipc";
|
import { broadcastMessage } from "../common/ipc";
|
||||||
import logger from "../main/logger";
|
import logger from "../main/logger";
|
||||||
|
import type { ClusterId } from "./cluster-store";
|
||||||
|
|
||||||
export type WorkspaceId = string;
|
export type WorkspaceId = string;
|
||||||
|
|
||||||
export interface WorkspaceStoreModel {
|
export interface WorkspaceStoreModel {
|
||||||
|
workspaces: WorkspaceModel[];
|
||||||
currentWorkspace?: WorkspaceId;
|
currentWorkspace?: WorkspaceId;
|
||||||
workspaces: WorkspaceModel[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceModel {
|
export interface WorkspaceModel {
|
||||||
@ -18,6 +19,7 @@ export interface WorkspaceModel {
|
|||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
ownerRef?: string;
|
ownerRef?: string;
|
||||||
|
lastActiveClusterId?: ClusterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceState {
|
export interface WorkspaceState {
|
||||||
@ -30,6 +32,7 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
|||||||
@observable description?: string;
|
@observable description?: string;
|
||||||
@observable ownerRef?: string;
|
@observable ownerRef?: string;
|
||||||
@observable enabled: boolean;
|
@observable enabled: boolean;
|
||||||
|
@observable lastActiveClusterId?: ClusterId;
|
||||||
|
|
||||||
constructor(data: WorkspaceModel) {
|
constructor(data: WorkspaceModel) {
|
||||||
Object.assign(this, data);
|
Object.assign(this, data);
|
||||||
@ -66,7 +69,8 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
|||||||
id: this.id,
|
id: this.id,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
ownerRef: this.ownerRef
|
ownerRef: this.ownerRef,
|
||||||
|
lastActiveClusterId: this.lastActiveClusterId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -138,13 +142,12 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
setActive(id = WorkspaceStore.defaultId, reset = true) {
|
setActive(id = WorkspaceStore.defaultId) {
|
||||||
if (id === this.currentWorkspaceId) return;
|
if (id === this.currentWorkspaceId) return;
|
||||||
if (!this.getById(id)) {
|
if (!this.getById(id)) {
|
||||||
throw new Error(`workspace ${id} doesn't exist`);
|
throw new Error(`workspace ${id} doesn't exist`);
|
||||||
}
|
}
|
||||||
this.currentWorkspaceId = id;
|
this.currentWorkspaceId = id;
|
||||||
clusterStore.activeCluster = null; // fixme: handle previously selected cluster from current workspace
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -184,6 +187,11 @@ export class WorkspaceStore extends BaseStore<WorkspaceStoreModel> {
|
|||||||
clusterStore.removeByWorkspaceId(id);
|
clusterStore.removeByWorkspaceId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setLastActiveClusterId(clusterId?: ClusterId, workspaceId = this.currentWorkspaceId) {
|
||||||
|
this.getById(workspaceId).lastActiveClusterId = clusterId;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
|
protected fromStore({ currentWorkspace, workspaces = [] }: WorkspaceStoreModel) {
|
||||||
if (currentWorkspace) {
|
if (currentWorkspace) {
|
||||||
|
|||||||
@ -95,7 +95,6 @@ export function createTrayMenu(windowManager: WindowManager): Menu {
|
|||||||
toolTip: clusterId,
|
toolTip: clusterId,
|
||||||
async click() {
|
async click() {
|
||||||
workspaceStore.setActive(workspace);
|
workspaceStore.setActive(workspace);
|
||||||
clusterStore.setActive(clusterId);
|
|
||||||
windowManager.navigate(clusterViewURL({ params: { clusterId } }));
|
windowManager.navigate(clusterViewURL({ params: { clusterId } }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,8 +7,11 @@ import { Trans } from "@lingui/macro";
|
|||||||
import { Menu, MenuItem, MenuProps } from "../menu";
|
import { Menu, MenuItem, MenuProps } from "../menu";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import { observable } from "mobx";
|
import { observable } from "mobx";
|
||||||
import { workspaceStore } from "../../../common/workspace-store";
|
import { WorkspaceId, workspaceStore } from "../../../common/workspace-store";
|
||||||
import { cssNames } from "../../utils";
|
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> {
|
interface Props extends Partial<MenuProps> {
|
||||||
}
|
}
|
||||||
@ -17,6 +20,16 @@ interface Props extends Partial<MenuProps> {
|
|||||||
export class WorkspaceMenu extends React.Component<Props> {
|
export class WorkspaceMenu extends React.Component<Props> {
|
||||||
@observable menuVisible = false;
|
@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() {
|
render() {
|
||||||
const { className, ...menuProps } = this.props;
|
const { className, ...menuProps } = this.props;
|
||||||
const { enabledWorkspacesList, currentWorkspace } = workspaceStore;
|
const { enabledWorkspacesList, currentWorkspace } = workspaceStore;
|
||||||
@ -38,7 +51,7 @@ export class WorkspaceMenu extends React.Component<Props> {
|
|||||||
key={workspaceId}
|
key={workspaceId}
|
||||||
title={description}
|
title={description}
|
||||||
active={workspaceId === currentWorkspace.id}
|
active={workspaceId === currentWorkspace.id}
|
||||||
onClick={() => workspaceStore.setActive(workspaceId)}
|
onClick={() => this.activateWorkspace(workspaceId)}
|
||||||
>
|
>
|
||||||
<Icon small material="layers"/>
|
<Icon small material="layers"/>
|
||||||
<span className="workspace">{name}</span>
|
<span className="workspace">{name}</span>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { Input } from "../input";
|
|||||||
import { cssNames, prevDefault } from "../../utils";
|
import { cssNames, prevDefault } from "../../utils";
|
||||||
import { Button } from "../button";
|
import { Button } from "../button";
|
||||||
import { isRequired, InputValidator } from "../input/input_validators";
|
import { isRequired, InputValidator } from "../input/input_validators";
|
||||||
|
import { clusterStore } from "../../../common/cluster-store";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class Workspaces extends React.Component {
|
export class Workspaces extends React.Component {
|
||||||
@ -70,6 +71,12 @@ export class Workspaces extends React.Component {
|
|||||||
this.editingWorkspaces.set(id, toJS(workspace));
|
this.editingWorkspaces.set(id, toJS(workspace));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
activateWorkspace = (id: WorkspaceId) => {
|
||||||
|
const clusterId = workspaceStore.getById(id).lastActiveClusterId;
|
||||||
|
workspaceStore.setActive(id);
|
||||||
|
clusterStore.setActive(clusterId);
|
||||||
|
};
|
||||||
|
|
||||||
clearEditing = (id: WorkspaceId) => {
|
clearEditing = (id: WorkspaceId) => {
|
||||||
this.editingWorkspaces.delete(id);
|
this.editingWorkspaces.delete(id);
|
||||||
};
|
};
|
||||||
@ -135,7 +142,7 @@ export class Workspaces extends React.Component {
|
|||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<span className="name flex gaps align-center">
|
<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>}
|
{isActive && <span> <Trans>(current)</Trans></span>}
|
||||||
</span>
|
</span>
|
||||||
<span className="description">{description}</span>
|
<span className="description">{description}</span>
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export class ClustersMenu extends React.Component<Props> {
|
|||||||
ok: () => {
|
ok: () => {
|
||||||
if (clusterStore.activeClusterId === cluster.id) {
|
if (clusterStore.activeClusterId === cluster.id) {
|
||||||
navigate(landingURL());
|
navigate(landingURL());
|
||||||
|
clusterStore.setActive(null);
|
||||||
}
|
}
|
||||||
clusterStore.removeById(cluster.id);
|
clusterStore.removeById(cluster.id);
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user