From eb1a2a07e0253cfa982d832df22c93f2671fe1bf Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 4 Sep 2020 11:29:55 -0400 Subject: [PATCH] fix clusters staying in previously icon reordered workspaces Signed-off-by: Sebastian Malton --- package.json | 4 +- src/common/cluster-store.ts | 43 ++++++++-------- src/common/cluster-store_test.ts | 28 +++++++++++ src/main/cluster.ts | 2 +- .../cluster-manager/clusters-menu.tsx | 10 ++-- yarn.lock | 49 +++++++++++++++++-- 6 files changed, 102 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index db4d4af404..9a1b57733e 100644 --- a/package.json +++ b/package.json @@ -198,8 +198,8 @@ "openid-client": "^3.15.2", "path-to-regexp": "^6.1.0", "proper-lockfile": "^4.1.1", - "react-router": "^5.2.0", "react-beautiful-dnd": "^13.0.0", + "react-router": "^5.2.0", "request": "^2.88.2", "request-promise-native": "^1.0.8", "semver": "^7.3.2", @@ -318,4 +318,4 @@ "xterm": "^4.6.0", "xterm-addon-fit": "^0.4.0" } -} \ No newline at end of file +} diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index f91757c72f..824f124830 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -11,6 +11,9 @@ import { tracker } from "./tracker"; import { dumpConfigYaml } from "./kube-helpers"; import { saveToAppFiles } from "./utils/saveToAppFiles"; import { KubeConfig } from "@kubernetes/client-node"; +import _ from "lodash"; +import move from "array-move"; +import { is } from "immer/dist/internal"; export interface ClusterIconUpload { clusterId: string; @@ -18,14 +21,9 @@ export interface ClusterIconUpload { path: string; } -interface ClusterIconOrdering { - [workspaceId: string]: ClusterId[] -} - export interface ClusterStoreModel { activeCluster?: ClusterId; // last opened cluster clusters?: ClusterModel[] - iconOrder?: ClusterIconOrdering, } export type ClusterId = string; @@ -53,6 +51,7 @@ export interface ClusterPreferences { prometheusProvider?: { type: string; }; + iconOrder?: number; icon?: string; httpsProxy?: string; } @@ -88,7 +87,6 @@ export class ClusterStore extends BaseStore { @observable activeClusterId: ClusterId; @observable removedClusters = observable.map(); @observable clusters = observable.map(); - @observable clusterOrders: ClusterIconOrdering = {}; @computed get activeCluster(): Cluster | null { return this.getById(this.activeClusterId); @@ -107,6 +105,20 @@ export class ClusterStore extends BaseStore { this.activeClusterId = id; } + @action + swapIconOrders(workspace: WorkspaceId, from: number, to: number) { + const clusters = this.getByWorkspaceId(workspace); + if (from < 0 || to < 0 || from >= clusters.length || to >= clusters.length || isNaN(from) || isNaN(to)) { + throw new Error(`invalid from<->to arguments`) + } + + move.mutate(clusters, from, to); + for (const i in clusters) { + // This resets the iconOrder to the current display order + clusters[i].preferences.iconOrder = +i; + } + } + hasClusters() { return this.clusters.size > 0; } @@ -120,10 +132,9 @@ export class ClusterStore extends BaseStore { } getByWorkspaceId(workspaceId: string): Cluster[] { - this.clusterOrders[workspaceId] ??= Array.from(this.clusters.values()) - .filter(cluster => cluster.workspace === workspaceId) - .map(cluster => cluster.id); - return this.clusterOrders[workspaceId].map(clusterId => this.clusters.get(clusterId)); + const clusters = Array.from(this.clusters.values()) + .filter(cluster => cluster.workspace === workspaceId); + return _.sortBy(clusters, cluster => cluster.preferences.iconOrder) } @action @@ -159,13 +170,11 @@ export class ClusterStore extends BaseStore { } @action - protected fromStore({ activeCluster, clusters = [], iconOrder = {} }: ClusterStoreModel = {}) { + protected fromStore({ activeCluster, clusters = [] }: ClusterStoreModel = {}) { const currentClusters = this.clusters.toJS(); const newClusters = new Map(); const removedClusters = new Map(); - this.clusterOrders = iconOrder; - // update new clusters for (const clusterModel of clusters) { let cluster = currentClusters.get(clusterModel.id); @@ -175,13 +184,6 @@ export class ClusterStore extends BaseStore { cluster = new Cluster(clusterModel); } newClusters.set(clusterModel.id, cluster); - - if (!this.clusterOrders[cluster.workspace]) { - this.clusterOrders[cluster.workspace] = []; - } - if (!this.clusterOrders[cluster.workspace].includes(cluster.id)) { - this.clusterOrders[cluster.workspace].push(cluster.id); - } } // update removed clusters @@ -200,7 +202,6 @@ export class ClusterStore extends BaseStore { return toJS({ activeCluster: this.activeClusterId, clusters: this.clustersList.map(cluster => cluster.toJSON()), - iconOrder: this.clusterOrders, }, { recurseEverything: true }) diff --git a/src/common/cluster-store_test.ts b/src/common/cluster-store_test.ts index fd27d0da43..cee18078bc 100644 --- a/src/common/cluster-store_test.ts +++ b/src/common/cluster-store_test.ts @@ -93,6 +93,34 @@ describe("empty config", () => { expect(fs.readFileSync(file, "utf8")).toBe("kubeconfig"); }) + it("check if reorderring works for same from and to", () => { + clusterStore.swapIconOrders("workstation", 1, 1) + + const clusters = clusterStore.getByWorkspaceId("workstation"); + expect(clusters[0].id).toBe("prod") + expect(clusters[0].preferences.iconOrder).toBe(0) + expect(clusters[1].id).toBe("dev") + expect(clusters[1].preferences.iconOrder).toBe(1) + }); + + it("check if reorderring works for different from and to", () => { + clusterStore.swapIconOrders("workstation", 0, 1) + + const clusters = clusterStore.getByWorkspaceId("workstation"); + expect(clusters[0].id).toBe("dev") + expect(clusters[0].preferences.iconOrder).toBe(0) + expect(clusters[1].id).toBe("prod") + expect(clusters[1].preferences.iconOrder).toBe(1) + }); + + it("check if after icon reordering, changing workspaces still works", () => { + clusterStore.swapIconOrders("workstation", 1, 1) + clusterStore.getById("prod").workspace = "default" + + expect(clusterStore.getByWorkspaceId("workstation").length).toBe(1); + expect(clusterStore.getByWorkspaceId("default").length).toBe(2); + }); + it("removes cluster from store", async () => { await clusterStore.removeById("foo"); expect(clusterStore.getById("foo")).toBeUndefined(); diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 873c026751..8f17216c18 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -2,7 +2,7 @@ import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/clus import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api"; import type { WorkspaceId } from "../common/workspace-store"; import type { FeatureStatusMap } from "./feature" -import { action, computed, observable, reaction, toJS, when } from "mobx"; +import { action, computed, intercept, observable, reaction, toJS, when } from "mobx"; import { apiKubePrefix } from "../common/vars"; import { broadcastIpc } from "../common/ipc"; import { ContextHandler } from "./context-handler" diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index ed9115fb7a..07780830ff 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -21,7 +21,6 @@ import { ConfirmDialog } from "../confirm-dialog"; import { clusterIpc } from "../../../common/cluster-ipc"; import { clusterViewURL, getMatchedClusterId } from "./cluster-view.route"; import { DragDropContext, Droppable, Draggable, DropResult, DroppableProvided, DraggableProvided } from "react-beautiful-dnd"; -import move from "array-move"; // fixme: allow to rearrange clusters with drag&drop @@ -93,10 +92,11 @@ export class ClustersMenu extends React.Component { swapClusterIconOrder(result: DropResult) { if (result.reason === "DROP") { const { currentWorkspaceId } = workspaceStore; - const clusters = clusterStore.getByWorkspaceId(currentWorkspaceId); - - move.mutate(clusters, result.source.index, result.destination.index); - clusterStore.clusterOrders[currentWorkspaceId] = clusters.map(c => c.id); + const { + source: { index: from }, + destination: { index: to }, + } = result + clusterStore.swapIconOrders(currentWorkspaceId, from, to) } } diff --git a/yarn.lock b/yarn.lock index 6b07946aad..44fc03bfd8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1350,6 +1350,17 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jest/types@^26.3.0": + version "26.3.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71" + integrity sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + "@kubernetes/client-node@^0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@kubernetes/client-node/-/client-node-0.12.0.tgz#79120311bced206ac8fa36435fb4cc2c1828fff2" @@ -1846,6 +1857,21 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@26.x": + version "26.0.13" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.13.tgz#5a7b9d5312f5dd521a38329c38ee9d3802a0b85e" + integrity sha512-sCzjKow4z9LILc6DhBvn5AkIfmQzDZkgtVVKmGwVrs5tuid38ws281D4l+7x1kP487+FlKDh5kfMZ8WSPAdmdA== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/jest@^25.2.3": version "25.2.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" @@ -7144,6 +7170,18 @@ jest-snapshot@^26.0.1: pretty-format "^26.0.1" semver "^7.3.2" +jest-util@26.x: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e" + integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw== + dependencies: + "@jest/types" "^26.3.0" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-util@^26.0.1: version "26.0.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.0.1.tgz#72c4c51177b695fdd795ca072a6f94e3d7cef00a" @@ -7903,7 +7941,7 @@ messageformat-parser@^4.1.3: resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-4.1.3.tgz#b824787f57fcda7d50769f5b63e8d4fda68f5b9e" integrity sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg== -micromatch@4.0.2, micromatch@4.x, micromatch@^4.0.0, micromatch@^4.0.2: +micromatch@4.0.2, micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -11285,17 +11323,18 @@ truncate-utf8-bytes@^1.0.0: utf8-byte-length "^1.0.1" ts-jest@^26.1.0: - version "26.1.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.1.0.tgz#e9070fc97b3ea5557a48b67c631c74eb35e15417" - integrity sha512-JbhQdyDMYN5nfKXaAwCIyaWLGwevcT2/dbqRPsQeh6NZPUuXjZQZEfeLb75tz0ubCIgEELNm6xAzTe5NXs5Y4Q== + version "26.3.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.3.0.tgz#6b2845045347dce394f069bb59358253bc1338a9" + integrity sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q== dependencies: + "@types/jest" "26.x" bs-logger "0.x" buffer-from "1.x" fast-json-stable-stringify "2.x" + jest-util "26.x" json5 "2.x" lodash.memoize "4.x" make-error "1.x" - micromatch "4.x" mkdirp "1.x" semver "7.x" yargs-parser "18.x"