From 7f51e3addd2e7e5d0d03afcc4a7e84bee1045399 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 18 Jun 2021 08:44:28 -0400 Subject: [PATCH] Make CatalogEntity labels clickable (#3075) - Transition workspace's to be labels - Fix migrations to support multiple migrations for the same version Signed-off-by: Sebastian Malton --- package.json | 2 +- src/common/cluster-store.ts | 5 ++ src/main/cluster-manager.ts | 2 + src/main/cluster.ts | 10 +++ src/migrations/cluster-store/2.0.0-beta.2.ts | 12 ++-- src/migrations/cluster-store/2.4.1.ts | 9 +-- src/migrations/cluster-store/2.6.0-beta.2.ts | 6 +- src/migrations/cluster-store/2.6.0-beta.3.ts | 10 +-- src/migrations/cluster-store/2.7.0-beta.0.ts | 6 +- src/migrations/cluster-store/2.7.0-beta.1.ts | 6 +- src/migrations/cluster-store/3.6.0-beta.1.ts | 16 ++--- src/migrations/cluster-store/5.0.0-beta.10.ts | 69 +++++++++++++++++++ src/migrations/cluster-store/index.ts | 24 ++++--- src/migrations/cluster-store/snap.ts | 12 ++-- .../{migration-wrapper.ts => helpers.ts} | 41 +++++++---- src/migrations/hotbar-store/5.0.0-alpha.0.ts | 10 +-- src/migrations/hotbar-store/5.0.0-alpha.2.ts | 6 +- src/migrations/hotbar-store/5.0.0-beta.5.ts | 6 +- src/migrations/hotbar-store/index.ts | 12 ++-- src/migrations/user-store/2.1.0-beta.4.ts | 6 +- src/migrations/user-store/5.0.0-alpha.3.ts | 6 +- src/migrations/user-store/index.ts | 10 +-- .../+catalog/catalog-entity-details.tsx | 13 ++-- ...tity.store.ts => catalog-entity.store.tsx} | 38 +++++++--- .../components/+catalog/catalog.module.css | 4 ++ src/renderer/components/+catalog/catalog.tsx | 21 +++--- src/renderer/components/badge/badge.scss | 6 +- src/renderer/components/badge/badge.tsx | 3 +- .../item-object-list/page-filters.store.ts | 15 +++- src/renderer/themes/lens-light.json | 2 +- 30 files changed, 268 insertions(+), 120 deletions(-) create mode 100644 src/migrations/cluster-store/5.0.0-beta.10.ts rename src/migrations/{migration-wrapper.ts => helpers.ts} (59%) rename src/renderer/components/+catalog/{catalog-entity.store.ts => catalog-entity.store.tsx} (81%) diff --git a/package.json b/package.json index 3d62c8558f..297e37e16b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "OpenLens", "description": "OpenLens - Open Source IDE for Kubernetes", "homepage": "https://github.com/lensapp/lens", - "version": "5.0.0-beta.9", + "version": "5.0.0-beta.10", "main": "static/build/main.js", "copyright": "© 2021 OpenLens Authors", "license": "MIT", diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 01ff9b3fa1..1a39f201c0 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -80,6 +80,11 @@ export interface ClusterModel { /** Metadata */ metadata?: ClusterMetadata; + /** + * Labels for the catalog entity + */ + labels?: Record; + /** List of accessible namespaces */ accessibleNamespaces?: string[]; } diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index d42b2a6002..5f2ef9899d 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -102,6 +102,8 @@ export class ClusterManager extends Singleton { const entity = catalogEntityRegistry.items[index] as KubernetesCluster; this.updateEntityStatus(entity, cluster); + + entity.metadata.labels = Object.assign({}, cluster.labels, entity.metadata.labels); if (cluster.preferences?.clusterName) { entity.metadata.name = cluster.preferences.clusterName; diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 56c07a26b0..6675bd0151 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -207,6 +207,11 @@ export class Cluster implements ClusterModel, ClusterState { */ @observable accessibleNamespaces: string[] = []; + /** + * Labels for the catalog entity + */ + @observable labels: Record = {}; + /** * Is cluster available * @@ -304,6 +309,10 @@ export class Cluster implements ClusterModel, ClusterState { if (model.accessibleNamespaces) { this.accessibleNamespaces = model.accessibleNamespaces; } + + if (model.labels) { + this.labels = model.labels; + } } /** @@ -583,6 +592,7 @@ export class Cluster implements ClusterModel, ClusterState { preferences: this.preferences, metadata: this.metadata, accessibleNamespaces: this.accessibleNamespaces, + labels: this.labels, }; return toJS(model); diff --git a/src/migrations/cluster-store/2.0.0-beta.2.ts b/src/migrations/cluster-store/2.0.0-beta.2.ts index 0d54588194..db2fd0bff0 100644 --- a/src/migrations/cluster-store/2.0.0-beta.2.ts +++ b/src/migrations/cluster-store/2.0.0-beta.2.ts @@ -19,12 +19,14 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* Early store format had the kubeconfig directly under context name, this moves - it under the kubeConfig key */ +import type { MigrationDeclaration } from "../helpers"; -import { migration } from "../migration-wrapper"; +/** + * Early store format had the kubeconfig directly under context name, this moves + * it under the kubeConfig key + */ -export default migration({ +export default { version: "2.0.0-beta.2", run(store) { for (const value of store) { @@ -35,4 +37,4 @@ export default migration({ store.set(contextName, { kubeConfig: value[1] }); } } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/2.4.1.ts b/src/migrations/cluster-store/2.4.1.ts index 59d4ef21dc..192f7f3dc8 100644 --- a/src/migrations/cluster-store/2.4.1.ts +++ b/src/migrations/cluster-store/2.4.1.ts @@ -19,10 +19,11 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Cleans up a store that had the state related data stored -import { migration } from "../migration-wrapper"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +// Cleans up a store that had the state related data stored + +export default { version: "2.4.1", run(store) { for (const value of store) { @@ -34,4 +35,4 @@ export default migration({ store.set(contextName, { kubeConfig: cluster.kubeConfig, icon: cluster.icon || null, preferences: cluster.preferences || {} }); } } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/2.6.0-beta.2.ts b/src/migrations/cluster-store/2.6.0-beta.2.ts index ffe303a28d..a1d7df0c1b 100644 --- a/src/migrations/cluster-store/2.6.0-beta.2.ts +++ b/src/migrations/cluster-store/2.6.0-beta.2.ts @@ -20,9 +20,9 @@ */ // Move cluster icon from root to preferences -import { migration } from "../migration-wrapper"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "2.6.0-beta.2", run(store) { for (const value of store) { @@ -40,4 +40,4 @@ export default migration({ store.set(clusterKey, { contextName: clusterKey, kubeConfig: value[1].kubeConfig, preferences: value[1].preferences }); } } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/2.6.0-beta.3.ts b/src/migrations/cluster-store/2.6.0-beta.3.ts index 9ec844fe65..75662582e5 100644 --- a/src/migrations/cluster-store/2.6.0-beta.3.ts +++ b/src/migrations/cluster-store/2.6.0-beta.3.ts @@ -19,12 +19,12 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { migration } from "../migration-wrapper"; import yaml from "js-yaml"; +import { MigrationDeclaration, migrationLog } from "../helpers"; -export default migration({ +export default { version: "2.6.0-beta.3", - run(store, log) { + run(store) { for (const value of store) { const clusterKey = value[0]; @@ -50,7 +50,7 @@ export default migration({ if (authConfig.expiry) { authConfig.expiry = `${authConfig.expiry}`; } - log(authConfig); + migrationLog(authConfig); user["auth-provider"].config = authConfig; kubeConfig.users = [{ name: userObj.name, @@ -62,4 +62,4 @@ export default migration({ } } } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/2.7.0-beta.0.ts b/src/migrations/cluster-store/2.7.0-beta.0.ts index a23d3be991..150cb3f8ce 100644 --- a/src/migrations/cluster-store/2.7.0-beta.0.ts +++ b/src/migrations/cluster-store/2.7.0-beta.0.ts @@ -20,9 +20,9 @@ */ // Add existing clusters to "default" workspace -import { migration } from "../migration-wrapper"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "2.7.0-beta.0", run(store) { for (const value of store) { @@ -35,4 +35,4 @@ export default migration({ store.set(clusterKey, cluster); } } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/2.7.0-beta.1.ts b/src/migrations/cluster-store/2.7.0-beta.1.ts index 06439063c6..b7f9c8b0a0 100644 --- a/src/migrations/cluster-store/2.7.0-beta.1.ts +++ b/src/migrations/cluster-store/2.7.0-beta.1.ts @@ -20,10 +20,10 @@ */ // Add id for clusters and store them to array -import { migration } from "../migration-wrapper"; import { v4 as uuid } from "uuid"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "2.7.0-beta.1", run(store) { const clusters: any[] = []; @@ -48,4 +48,4 @@ export default migration({ store.set("clusters", clusters); } } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/3.6.0-beta.1.ts b/src/migrations/cluster-store/3.6.0-beta.1.ts index 516e025eb6..2599005332 100644 --- a/src/migrations/cluster-store/3.6.0-beta.1.ts +++ b/src/migrations/cluster-store/3.6.0-beta.1.ts @@ -24,25 +24,25 @@ import path from "path"; import { app } from "electron"; -import { migration } from "../migration-wrapper"; import fse from "fs-extra"; import { ClusterModel, ClusterStore } from "../../common/cluster-store"; import { loadConfigFromFileSync } from "../../common/kube-helpers"; +import { MigrationDeclaration, migrationLog } from "../helpers"; interface Pre360ClusterModel extends ClusterModel { kubeConfig: string; } -export default migration({ +export default { version: "3.6.0-beta.1", - run(store, printLog) { + run(store) { const userDataPath = app.getPath("userData"); const storedClusters: Pre360ClusterModel[] = store.get("clusters") ?? []; const migratedClusters: ClusterModel[] = []; fse.ensureDirSync(ClusterStore.storedKubeConfigFolder); - printLog("Number of clusters to migrate: ", storedClusters.length); + migrationLog("Number of clusters to migrate: ", storedClusters.length); for (const clusterModel of storedClusters) { /** @@ -59,7 +59,7 @@ export default migration({ delete clusterModel.kubeConfig; } catch (error) { - printLog(`Failed to migrate Kubeconfig for cluster "${clusterModel.id}", removing clusterModel...`, error); + migrationLog(`Failed to migrate Kubeconfig for cluster "${clusterModel.id}", removing clusterModel...`, error); continue; } @@ -69,7 +69,7 @@ export default migration({ */ try { if (clusterModel.preferences?.icon) { - printLog(`migrating ${clusterModel.preferences.icon} for ${clusterModel.preferences.clusterName}`); + migrationLog(`migrating ${clusterModel.preferences.icon} for ${clusterModel.preferences.clusterName}`); const iconPath = clusterModel.preferences.icon.replace("store://", ""); const fileData = fse.readFileSync(path.join(userDataPath, iconPath)); @@ -78,7 +78,7 @@ export default migration({ delete clusterModel.preferences?.icon; } } catch (error) { - printLog(`Failed to migrate cluster icon for cluster "${clusterModel.id}"`, error); + migrationLog(`Failed to migrate cluster icon for cluster "${clusterModel.id}"`, error); delete clusterModel.preferences.icon; } @@ -87,4 +87,4 @@ export default migration({ store.set("clusters", migratedClusters); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/5.0.0-beta.10.ts b/src/migrations/cluster-store/5.0.0-beta.10.ts new file mode 100644 index 0000000000..daea58279b --- /dev/null +++ b/src/migrations/cluster-store/5.0.0-beta.10.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import path from "path"; +import { app } from "electron"; +import fse from "fs-extra"; +import type { ClusterModel } from "../../common/cluster-store"; +import { MigrationDeclaration, migrationLog } from "../helpers"; + +interface Pre500WorkspaceStoreModel { + workspaces: { + id: string; + name: string; + }[]; +} + +export default { + version: "5.0.0-beta.10", + run(store) { + const userDataPath = app.getPath("userData"); + + try { + const workspaceData: Pre500WorkspaceStoreModel = fse.readJsonSync(path.join(userDataPath, "lens-workspace-store.json")); + const workspaces = new Map(); // mapping from WorkspaceId to name + + for (const { id, name } of workspaceData.workspaces) { + workspaces.set(id, name); + } + + migrationLog("workspaces", JSON.stringify([...workspaces.entries()])); + + const clusters: ClusterModel[] = store.get("clusters"); + + for (const cluster of clusters) { + if (cluster.workspace && workspaces.has(cluster.workspace)) { + cluster.labels ??= {}; + cluster.labels.workspace = workspaces.get(cluster.workspace); + } + } + + store.set("clusters", clusters); + } catch (error) { + migrationLog("error", error.path); + + if (!(error.code === "ENOENT" && error.path.endsWith("lens-workspace-store.json"))) { + // ignore lens-workspace-store.json being missing + throw error; + } + } + }, +} as MigrationDeclaration; diff --git a/src/migrations/cluster-store/index.ts b/src/migrations/cluster-store/index.ts index b80911bbfe..9bf870825c 100644 --- a/src/migrations/cluster-store/index.ts +++ b/src/migrations/cluster-store/index.ts @@ -21,6 +21,8 @@ // Cluster store migrations +import { joinMigrations } from "../helpers"; + import version200Beta2 from "./2.0.0-beta.2"; import version241 from "./2.4.1"; import version260Beta2 from "./2.6.0-beta.2"; @@ -28,15 +30,17 @@ import version260Beta3 from "./2.6.0-beta.3"; import version270Beta0 from "./2.7.0-beta.0"; import version270Beta1 from "./2.7.0-beta.1"; import version360Beta1 from "./3.6.0-beta.1"; +import version500Beta10 from "./5.0.0-beta.10"; import snap from "./snap"; -export default { - ...version200Beta2, - ...version241, - ...version260Beta2, - ...version260Beta3, - ...version270Beta0, - ...version270Beta1, - ...version360Beta1, - ...snap -}; +export default joinMigrations( + version200Beta2, + version241, + version260Beta2, + version260Beta3, + version270Beta0, + version270Beta1, + version360Beta1, + version500Beta10, + snap, +); diff --git a/src/migrations/cluster-store/snap.ts b/src/migrations/cluster-store/snap.ts index 3dcf998f1c..c1c5eb2dc5 100644 --- a/src/migrations/cluster-store/snap.ts +++ b/src/migrations/cluster-store/snap.ts @@ -21,22 +21,22 @@ // Fix embedded kubeconfig paths under snap config -import { migration } from "../migration-wrapper"; import type { ClusterModel } from "../../common/cluster-store"; import { getAppVersion } from "../../common/utils/app-version"; import fs from "fs"; +import { MigrationDeclaration, migrationLog } from "../helpers"; -export default migration({ +export default { version: getAppVersion(), // Run always after upgrade - run(store, printLog) { + run(store) { if (!process.env["SNAP"]) return; - printLog("Migrating embedded kubeconfig paths"); + migrationLog("Migrating embedded kubeconfig paths"); const storedClusters: ClusterModel[] = store.get("clusters") || []; if (!storedClusters.length) return; - printLog("Number of clusters to migrate: ", storedClusters.length); + migrationLog("Number of clusters to migrate: ", storedClusters.length); const migratedClusters = storedClusters .map(cluster => { /** @@ -54,4 +54,4 @@ export default migration({ store.set("clusters", migratedClusters); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/migration-wrapper.ts b/src/migrations/helpers.ts similarity index 59% rename from src/migrations/migration-wrapper.ts rename to src/migrations/helpers.ts index 26b17e0450..623d46a63e 100644 --- a/src/migrations/migration-wrapper.ts +++ b/src/migrations/helpers.ts @@ -19,24 +19,37 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import type Config from "conf"; +import type Conf from "conf"; +import type { Migrations } from "conf/dist/source/types"; +import { ExtendedMap, iter } from "../common/utils"; import { isTestEnv } from "../common/vars"; -export interface MigrationOpts { - version: string; - run(storeConfig: Config, log: (...args: any[]) => void): void; +export function migrationLog(...args: any[]) { + if (!isTestEnv) { + console.log(...args); + } } -function infoLog(...args: any[]) { - if (isTestEnv) return; - console.log(...args); +export interface MigrationDeclaration { + version: string, + run(store: Conf): void; } -export function migration({ version, run }: MigrationOpts) { - return { - [version]: (storeConfig: Config) => { - infoLog(`STORE MIGRATION (${storeConfig.path}): ${version}`,); - run(storeConfig, infoLog); - } - }; +export function joinMigrations(...declarations: MigrationDeclaration[]): Migrations { + const migrations = new ExtendedMap) => void)[]>(); + + for (const decl of declarations) { + migrations.getOrInsert(decl.version, () => []).push(decl.run); + } + + return Object.fromEntries( + iter.map( + migrations, + ([v, fns]) => [v, (store: Conf) => { + for (const fn of fns) { + fn(store); + } + }] + ) + ); } diff --git a/src/migrations/hotbar-store/5.0.0-alpha.0.ts b/src/migrations/hotbar-store/5.0.0-alpha.0.ts index 461d6e1886..7cd8011414 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.0.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.0.ts @@ -22,10 +22,10 @@ // Cleans up a store that had the state related data stored import type { Hotbar } from "../../common/hotbar-store"; import { ClusterStore } from "../../common/cluster-store"; -import { migration } from "../migration-wrapper"; -import { v4 as uuid } from "uuid"; +import * as uuid from "uuid"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "5.0.0-alpha.0", run(store) { const hotbars: Hotbar[] = []; @@ -38,7 +38,7 @@ export default migration({ let hotbar = hotbars.find((h) => h.name === name); if (!hotbar) { - hotbar = { id: uuid(), name, items: [] }; + hotbar = { id: uuid.v4(), name, items: [] }; hotbars.push(hotbar); } @@ -50,4 +50,4 @@ export default migration({ store.set("hotbars", hotbars); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/hotbar-store/5.0.0-alpha.2.ts b/src/migrations/hotbar-store/5.0.0-alpha.2.ts index 83696d01b0..e049a6d731 100644 --- a/src/migrations/hotbar-store/5.0.0-alpha.2.ts +++ b/src/migrations/hotbar-store/5.0.0-alpha.2.ts @@ -21,10 +21,10 @@ // Cleans up a store that had the state related data stored import type { Hotbar } from "../../common/hotbar-store"; -import { migration } from "../migration-wrapper"; import * as uuid from "uuid"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "5.0.0-alpha.2", run(store) { const hotbars = (store.get("hotbars") || []) as Hotbar[]; @@ -34,4 +34,4 @@ export default migration({ ...hotbar }))); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/hotbar-store/5.0.0-beta.5.ts b/src/migrations/hotbar-store/5.0.0-beta.5.ts index 33e39f2a13..1cc8bc2840 100644 --- a/src/migrations/hotbar-store/5.0.0-beta.5.ts +++ b/src/migrations/hotbar-store/5.0.0-beta.5.ts @@ -20,10 +20,10 @@ */ import type { Hotbar } from "../../common/hotbar-store"; -import { migration } from "../migration-wrapper"; import { catalogEntityRegistry } from "../../renderer/api/catalog-entity-registry"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "5.0.0-beta.5", run(store) { const hotbars: Hotbar[] = store.get("hotbars"); @@ -48,4 +48,4 @@ export default migration({ store.set("hotbars", hotbars); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/hotbar-store/index.ts b/src/migrations/hotbar-store/index.ts index fabedee4b7..899429cf2a 100644 --- a/src/migrations/hotbar-store/index.ts +++ b/src/migrations/hotbar-store/index.ts @@ -21,12 +21,14 @@ // Hotbar store migrations +import { joinMigrations } from "../helpers"; + import version500alpha0 from "./5.0.0-alpha.0"; import version500alpha2 from "./5.0.0-alpha.2"; import version500beta5 from "./5.0.0-beta.5"; -export default { - ...version500alpha0, - ...version500alpha2, - ...version500beta5, -}; +export default joinMigrations( + version500alpha0, + version500alpha2, + version500beta5, +); diff --git a/src/migrations/user-store/2.1.0-beta.4.ts b/src/migrations/user-store/2.1.0-beta.4.ts index eace0dd367..12aa3d1cf0 100644 --- a/src/migrations/user-store/2.1.0-beta.4.ts +++ b/src/migrations/user-store/2.1.0-beta.4.ts @@ -20,11 +20,11 @@ */ // Add / reset "lastSeenAppVersion" -import { migration } from "../migration-wrapper"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "2.1.0-beta.4", run(store) { store.set("lastSeenAppVersion", "0.0.0"); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/user-store/5.0.0-alpha.3.ts b/src/migrations/user-store/5.0.0-alpha.3.ts index 57692c0bc0..3f70240164 100644 --- a/src/migrations/user-store/5.0.0-alpha.3.ts +++ b/src/migrations/user-store/5.0.0-alpha.3.ts @@ -20,9 +20,9 @@ */ // Switch representation of hiddenTableColumns in store -import { migration } from "../migration-wrapper"; +import type { MigrationDeclaration } from "../helpers"; -export default migration({ +export default { version: "5.0.0-alpha.3", run(store) { const preferences = store.get("preferences"); @@ -36,4 +36,4 @@ export default migration({ store.set("preferences", preferences); } -}); +} as MigrationDeclaration; diff --git a/src/migrations/user-store/index.ts b/src/migrations/user-store/index.ts index cf345b1bfb..c08ef2c5c5 100644 --- a/src/migrations/user-store/index.ts +++ b/src/migrations/user-store/index.ts @@ -21,6 +21,8 @@ // User store migrations +import { joinMigrations } from "../helpers"; + import version210Beta4 from "./2.1.0-beta.4"; import version500Alpha3 from "./5.0.0-alpha.3"; import { fileNameMigration } from "./file-name-migration"; @@ -29,7 +31,7 @@ export { fileNameMigration }; -export default { - ...version210Beta4, - ...version500Alpha3, -}; +export default joinMigrations( + version210Beta4, + version500Alpha3, +); diff --git a/src/renderer/components/+catalog/catalog-entity-details.tsx b/src/renderer/components/+catalog/catalog-entity-details.tsx index c36a227695..b0df7371a6 100644 --- a/src/renderer/components/+catalog/catalog-entity-details.tsx +++ b/src/renderer/components/+catalog/catalog-entity-details.tsx @@ -22,9 +22,9 @@ import "./catalog-entity-details.scss"; import React, { Component } from "react"; import { observer } from "mobx-react"; -import { Drawer, DrawerItem, DrawerItemLabels } from "../drawer"; -import { CatalogEntity, catalogEntityRunContext } from "../../api/catalog-entity"; -import type { CatalogCategory } from "../../../common/catalog"; +import { Drawer, DrawerItem } from "../drawer"; +import { catalogEntityRunContext } from "../../api/catalog-entity"; +import type { CatalogCategory, CatalogEntity } from "../../../common/catalog"; import { Icon } from "../icon"; import { CatalogEntityDrawerMenu } from "./catalog-entity-drawer-menu"; import { CatalogEntityDetailRegistry } from "../../../extensions/registries"; @@ -86,10 +86,9 @@ export class CatalogEntityDetails extends Component {item.phase} - + + {...item.getLabelBadges(this.props.hideDetails)} + )} diff --git a/src/renderer/components/+catalog/catalog-entity.store.ts b/src/renderer/components/+catalog/catalog-entity.store.tsx similarity index 81% rename from src/renderer/components/+catalog/catalog-entity.store.ts rename to src/renderer/components/+catalog/catalog-entity.store.tsx index 2b93c241cc..fea9e5e6ba 100644 --- a/src/renderer/components/+catalog/catalog-entity.store.ts +++ b/src/renderer/components/+catalog/catalog-entity.store.tsx @@ -19,12 +19,23 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import styles from "./catalog.module.css"; + +import React from "react"; import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from "mobx"; import { catalogEntityRegistry } from "../../api/catalog-entity-registry"; import type { CatalogEntity, CatalogEntityActionContext } from "../../api/catalog-entity"; import { ItemObject, ItemStore } from "../../item.store"; import { CatalogCategory, catalogCategoryRegistry } from "../../../common/catalog"; import { autoBind } from "../../../common/utils"; +import { Badge } from "../badge"; +import { navigation } from "../../navigation"; +import { searchUrlParam } from "../input"; +import { makeCss } from "../../../common/utils/makeCss"; +import { KubeObject } from "../../api/kube-object"; + +const css = makeCss(styles); + export class CatalogEntityItem implements ItemObject { constructor(public entity: T) {} @@ -61,15 +72,24 @@ export class CatalogEntityItem implements ItemObject { } get labels() { - const labels: string[] = []; + return KubeObject.stringifyLabels(this.entity.metadata.labels); + } - Object.keys(this.entity.metadata.labels).forEach((key) => { - const value = this.entity.metadata.labels[key]; - - labels.push(`${key}=${value}`); - }); - - return labels; + getLabelBadges(onClick?: React.MouseEventHandler) { + return this.labels + .map(label => ( + { + navigation.searchParams.set(searchUrlParam.name, label); + onClick?.(event); + event.stopPropagation(); + }} + /> + )); } get source() { @@ -82,7 +102,7 @@ export class CatalogEntityItem implements ItemObject { this.id, this.phase, `source=${this.source}`, - ...this.labels.map((value, key) => `${key}=${value}`) + ...this.labels, ]; } diff --git a/src/renderer/components/+catalog/catalog.module.css b/src/renderer/components/+catalog/catalog.module.css index b651814507..4c529a0934 100644 --- a/src/renderer/components/+catalog/catalog.module.css +++ b/src/renderer/components/+catalog/catalog.module.css @@ -63,6 +63,10 @@ max-width: unset; } +.badge:hover { + color: var(--textColorAccent); +} + .badge:not(:first-child) { margin-left: 0.5em; } diff --git a/src/renderer/components/+catalog/catalog.tsx b/src/renderer/components/+catalog/catalog.tsx index f904fe9d9c..a55b2ae0af 100644 --- a/src/renderer/components/+catalog/catalog.tsx +++ b/src/renderer/components/+catalog/catalog.tsx @@ -29,7 +29,6 @@ import { CatalogEntityItem, CatalogEntityStore } from "./catalog-entity.store"; import { navigate } from "../../navigation"; import { MenuItem, MenuActions } from "../menu"; import type { CatalogEntityContextMenu, CatalogEntityContextMenuContext } from "../../api/catalog-entity"; -import { Badge } from "../badge"; import { HotbarStore } from "../../../common/hotbar-store"; import { ConfirmDialog } from "../confirm-dialog"; import { catalogCategoryRegistry, CatalogEntity } from "../../../common/catalog"; @@ -208,7 +207,7 @@ export class Catalog extends React.Component { this.renderIcon(item), item.name, item.source, - item.labels.map((label) => ), + item.getLabelBadges(), { title: item.phase, className: cssNames(css[item.phase]) } ]} onDetails={this.onDetails} @@ -252,7 +251,7 @@ export class Catalog extends React.Component { item.name, item.kind, item.source, - item.labels.map((label) => ), + item.getLabelBadges(), { title: item.phase, className: cssNames(css[item.phase]) } ]} detailsItem={this.catalogEntityStore.selectedItem} @@ -274,15 +273,13 @@ export class Catalog extends React.Component { { this.catalogEntityStore.selectedItem - ? ( - this.catalogEntityStore.selectedItemId = null} - /> - ) - : ( - - ) + ? this.catalogEntityStore.selectedItemId = null} + /> + : } ); diff --git a/src/renderer/components/badge/badge.scss b/src/renderer/components/badge/badge.scss index 4e7f9702f6..23f8e99ff0 100644 --- a/src/renderer/components/badge/badge.scss +++ b/src/renderer/components/badge/badge.scss @@ -36,4 +36,8 @@ &.small { font-size: $font-size-small; } -} \ No newline at end of file + + &.clickable { + cursor: pointer; + } +} diff --git a/src/renderer/components/badge/badge.tsx b/src/renderer/components/badge/badge.tsx index 57d1e8917a..14959b04d1 100644 --- a/src/renderer/components/badge/badge.tsx +++ b/src/renderer/components/badge/badge.tsx @@ -35,9 +35,10 @@ export interface BadgeProps extends React.HTMLAttributes, TooltipDecoratorP export class Badge extends React.Component { render() { const { className, label, small, flat, children, ...elemProps } = this.props; + const clickable = Boolean(this.props.onClick); return <> - + {label} {children} diff --git a/src/renderer/components/item-object-list/page-filters.store.ts b/src/renderer/components/item-object-list/page-filters.store.ts index f2d36a0da8..a6d840dc93 100644 --- a/src/renderer/components/item-object-list/page-filters.store.ts +++ b/src/renderer/components/item-object-list/page-filters.store.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { computed, observable, reaction, makeObservable } from "mobx"; +import { computed, observable, reaction, makeObservable, action } from "mobx"; import { autoBind } from "../../utils"; import { searchUrlParam } from "../input/search-input-url"; @@ -68,6 +68,7 @@ export class PageFiltersStore { return () => disposers.forEach(dispose => dispose()); } + @action addFilter(filter: Filter, begin = false) { if (begin) this.filters.unshift(filter); else { @@ -75,12 +76,17 @@ export class PageFiltersStore { } } + @action removeFilter(filter: Filter) { if (!this.filters.remove(filter)) { const filterCopy = this.filters.find(f => f.type === filter.type && f.value === filter.value); if (filterCopy) this.filters.remove(filterCopy); } + + if (filter.type === FilterType.SEARCH) { + searchUrlParam.clear(); + } } getByType(type: FilterType, value?: any): Filter { @@ -99,19 +105,26 @@ export class PageFiltersStore { return !this.isDisabled.get(type); } + @action disable(type: FilterType | FilterType[]) { [type].flat().forEach(type => this.isDisabled.set(type, true)); return () => this.enable(type); } + @action enable(type: FilterType | FilterType[]) { [type].flat().forEach(type => this.isDisabled.delete(type)); return () => this.disable(type); } + @action reset() { + if (this.isEnabled(FilterType.SEARCH)) { + searchUrlParam.clear(); + } + this.filters.length = 0; this.isDisabled.clear(); } diff --git a/src/renderer/themes/lens-light.json b/src/renderer/themes/lens-light.json index 5084b63c52..8893fe27ac 100644 --- a/src/renderer/themes/lens-light.json +++ b/src/renderer/themes/lens-light.json @@ -12,7 +12,7 @@ "textColorPrimary": "#555555", "textColorSecondary": "#51575d", "textColorTertiary": "#555555", - "textColorAccent": "#333333", + "textColorAccent": "#222222", "textColorDimmed": "#5557598c", "borderColor": "#c9cfd3", "borderFaintColor": "#dfdfdf",