From 271f8860e279da7b45471e47715d27af96aba057 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Thu, 4 May 2023 16:32:43 -0400 Subject: [PATCH] chore: Fixup remaining type errors from core Signed-off-by: Sebastian Malton --- .../src/common/__tests__/kube-helpers.test.ts | 4 +- .../src/common/catalog/category-registry.ts | 18 ++++--- packages/core/src/common/catalog/helpers.ts | 10 ++-- .../fs/validate-directory.injectable.ts | 8 ++- .../k8s-api/__tests__/ingress.api.test.ts | 6 +-- .../request-charts.injectable.ts | 3 +- .../utils/add-separator/add-separator.ts | 3 +- .../common/utils/cluster-id-url-parsing.ts | 2 +- .../find-composite/find-composite.ts | 8 +-- .../composite/get-composite/get-composite.ts | 22 ++++---- .../find-exactly-one/find-exactly-one.ts | 4 +- .../with-error-logging.test.ts | 2 +- .../src/extensions/lens-renderer-extension.ts | 2 +- .../installing-update-using-tray.test.ts | 2 +- .../features/catalog/entity-running.test.tsx | 8 ++- .../edit-namespace-from-new-tab.test.tsx | 2 +- .../cluster/storage/cluster-storage.test.ts | 14 ++--- .../helm-file-input/helm-file-input.tsx | 6 +-- .../features/hotbar/storage/common/hotbar.ts | 3 +- .../hotbar/storage/storage-technical.test.ts | 8 +-- .../features/pod-logs/download-logs.test.tsx | 3 +- .../__test__/kubeconfig-sync.test.ts | 4 +- .../helm/install-helm-chart.injectable.ts | 2 +- packages/core/src/main/prometheus/provider.ts | 6 +-- .../get-service-account-route.injectable.ts | 2 +- .../start-port-forward-route.injectable.ts | 10 +--- ...p-current-port-forward-route.injectable.ts | 4 +- .../__tests__/catalog-entity-registry.test.ts | 16 +++--- .../catalog/__tests__/custom-views.test.ts | 6 +-- .../src/renderer/components/chart/chart.tsx | 11 ++-- .../local-terminal-settings.tsx | 6 ++- .../components/cluster/cluster-metrics.tsx | 16 ++++-- .../selected-metrics-type.injectable.ts | 8 +-- .../dock/__test__/dock-store.test.ts | 3 +- .../renderer/components/dock/dock/store.ts | 10 ++-- .../__test__/log-resource-selector.test.tsx | 6 ++- .../dock/logs/__test__/log-search.test.tsx | 3 +- .../components/dock/logs/controls.tsx | 2 +- .../create-workload-logs-tab.injectable.ts | 12 +++-- .../renderer/components/dock/logs/list.tsx | 2 +- .../renderer/components/dock/logs/store.ts | 10 ++-- .../components/events/kube-event-icon.tsx | 12 ++--- .../src/renderer/components/events/store.ts | 4 +- .../unpack-extension.injectable.tsx | 4 +- .../attempt-install/validate-package.tsx | 2 +- .../item-object-list/page-filters/store.ts | 6 ++- .../kube-object-status-icon.tsx | 8 +-- .../src/renderer/components/list/list.tsx | 3 +- .../network-ingresses/ingress-charts.tsx | 10 ++-- .../renderer/components/nodes/node-charts.tsx | 28 +++++----- .../src/renderer/components/nodes/route.tsx | 4 +- .../components/scroll-spy/scroll-spy.tsx | 6 +-- .../components/select/select.test.tsx | 20 +++---- .../volume-claim-disk-chart.tsx | 8 +-- .../renderer/components/table/react-table.tsx | 2 +- .../src/renderer/components/table/sorting.ts | 6 ++- .../table/table-model/table-model.ts | 3 +- .../test-utils/get-application-builder.tsx | 2 +- .../components/virtual-list/virtual-list.tsx | 10 +++- .../__tests__/pod-tolerations.test.tsx | 6 ++- .../workloads-pods/container-charts.tsx | 20 +++---- .../components/workloads-pods/pod-charts.tsx | 16 +++--- .../src/renderer/navigation/page-param.ts | 4 +- ...oute-specific-component-injection-token.ts | 25 +++++++-- .../utilities/src/convertMemory.ts | 15 +++--- .../utilities/src/formatDuration.ts | 2 +- .../utilities/src/hash-set.ts | 53 +++---------------- .../utility-features/utilities/src/iter.ts | 39 ++++++++++++-- .../utilities/src/jsonPath.ts | 2 +- .../utilities/src/on-keyboard-shortcut.ts | 4 ++ .../utility-features/utilities/src/tuple.ts | 12 +++++ .../utilities/src/type-narrowing.ts | 10 ++-- 72 files changed, 345 insertions(+), 278 deletions(-) diff --git a/packages/core/src/common/__tests__/kube-helpers.test.ts b/packages/core/src/common/__tests__/kube-helpers.test.ts index d6466e4b1e..d304ab39d6 100644 --- a/packages/core/src/common/__tests__/kube-helpers.test.ts +++ b/packages/core/src/common/__tests__/kube-helpers.test.ts @@ -239,8 +239,8 @@ describe("kube helpers", () => { assert(result.isOk === true); expect(result.value.getCurrentContext()).toBe("minikube"); expect(result.value.contexts.length).toBe(2); - expect(result.value.contexts[0].name).toBe("minikube"); - expect(result.value.contexts[1].name).toBe("cluster-3"); + expect(result.value.contexts[0]?.name).toBe("minikube"); + expect(result.value.contexts[1]?.name).toBe("cluster-3"); }); }); }); diff --git a/packages/core/src/common/catalog/category-registry.ts b/packages/core/src/common/catalog/category-registry.ts index 3223cedf58..6c2a171c98 100644 --- a/packages/core/src/common/catalog/category-registry.ts +++ b/packages/core/src/common/catalog/category-registry.ts @@ -79,17 +79,23 @@ export class CatalogCategoryRegistry { } hasCategoryForEntity({ kind, apiVersion }: CatalogEntityData & CatalogEntityKindData): boolean { - const splitApiVersion = apiVersion.split("/"); - const group = splitApiVersion[0]; + const [group] = apiVersion.split("/"); + + if (!group) { + return false; + } return this.groupKinds.get(group)?.has(kind) ?? false; } - getCategoryForEntity(data: CatalogEntityData & CatalogEntityKindData): T | undefined { - const splitApiVersion = data.apiVersion.split("/"); - const group = splitApiVersion[0]; + getCategoryForEntity({ apiVersion, kind }: CatalogEntityData & CatalogEntityKindData): T | undefined { + const [group] = apiVersion.split("/"); - return this.getForGroupKind(group, data.kind); + if (!group) { + return undefined; + } + + return this.getForGroupKind(group, kind); } getByName(name: string) { diff --git a/packages/core/src/common/catalog/helpers.ts b/packages/core/src/common/catalog/helpers.ts index 3d34d1e509..27dcee1662 100644 --- a/packages/core/src/common/catalog/helpers.ts +++ b/packages/core/src/common/catalog/helpers.ts @@ -5,22 +5,22 @@ import type { CatalogEntity } from "./catalog-entity"; import GraphemeSplitter from "grapheme-splitter"; -import { hasOwnProperty, hasTypedProperty, isObject, isString, iter } from "@k8slens/utilities"; +import { hasLengthAtLeast, hasOwnProperty, hasTypedProperty, isObject, isString, iter } from "@k8slens/utilities"; -function getNameParts(name: string): string[] { +function getNameParts(name: string): [string, ...string[]] { const byWhitespace = name.split(/\s+/); - if (byWhitespace.length > 1) { + if (hasLengthAtLeast(byWhitespace, 1)) { return byWhitespace; } const byDashes = name.split(/[-_]+/); - if (byDashes.length > 1) { + if (hasLengthAtLeast(byDashes, 1)) { return byDashes; } - return name.split(/@+/); + return name.split(/@+/) as [string, ...string[]]; } export function limitGraphemeLengthOf(src: string, count: number): string { diff --git a/packages/core/src/common/fs/validate-directory.injectable.ts b/packages/core/src/common/fs/validate-directory.injectable.ts index d6af229837..670e4c22cd 100644 --- a/packages/core/src/common/fs/validate-directory.injectable.ts +++ b/packages/core/src/common/fs/validate-directory.injectable.ts @@ -9,7 +9,7 @@ import type { Stats } from "fs-extra"; import { lowerFirst } from "lodash/fp"; import statInjectable from "./stat.injectable"; -export type ValidateDirectory = (path: string) => AsyncResult; +export type ValidateDirectory = (path: string) => AsyncResult; function getUserReadableFileType(stats: Stats): string { if (stats.isFile()) { @@ -46,7 +46,7 @@ const validateDirectoryInjectable = getInjectable({ const stats = await stat(path); if (stats.isDirectory()) { - return { isOk: true, value: undefined }; + return { isOk: true }; } return { isOk: false, error: `the provided path is ${getUserReadableFileType(stats)} and not a directory.` }; @@ -63,9 +63,7 @@ const validateDirectoryInjectable = getInjectable({ ENOTDIR: "a prefix of the provided path is not a directory.", }; - const humanReadableError = error.code - ? humanReadableErrors[error.code] - : lowerFirst(String(error)); + const humanReadableError = humanReadableErrors[String(error.code)] ?? lowerFirst(String(error)); return { isOk: false, error: humanReadableError }; } diff --git a/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts b/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts index 96c2dfce7a..57b947b6e9 100644 --- a/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts +++ b/packages/core/src/common/k8s-api/__tests__/ingress.api.test.ts @@ -83,7 +83,7 @@ describe("computeRuleDeclarations", () => { }, }); - expect(result[0].url).toBe("http://foo.bar/"); + expect(result[0]?.url).toBe("http://foo.bar/"); }); it("given no tls entries, should format links as http://", () => { @@ -120,7 +120,7 @@ describe("computeRuleDeclarations", () => { }, }); - expect(result[0].url).toBe("http://foo.bar/"); + expect(result[0]?.url).toBe("http://foo.bar/"); }); it("given some tls entries, should format links as https://", () => { @@ -159,6 +159,6 @@ describe("computeRuleDeclarations", () => { }, }); - expect(result[0].url).toBe("https://foo.bar/"); + expect(result[0]?.url).toBe("https://foo.bar/"); }); }); diff --git a/packages/core/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts b/packages/core/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts index 246b628ea0..94da23fbb1 100644 --- a/packages/core/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts +++ b/packages/core/src/common/k8s-api/endpoints/helm-charts.api/request-charts.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { RawHelmChart } from "../helm-charts.api"; import { HelmChart } from "../helm-charts.api"; -import { isDefined } from "@k8slens/utilities"; +import { hasLengthAtLeast, isDefined } from "@k8slens/utilities"; import apiBaseInjectable from "../../api-base.injectable"; export type RequestHelmCharts = () => Promise; @@ -25,6 +25,7 @@ const requestHelmChartsInjectable = getInjectable({ return Object .values(data) .reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), new Array()) + .filter((charts): charts is [HelmChart, ...HelmChart[]] => hasLengthAtLeast(charts, 1)) .map(([chart]) => HelmChart.create(chart, { onError: "log" })) .filter(isDefined); }; diff --git a/packages/core/src/common/utils/add-separator/add-separator.ts b/packages/core/src/common/utils/add-separator/add-separator.ts index 6fd6b14642..fd7b5186ad 100644 --- a/packages/core/src/common/utils/add-separator/add-separator.ts +++ b/packages/core/src/common/utils/add-separator/add-separator.ts @@ -19,7 +19,8 @@ const toSeparatedTupleUsing = return [leftItem]; } - const rightItem = arr[index + 1]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const rightItem = arr[index + 1]!; const separator = getSeparator(leftItem, rightItem); return [leftItem, separator]; diff --git a/packages/core/src/common/utils/cluster-id-url-parsing.ts b/packages/core/src/common/utils/cluster-id-url-parsing.ts index 393a791f09..75de80affe 100644 --- a/packages/core/src/common/utils/cluster-id-url-parsing.ts +++ b/packages/core/src/common/utils/cluster-id-url-parsing.ts @@ -12,7 +12,7 @@ import type { ClusterId } from "../cluster-types"; */ export function getClusterIdFromHost(host: string): ClusterId | undefined { // e.g host == "%clusterId.localhost:45345" - const subDomains = host.split(":")[0].split("."); + const subDomains = host.split(":")[0]?.split(".") ?? []; return subDomains.slice(-3, -2)[0]; // ClusterId or undefined } diff --git a/packages/core/src/common/utils/composite/find-composite/find-composite.ts b/packages/core/src/common/utils/composite/find-composite/find-composite.ts index 71dcf5baa3..3b2af82816 100644 --- a/packages/core/src/common/utils/composite/find-composite/find-composite.ts +++ b/packages/core/src/common/utils/composite/find-composite/find-composite.ts @@ -8,7 +8,7 @@ const _findComposite = (currentLeftIds: string[], currentId: string, currentR const [nextId, ...nextRightIds] = currentRightIds; const nextLeftIds = [...currentLeftIds, currentId]; - if (currentRightIds.length === 0 && composite.id === currentId) { + if (!nextId || composite.id === currentId) { return composite; } @@ -26,11 +26,11 @@ Node '${[...currentLeftIds, composite.id].join(" -> ")}' had only following chil ${composite.children.map((child) => child.id).join("\n")}`); }; -export const findComposite = - (...path: string[]) => +export const findComposite = (...path: [string, ...string[]]) => ( (composite: Composite): Composite => { const [currentId, ...rightIds] = path; const leftIds: string[] = []; return _findComposite(leftIds, currentId, rightIds, composite); - }; + } +); diff --git a/packages/core/src/common/utils/composite/get-composite/get-composite.ts b/packages/core/src/common/utils/composite/get-composite/get-composite.ts index 7e499ad40a..0b9ffe0e56 100644 --- a/packages/core/src/common/utils/composite/get-composite/get-composite.ts +++ b/packages/core/src/common/utils/composite/get-composite/get-composite.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import { hasLengthAtLeast } from "@k8slens/utilities"; import { pipeline } from "@ogre-tools/fp"; import { countBy, @@ -119,11 +120,11 @@ export const getCompositeFor = ({ const roots = source.filter(isRootId); - if (roots.length > 1) { + if (!hasLengthAtLeast(roots, 1)) { + const ids = roots.map(getId).join('", "'); + throw new Error( - `Tried to get a composite, but multiple roots where encountered: "${roots - .map(getId) - .join('", "')}"`, + `Tried to get a composite, but multiple roots where encountered: "${ids}"`, ); } @@ -139,9 +140,12 @@ const throwMissingParentIds = ({ missingParentIds, availableParentIds, }: ParentIdsForHandling) => { - throw new Error( - `Tried to get a composite but encountered missing parent ids: "${missingParentIds.join( - '", "', - )}".\n\nAvailable parent ids are:\n"${availableParentIds.join('",\n"')}"`, - ); + const missingIds = missingParentIds.join('", "'); + const availableIds = availableParentIds.join('", "'); + + throw new Error([ + `Tried to get a composite but encountered missing parent ids: "${missingIds}".`, + "", + `Available parent ids are: "${availableIds}"`, + ].join("\n")); }; diff --git a/packages/core/src/common/utils/find-exactly-one/find-exactly-one.ts b/packages/core/src/common/utils/find-exactly-one/find-exactly-one.ts index 181720d8b1..62dde751a0 100644 --- a/packages/core/src/common/utils/find-exactly-one/find-exactly-one.ts +++ b/packages/core/src/common/utils/find-exactly-one/find-exactly-one.ts @@ -2,6 +2,8 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import { hasLengthAtLeast } from "@k8slens/utilities"; + export const findExactlyOne = (predicate: (item: T) => boolean) => (collection: T[]): T => { const itemsFound = collection.filter(predicate); @@ -11,7 +13,7 @@ export const findExactlyOne = (predicate: (item: T) => boolean) => (collectio ); } - if (itemsFound.length > 1) { + if (!hasLengthAtLeast(itemsFound, 1)) { throw new Error( "Tried to find exactly one, but found many", ); diff --git a/packages/core/src/common/utils/with-error-logging/with-error-logging.test.ts b/packages/core/src/common/utils/with-error-logging/with-error-logging.test.ts index 2dbaffc08e..715c838475 100644 --- a/packages/core/src/common/utils/with-error-logging/with-error-logging.test.ts +++ b/packages/core/src/common/utils/with-error-logging/with-error-logging.test.ts @@ -10,7 +10,7 @@ import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import { getPromiseStatus } from "@k8slens/test-utils"; import logErrorInjectable from "../../log-error.injectable"; -import type { Logger } from "../../logger"; +import type { Logger } from "@k8slens/logger"; describe("with-error-logging", () => { describe("given decorated sync function", () => { diff --git a/packages/core/src/extensions/lens-renderer-extension.ts b/packages/core/src/extensions/lens-renderer-extension.ts index 62944884e4..f1c09729d2 100644 --- a/packages/core/src/extensions/lens-renderer-extension.ts +++ b/packages/core/src/extensions/lens-renderer-extension.ts @@ -125,7 +125,7 @@ export class LensRendererExtension extends LensExtension { object.entries(params), map(([key, value]) => [ key, - normalizedParams[key].stringify(value), + normalizedParams[key]?.stringify(value), ]), fromPairs, ); diff --git a/packages/core/src/features/application-update/child-features/application-update-using-tray/installing-update-using-tray.test.ts b/packages/core/src/features/application-update/child-features/application-update-using-tray/installing-update-using-tray.test.ts index b8b0260b34..406d64b636 100644 --- a/packages/core/src/features/application-update/child-features/application-update-using-tray/installing-update-using-tray.test.ts +++ b/packages/core/src/features/application-update/child-features/application-update-using-tray/installing-update-using-tray.test.ts @@ -225,7 +225,7 @@ describe("installing update using tray", () => { }); it("when download progresses with decimals, percentage increases as integers", () => { - downloadPlatformUpdateMock.mock.calls[0][0]({ percentage: 42.424242 }); + downloadPlatformUpdateMock.mock.calls[0]?.[0]({ percentage: 42.424242 }); expect( builder.tray.get("check-for-updates")?.label, diff --git a/packages/core/src/features/catalog/entity-running.test.tsx b/packages/core/src/features/catalog/entity-running.test.tsx index fc53289cc0..d3d39b003e 100644 --- a/packages/core/src/features/catalog/entity-running.test.tsx +++ b/packages/core/src/features/catalog/entity-running.test.tsx @@ -140,13 +140,11 @@ describe("entity running technical tests", () => { }); it("calls on before run event", () => { - const target = onBeforeRunMock.mock.calls[0][0].target; - const actual = { id: target.getId(), name: target.getName() }; - - expect(actual).toEqual({ + expect(onBeforeRunMock).toHaveBeenCalled(); + expect(onBeforeRunMock).toHaveBeenCalledWith(expect.objectContaining({ id: "a_catalogEntity_uid", name: "a catalog entity", - }); + })); }); it("does not call onRun yet", () => { diff --git a/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx b/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx index 3e623ead48..66b9aee5c8 100644 --- a/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx +++ b/packages/core/src/features/cluster/namespaces/edit-namespace-from-new-tab.test.tsx @@ -546,7 +546,7 @@ metadata: ) as Record>; expect( - actual.edit_resource_store["some-first-tab-id"], + actual.edit_resource_store?.["some-first-tab-id"], ).toEqual({ resource: "/api/some-api-version/namespaces/some-uid", firstDraft: `apiVersion: some-api-version diff --git a/packages/core/src/features/cluster/storage/cluster-storage.test.ts b/packages/core/src/features/cluster/storage/cluster-storage.test.ts index a92928605d..2527ed1829 100644 --- a/packages/core/src/features/cluster/storage/cluster-storage.test.ts +++ b/packages/core/src/features/cluster/storage/cluster-storage.test.ts @@ -220,11 +220,11 @@ describe("cluster storage technical tests", () => { const storedClusters = clusters.get(); expect(storedClusters.length).toBe(3); - expect(storedClusters[0].id).toBe("cluster1"); - expect(storedClusters[0].preferences.terminalCWD).toBe("/foo"); - expect(storedClusters[1].id).toBe("cluster2"); - expect(storedClusters[1].preferences.terminalCWD).toBe("/foo2"); - expect(storedClusters[2].id).toBe("cluster3"); + expect(storedClusters[0]?.id).toBe("cluster1"); + expect(storedClusters[0]?.preferences.terminalCWD).toBe("/foo"); + expect(storedClusters[1]?.id).toBe("cluster2"); + expect(storedClusters[1]?.preferences.terminalCWD).toBe("/foo2"); + expect(storedClusters[2]?.id).toBe("cluster3"); }); }); @@ -259,13 +259,13 @@ describe("cluster storage technical tests", () => { }); it("migrates to modern format with kubeconfig in a file", () => { - const configPath = clusters.get()[0].kubeConfigPath.get(); + const configPath = clusters.get()[0]?.kubeConfigPath.get() ?? ""; expect(readFileSync(configPath)).toBe(minimalValidKubeConfig); }); it("migrates to modern format with icon not in file", () => { - expect(clusters.get()[0].preferences.icon).toMatch(/data:;base64,/); + expect(clusters.get()[0]?.preferences.icon).toMatch(/data:;base64,/); }); }); }); diff --git a/packages/core/src/features/helm-charts/child-features/preferences/renderer/adding-of-custom-helm-repository/helm-file-input/helm-file-input.tsx b/packages/core/src/features/helm-charts/child-features/preferences/renderer/adding-of-custom-helm-repository/helm-file-input/helm-file-input.tsx index 26f63d57fc..b0e52ef657 100644 --- a/packages/core/src/features/helm-charts/child-features/preferences/renderer/adding-of-custom-helm-repository/helm-file-input/helm-file-input.tsx +++ b/packages/core/src/features/helm-charts/child-features/preferences/renderer/adding-of-custom-helm-repository/helm-file-input/helm-file-input.tsx @@ -49,9 +49,9 @@ const NonInjectedHelmFileInput = ({ name: placeholder, extensions: fileExtensions, }, - onPick: (filePaths) => { - if (filePaths.length) { - setValue(filePaths[0]); + onPick: ([filePath]) => { + if (filePath) { + setValue(filePath); } }, })} diff --git a/packages/core/src/features/hotbar/storage/common/hotbar.ts b/packages/core/src/features/hotbar/storage/common/hotbar.ts index dfe8a1bef8..6331c42605 100644 --- a/packages/core/src/features/hotbar/storage/common/hotbar.ts +++ b/packages/core/src/features/hotbar/storage/common/hotbar.ts @@ -70,7 +70,8 @@ export class Hotbar { from >= this.items.length || to >= this.items.length || isNaN(from) || - isNaN(to) + isNaN(to) || + !source ) { throw new Error("Invalid 'from' or 'to' arguments"); } diff --git a/packages/core/src/features/hotbar/storage/storage-technical.test.ts b/packages/core/src/features/hotbar/storage/storage-technical.test.ts index 026ef1bd5f..3b42a093d0 100644 --- a/packages/core/src/features/hotbar/storage/storage-technical.test.ts +++ b/packages/core/src/features/hotbar/storage/storage-technical.test.ts @@ -359,15 +359,15 @@ describe("Hotbars technical tests", () => { }); it("clears cells without entity", () => { - const items = hotbars.get()[0].items; + const items = hotbars.get()[0]?.items; - expect(items[2]).toBeNull(); + expect(items?.[2]).toBeNull(); }); it("adds extra data to cells with according entity", () => { - const items = hotbars.get()[0].items; + const items = hotbars.get()[0]?.items; - expect(items[0]).toEqual({ + expect(items?.[0]).toEqual({ entity: { name: "my-aws-cluster", source: "local", diff --git a/packages/core/src/features/pod-logs/download-logs.test.tsx b/packages/core/src/features/pod-logs/download-logs.test.tsx index 67b7265611..2ac1e7cd6f 100644 --- a/packages/core/src/features/pod-logs/download-logs.test.tsx +++ b/packages/core/src/features/pod-logs/download-logs.test.tsx @@ -67,7 +67,8 @@ describe("download logs options in logs dock tab", () => { windowDi.override(reloadLogsInjectable, () => jest.fn()); windowDi.override(getLogTabDataInjectable, () => () => ({ selectedPodId: selectedPod.getId(), - selectedContainer: selectedPod.getContainers()[0].name, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + selectedContainer: selectedPod.getContainers()[0]!.name, namespace: "default", showPrevious: true, showTimestamps: false, diff --git a/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 881d8ff627..97f0f60d2a 100644 --- a/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -97,8 +97,8 @@ describe("kubeconfig-sync.source tests", () => { const models = configToModels(config, "/bar"); expect(models.length).toBe(1); - expect(models[0].contextName).toBe("context-name"); - expect(models[0].kubeConfigPath).toBe("/bar"); + expect(models[0]?.contextName).toBe("context-name"); + expect(models[0]?.kubeConfigPath).toBe("/bar"); }); }); diff --git a/packages/core/src/main/helm/install-helm-chart.injectable.ts b/packages/core/src/main/helm/install-helm-chart.injectable.ts index 057ab521cb..0fc16302da 100644 --- a/packages/core/src/main/helm/install-helm-chart.injectable.ts +++ b/packages/core/src/main/helm/install-helm-chart.injectable.ts @@ -74,7 +74,7 @@ const installHelmChartInjectable = getInjectable({ } const output = result.value; - const releaseName = output.split("\n")[0].split(" ")[1].trim(); + const releaseName = output.split("\n")[0]?.split(" ")[1]?.trim() ?? ""; return { log: output, diff --git a/packages/core/src/main/prometheus/provider.ts b/packages/core/src/main/prometheus/provider.ts index e7ab5f05b5..b557d99af9 100644 --- a/packages/core/src/main/prometheus/provider.ts +++ b/packages/core/src/main/prometheus/provider.ts @@ -5,7 +5,7 @@ import type { CoreV1Api } from "@kubernetes/client-node"; import { getInjectionToken } from "@ogre-tools/injectable"; -import { isRequestError } from "@k8slens/utilities"; +import { hasLengthAtLeast, isRequestError } from "@k8slens/utilities"; export interface PrometheusService extends PrometheusServiceInfo { kind: string; @@ -54,7 +54,7 @@ export async function findFirstNamespacedService(client: CoreV1Api, ...selectors for (const selector of selectors) { const { body: { items: [service] }} = await client.listServiceForAllNamespaces(undefined, undefined, undefined, selector); - if (service?.metadata?.namespace && service.metadata.name && service.spec?.ports) { + if (service?.metadata?.namespace && service.metadata.name && service.spec?.ports && hasLengthAtLeast(service.spec.ports, 1)) { return { namespace: service.metadata.namespace, service: service.metadata.name, @@ -73,7 +73,7 @@ export async function findNamespacedService(client: CoreV1Api, name: string, nam try { const { body: service } = await client.readNamespacedService(name, namespace); - if (!service.metadata?.namespace || !service.metadata.name || !service.spec?.ports) { + if (!service.metadata?.namespace || !service.metadata.name || !service.spec?.ports || !hasLengthAtLeast(service.spec.ports, 1)) { throw new Error(`Service found in namespace="${namespace}" did not have required information`); } diff --git a/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts b/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts index f79e081e07..c81debf261 100644 --- a/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts +++ b/packages/core/src/main/routes/kubeconfig-route/get-service-account-route.injectable.ts @@ -66,7 +66,7 @@ const getServiceAccountRouteInjectable = getRouteInjectable({ { name: params.account, user: { - token: Buffer.from(token, "base64").toString("utf8"), + token: token ? Buffer.from(token, "base64").toString("utf8") : undefined, }, }, ], diff --git a/packages/core/src/main/routes/port-forward/start-port-forward-route.injectable.ts b/packages/core/src/main/routes/port-forward/start-port-forward-route.injectable.ts index ac08aa74ac..7e72b1f6c4 100644 --- a/packages/core/src/main/routes/port-forward/start-port-forward-route.injectable.ts +++ b/packages/core/src/main/routes/port-forward/start-port-forward-route.injectable.ts @@ -67,11 +67,7 @@ const startPortForwardRouteInjectable = getRouteInjectable({ }); return { - error: { - message: `Failed to forward port ${port} to ${ - thePort ? forwardPort : "random port" - }`, - }, + error: `Failed to forward port ${port} to ${thePort ? forwardPort : "random port"}`, }; } } @@ -84,9 +80,7 @@ const startPortForwardRouteInjectable = getRouteInjectable({ ); return { - error: { - message: `Failed to forward port ${port}`, - }, + error: `Failed to forward port ${port}`, }; } }); diff --git a/packages/core/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts b/packages/core/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts index f750182143..f6970e6327 100644 --- a/packages/core/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts +++ b/packages/core/src/main/routes/port-forward/stop-current-port-forward-route.injectable.ts @@ -34,9 +34,7 @@ const stopCurrentPortForwardRouteInjectable = getRouteInjectable({ logger.error("[PORT-FORWARD-ROUTE]: error stopping a port-forward", { namespace, port, forwardPort, resourceType, resourceName }); return { - error: { - message: `error stopping a forward port ${port}`, - }, + error: `error stopping a forward port ${port}`, }; } }); diff --git a/packages/core/src/renderer/api/__tests__/catalog-entity-registry.test.ts b/packages/core/src/renderer/api/__tests__/catalog-entity-registry.test.ts index 08dacb8f5a..d0b91a3070 100644 --- a/packages/core/src/renderer/api/__tests__/catalog-entity-registry.test.ts +++ b/packages/core/src/renderer/api/__tests__/catalog-entity-registry.test.ts @@ -143,13 +143,14 @@ describe("CatalogEntityRegistry", () => { entityRegistry.updateItems(items); expect(entityRegistry.items.get().length).toEqual(1); - expect(entityRegistry.items.get()[0].status.phase).toEqual("disconnected"); + expect(entityRegistry.items.get()[0]?.status.phase).toEqual("disconnected"); - items[0].status.phase = "connected"; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + items[0]!.status.phase = "connected"; entityRegistry.updateItems(items); expect(entityRegistry.items.get().length).toEqual(1); - expect(entityRegistry.items.get()[0].status.phase).toEqual("connected"); + expect(entityRegistry.items.get()[0]?.status.phase).toEqual("connected"); }); it("updates activeEntity", () => { @@ -170,11 +171,12 @@ describe("CatalogEntityRegistry", () => { entityRegistry.updateItems(items); entityRegistry.activeEntity = entityRegistry.items.get()[0]; - expect(entityRegistry.activeEntity.status.phase).toEqual("disconnected"); + expect(entityRegistry.activeEntity?.status.phase).toEqual("disconnected"); - items[0].status.phase = "connected"; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + items[0]!.status.phase = "connected"; entityRegistry.updateItems(items); - expect(entityRegistry.activeEntity.status.phase).toEqual("connected"); + expect(entityRegistry.activeEntity?.status.phase).toEqual("connected"); }); it("removes deleted items", () => { @@ -213,7 +215,7 @@ describe("CatalogEntityRegistry", () => { items.splice(0, 1); entityRegistry.updateItems(items); expect(entityRegistry.items.get().length).toEqual(1); - expect(entityRegistry.items.get()[0].metadata.uid).toEqual("456"); + expect(entityRegistry.items.get()[0]?.metadata.uid).toEqual("456"); }); }); diff --git a/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts b/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts index c115f5c4b7..67dadf53a1 100644 --- a/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts +++ b/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts @@ -53,8 +53,8 @@ describe("Custom Category Views", () => { const customCategoryViews = di.inject(customCategoryViewsInjectable); const { after = [] } = customCategoryViews.get().get("foo")?.get("bar") ?? {}; - expect(after[0].View).toBe(component2); - expect(after[1].View).toBe(component1); + expect(after[0]?.View).toBe(component2); + expect(after[1]?.View).toBe(component1); }); it("should put put priority < 50 items in before", () => { @@ -91,6 +91,6 @@ describe("Custom Category Views", () => { const customCategoryViews = di.inject(customCategoryViewsInjectable); const { before = [] } = customCategoryViews.get().get("foo")?.get("bar") ?? {}; - expect(before[0].View).toBe(component1); + expect(before[0]?.View).toBe(component1); }); }); diff --git a/packages/core/src/renderer/components/chart/chart.tsx b/packages/core/src/renderer/components/chart/chart.tsx index 08c3808772..13a86328c7 100644 --- a/packages/core/src/renderer/components/chart/chart.tsx +++ b/packages/core/src/renderer/components/chart/chart.tsx @@ -126,10 +126,10 @@ export class Chart extends React.Component { // Mutating inner chart datasets to enable seamless transitions nextDatasets.forEach((next, datasetIndex) => { - const index = datasets.findIndex(set => set.id === next.id); + const dataset = datasets.find(set => set.id === next.id); - if (index !== -1) { - const data = datasets[index].data = (datasets[index].data ?? []).slice(); // "Clean" mobx observables data to use in ChartJS + if (dataset) { + const data = dataset.data = (dataset.data ?? []).slice(); // "Clean" mobx observables data to use in ChartJS const nextData = next.data ??= []; data.splice(next.data.length); @@ -143,10 +143,7 @@ export class Chart extends React.Component { void _data; - datasets[index] = { - ...datasets[index], - ...props, - }; + Object.assign(dataset, props); } else { datasets[datasetIndex] = next; } diff --git a/packages/core/src/renderer/components/cluster-settings/local-terminal-settings.tsx b/packages/core/src/renderer/components/cluster-settings/local-terminal-settings.tsx index d98a31ba93..3f4b1f08d0 100644 --- a/packages/core/src/renderer/components/cluster-settings/local-terminal-settings.tsx +++ b/packages/core/src/renderer/components/cluster-settings/local-terminal-settings.tsx @@ -93,7 +93,11 @@ const NonInjectedClusterLocalTerminalSetting = observer((props: Dependencies & C message: "Choose Working Directory", buttonLabel: "Pick", properties: ["openDirectory", "showHiddenFiles"], - onPick: ([directory]) => setAndCommitDirectory(directory), + onPick: ([directory]) => { + if (directory) { + setAndCommitDirectory(directory); + } + }, }); }; diff --git a/packages/core/src/renderer/components/cluster/cluster-metrics.tsx b/packages/core/src/renderer/components/cluster/cluster-metrics.tsx index 9f837e4306..09ff570149 100644 --- a/packages/core/src/renderer/components/cluster/cluster-metrics.tsx +++ b/packages/core/src/renderer/components/cluster/cluster-metrics.tsx @@ -7,9 +7,9 @@ import styles from "./cluster-metrics.module.scss"; import React, { useState } from "react"; import { observer } from "mobx-react"; -import type { ChartOptions, ChartPoint } from "chart.js"; +import type { ChartOptions } from "chart.js"; import { BarChart } from "../chart"; -import { bytesToUnits, cssNames } from "@k8slens/utilities"; +import { bytesToUnits, cssNames, isArray, isObject } from "@k8slens/utilities"; import { Spinner } from "../spinner"; import { ZebraStripesPlugin } from "../chart/zebra-stripes.plugin"; import { ClusterNoMetrics } from "./cluster-no-metrics"; @@ -71,7 +71,11 @@ const NonInjectedClusterMetrics = observer((props: Dependencies) => { return ""; } - const value = data.datasets?.[0].data?.[index] as ChartPoint; + const value = data.datasets?.[0]?.data?.[index]; + + if (!isObject(value) || isArray(value)) { + return ""; + } return value.y?.toString() ?? ""; }, @@ -94,7 +98,11 @@ const NonInjectedClusterMetrics = observer((props: Dependencies) => { return ""; } - const value = data.datasets?.[0].data?.[index] as ChartPoint; + const value = data.datasets?.[0]?.data?.[index]; + + if (!isObject(value) || isArray(value)) { + return ""; + } return bytesToUnits(parseInt(value.y as string), { precision: 3 }); }, diff --git a/packages/core/src/renderer/components/cluster/overview/selected-metrics-type.injectable.ts b/packages/core/src/renderer/components/cluster/overview/selected-metrics-type.injectable.ts index 2eb768dec4..13b94aa981 100644 --- a/packages/core/src/renderer/components/cluster/overview/selected-metrics-type.injectable.ts +++ b/packages/core/src/renderer/components/cluster/overview/selected-metrics-type.injectable.ts @@ -29,18 +29,18 @@ const selectedMetricsTypeInjectable = getInjectable({ switch (type) { case "cpu": - return normalizeMetrics(rawValue.cpuUsage).data.result[0].values; + return normalizeMetrics(rawValue.cpuUsage).data.result[0]?.values ?? []; case "memory": - return normalizeMetrics(rawValue.memoryUsage).data.result[0].values; + return normalizeMetrics(rawValue.memoryUsage).data.result[0]?.values ?? []; default: return []; } }); const hasCPUMetrics = computed(() => ( - normalizeMetrics(overviewMetrics.value.get()?.cpuUsage).data.result[0].values.length > 0 + (normalizeMetrics(overviewMetrics.value.get()?.cpuUsage).data.result[0]?.values.length ?? 0) > 0 )); const hasMemoryMetrics = computed(() => ( - normalizeMetrics(overviewMetrics.value.get()?.memoryUsage).data.result[0].values.length > 0 + (normalizeMetrics(overviewMetrics.value.get()?.memoryUsage).data.result[0]?.values.length ?? 0) > 0 )); return { diff --git a/packages/core/src/renderer/components/dock/__test__/dock-store.test.ts b/packages/core/src/renderer/components/dock/__test__/dock-store.test.ts index 92a0b735cc..c99bcee72e 100644 --- a/packages/core/src/renderer/components/dock/__test__/dock-store.test.ts +++ b/packages/core/src/renderer/components/dock/__test__/dock-store.test.ts @@ -32,7 +32,8 @@ describe("DockStore", () => { it("closes tab and selects one from right", () => { dockStore.tabs = initialTabs; - dockStore.closeTab(dockStore.tabs[0].id); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + dockStore.closeTab(dockStore.tabs[0]!.id); expect(dockStore.selectedTabId).toBe("create"); diff --git a/packages/core/src/renderer/components/dock/dock/store.ts b/packages/core/src/renderer/components/dock/dock/store.ts index 00a1133954..81f9908d5a 100644 --- a/packages/core/src/renderer/components/dock/dock/store.ts +++ b/packages/core/src/renderer/components/dock/dock/store.ts @@ -169,10 +169,6 @@ export class DockStore implements DockStorageState { this.dependencies.storage.merge({ selectedTabId: tabId }); } - @computed get tabsNumber() : number { - return this.tabs.length; - } - @computed get selectedTab() { return this.tabs.find(tab => tab.id === this.selectedTabId); } @@ -325,7 +321,11 @@ export class DockStore implements DockStorageState { if (this.selectedTabId === tab.id) { if (this.tabs.length) { - const newTab = tabIndex < this.tabsNumber ? this.tabs[tabIndex] : this.tabs[tabIndex - 1]; + const newTab = ( + tabIndex < this.tabs.length + ? this.tabs[tabIndex] + : this.tabs[this.tabs.length - 1] + ) as Required; this.selectTab(newTab.id); } else { diff --git a/packages/core/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx b/packages/core/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx index 1829167920..ba24cc8223 100644 --- a/packages/core/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx +++ b/packages/core/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx @@ -48,7 +48,8 @@ function getOnePodViewModel(tabId: TabId, deps: Partial ({ selectedPodId: selectedPod.getId(), - selectedContainer: selectedPod.getContainers()[0].name, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + selectedContainer: selectedPod.getContainers()[0]!.name, namespace: selectedPod.getNs(), showPrevious: false, showTimestamps: false, @@ -76,7 +77,8 @@ const getFewPodsTabData = (tabId: TabId, deps: Partial ({ selectedPodId: selectedPod.getId(), - selectedContainer: selectedPod.getContainers()[0].name, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + selectedContainer: selectedPod.getContainers()[0]!.name, namespace: selectedPod.getNs(), showPrevious: false, showTimestamps: false, diff --git a/packages/core/src/renderer/components/dock/logs/controls.tsx b/packages/core/src/renderer/components/dock/logs/controls.tsx index 79b2121efd..90075d83c9 100644 --- a/packages/core/src/renderer/components/dock/logs/controls.tsx +++ b/packages/core/src/renderer/components/dock/logs/controls.tsx @@ -26,7 +26,7 @@ export const LogControls = observer(({ model }: LogControlsProps) => { const logs = model.timestampSplitLogs.get(); const { showTimestamps, showPrevious: previous } = tabData; - const since = logs.length ? logs[0][0] : null; + const since = logs[0]?.[0] ?? null; const toggleTimestamps = () => { model.updateLogTabData({ showTimestamps: !showTimestamps }); diff --git a/packages/core/src/renderer/components/dock/logs/create-workload-logs-tab.injectable.ts b/packages/core/src/renderer/components/dock/logs/create-workload-logs-tab.injectable.ts index f41905ed2d..c417e158ce 100644 --- a/packages/core/src/renderer/components/dock/logs/create-workload-logs-tab.injectable.ts +++ b/packages/core/src/renderer/components/dock/logs/create-workload-logs-tab.injectable.ts @@ -9,6 +9,7 @@ import type { DaemonSet, Deployment, Job, ReplicaSet, StatefulSet } from "@k8sle import type { TabId } from "../dock/store"; import type { CreateLogsTabData } from "./create-logs-tab.injectable"; import createLogsTabInjectable from "./create-logs-tab.injectable"; +import { hasLengthAtLeast } from "@k8slens/utilities"; export interface WorkloadLogsTabData { workload: StatefulSet | Job | Deployment | DaemonSet | ReplicaSet; @@ -24,15 +25,20 @@ const createWorkloadLogsTab = ({ getPodsByOwnerId, }: Dependencies) => ({ workload }: WorkloadLogsTabData): TabId | undefined => { const pods = getPodsByOwnerId(workload.getId()); + const selectedPod = pods[0]; - if (pods.length === 0) { + if (!selectedPod) { return undefined; } - const selectedPod = pods[0]; + const containers = selectedPod.getAllContainers(); + + if (!hasLengthAtLeast(containers, 1)) { + return undefined; + } return createLogsTab(`${workload.kind} ${selectedPod.getName()}`, { - selectedContainer: selectedPod.getAllContainers()[0].name, + selectedContainer: containers[0].name, selectedPodId: selectedPod.getId(), namespace: selectedPod.getNs(), owner: { diff --git a/packages/core/src/renderer/components/dock/logs/list.tsx b/packages/core/src/renderer/components/dock/logs/list.tsx index 190f339d62..1226b44914 100644 --- a/packages/core/src/renderer/components/dock/logs/list.tsx +++ b/packages/core/src/renderer/components/dock/logs/list.tsx @@ -199,7 +199,7 @@ class NonForwardedLogList extends React.Component { const { isActiveOverlay } = this.props.model.searchStore; const searchQuery = this.props.model.searchStore.searchQuery.get(); - const item = this.logs[rowIndex]; + const item = this.logs[rowIndex] ?? ""; // If search is enabled, replace keyword with a background const contents = searchQuery diff --git a/packages/core/src/renderer/components/dock/logs/store.ts b/packages/core/src/renderer/components/dock/logs/store.ts index 1a9f0eb4a2..f66b4fd1f9 100644 --- a/packages/core/src/renderer/components/dock/logs/store.ts +++ b/packages/core/src/renderer/components/dock/logs/store.ts @@ -179,7 +179,7 @@ export class LogStore { */ getLastSinceTime(tabId: TabId): string { const logs = this.podLogs.get(tabId) ?? []; - const [timestamp] = getTimestamps(logs[logs.length - 1]) ?? []; + const [timestamp] = getTimestamps(logs[logs.length - 1] ?? "") ?? []; const stamp = timestamp ? new Date(timestamp) : new Date(); stamp.setSeconds(stamp.getSeconds() + 1); // avoid duplicates from last second @@ -201,11 +201,7 @@ export class LogStore { const removeTimestamps = (logs: string) => logs.replace(/^\d+.*?\s/gm, ""); const getTimestamps = (logs: string) => logs.match(/^\d+\S+/gm); const splitOutTimestamp = (logs: string): [string, string] => { - const extraction = /^(\d+\S+)(.*)/m.exec(logs); + const [,timestamp = "", log = logs]= [.../^(\d+\S+)(.*)/m.exec(logs) ?? []]; - if (!extraction || extraction.length < 3) { - return ["", logs]; - } - - return [extraction[1], extraction[2]]; + return [timestamp, log]; }; diff --git a/packages/core/src/renderer/components/events/kube-event-icon.tsx b/packages/core/src/renderer/components/events/kube-event-icon.tsx index 2bd2240d0f..cde835fcb0 100644 --- a/packages/core/src/renderer/components/events/kube-event-icon.tsx +++ b/packages/core/src/renderer/components/events/kube-event-icon.tsx @@ -30,16 +30,14 @@ class NonInjectedKubeEventIcon extends React.Component events, eventStore } = this.props; const events = eventStore.getEventsByObject(object); - let warnings = events.filter(evt => evt.isWarning()); + const warnings = filterEvents(events.filter(event => event.isWarning())); - if (filterEvents) warnings = filterEvents(warnings); + const [event] = [...warnings, ...events]; // get latest event - if (!events.length || (this.showWarningsOnly && !warnings.length)) { - return null; - } - const event = [...warnings, ...events][0]; // get latest event + if (!event) return null; + if (this.showWarningsOnly && !event.isWarning()) return null; return ( { const groupsByInvolvedObject = groupBy(warnings, warning => warning.involvedObject.uid); const eventsWithError = Object.values(groupsByInvolvedObject).map(events => { const recent = events[0]; - const { kind, uid } = recent.involvedObject; + const { kind, uid } = recent?.involvedObject ?? {}; - if (kind == Pod.kind) { // Wipe out running pods + if (kind == Pod.kind && uid) { // Wipe out running pods const pod = this.dependencies.getPodById(uid); if (!pod || (!pod.hasIssues() && (pod.spec?.priority ?? 0) < 500000)) { diff --git a/packages/core/src/renderer/components/extensions/attempt-install/unpack-extension.injectable.tsx b/packages/core/src/renderer/components/extensions/attempt-install/unpack-extension.injectable.tsx index 89305f776f..d20f2dcc89 100644 --- a/packages/core/src/renderer/components/extensions/attempt-install/unpack-extension.injectable.tsx +++ b/packages/core/src/renderer/components/extensions/attempt-install/unpack-extension.injectable.tsx @@ -7,7 +7,7 @@ import extensionLoaderInjectable from "../../../../extensions/extension-loader/e import getExtensionDestFolderInjectable from "./get-extension-dest-folder.injectable"; import extensionInstallationStateStoreInjectable from "../../../../extensions/extension-installation-state-store/extension-installation-state-store.injectable"; import type { Disposer } from "@k8slens/utilities"; -import { noop } from "@k8slens/utilities"; +import { hasLengthAtLeast, noop } from "@k8slens/utilities"; import { extensionDisplayName } from "../../../../extensions/lens-extension"; import { getMessageFromError } from "../get-message-from-error/get-message-from-error"; import path from "path"; @@ -63,7 +63,7 @@ const unpackExtensionInjectable = getInjectable({ const unpackedFiles = await fse.readdir(unpackingTempFolder); let unpackedRootFolder = unpackingTempFolder; - if (unpackedFiles.length === 1) { + if (hasLengthAtLeast(unpackedFiles, 1)) { // check if %extension.tgz was packed with single top folder, // e.g. "npm pack %ext_name" downloads file with "package" root folder within tarball unpackedRootFolder = path.join(unpackingTempFolder, unpackedFiles[0]); diff --git a/packages/core/src/renderer/components/extensions/attempt-install/validate-package.tsx b/packages/core/src/renderer/components/extensions/attempt-install/validate-package.tsx index 11d6cfb19a..21ed1a184a 100644 --- a/packages/core/src/renderer/components/extensions/attempt-install/validate-package.tsx +++ b/packages/core/src/renderer/components/extensions/attempt-install/validate-package.tsx @@ -17,7 +17,7 @@ export async function validatePackage(filePath: string): Promise entry.startsWith(rootFolder)); const manifestLocation = packedInRootFolder ? path.join(rootFolder, manifestFilename) diff --git a/packages/core/src/renderer/components/item-object-list/page-filters/store.ts b/packages/core/src/renderer/components/item-object-list/page-filters/store.ts index 05b885a25c..a1612c5f3d 100644 --- a/packages/core/src/renderer/components/item-object-list/page-filters/store.ts +++ b/packages/core/src/renderer/components/item-object-list/page-filters/store.ts @@ -37,7 +37,11 @@ export class PageFiltersStore { protected syncWithGlobalSearch() { const disposers = [ - reaction(() => this.getValues(FilterType.SEARCH)[0], search => this.dependencies.searchUrlParam.set(search)), + reaction(() => this.getValues(FilterType.SEARCH)[0], search => { + if (search) { + this.dependencies.searchUrlParam.set(search); + } + }), reaction(() => this.dependencies.searchUrlParam.get(), search => { const filter = this.getByType(FilterType.SEARCH); diff --git a/packages/core/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx b/packages/core/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx index 14f6cb5908..b5f1880f6b 100644 --- a/packages/core/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx +++ b/packages/core/src/renderer/components/kube-object-status-icon/kube-object-status-icon.tsx @@ -17,7 +17,7 @@ import type { IComputedValue } from "mobx"; import { observer } from "mobx-react"; import type { KubeObjectStatusText } from "./kube-object-status-text-injection-token"; -function statusClassName(level: KubeObjectStatusLevel): string { +function statusClassName(level: KubeObjectStatusLevel | undefined): string | undefined { switch (level) { case KubeObjectStatusLevel.INFO: return "info"; @@ -26,6 +26,8 @@ function statusClassName(level: KubeObjectStatusLevel): string { case KubeObjectStatusLevel.CRITICAL: return "error"; } + + return undefined; } function statusTitle(level: KubeObjectStatusLevel): string { @@ -46,7 +48,7 @@ function getAge(timestamp: string | undefined) { } interface SplitStatusesByLevel { - maxLevel: string; + maxLevel: string | undefined; criticals: KubeObjectStatus[]; warnings: KubeObjectStatus[]; infos: KubeObjectStatus[]; @@ -69,7 +71,7 @@ function splitByLevel(statuses: KubeObjectStatus[]): SplitStatusesByLevel { const criticals = parts.get(KubeObjectStatusLevel.CRITICAL) ?? []; const warnings = parts.get(KubeObjectStatusLevel.WARNING) ?? []; const infos = parts.get(KubeObjectStatusLevel.INFO) ?? []; - const maxLevel = statusClassName(criticals[0]?.level ?? warnings[0]?.level ?? infos[0].level); + const maxLevel = statusClassName(criticals[0]?.level ?? warnings[0]?.level ?? infos[0]?.level); return { maxLevel, criticals, warnings, infos }; } diff --git a/packages/core/src/renderer/components/list/list.tsx b/packages/core/src/renderer/components/list/list.tsx index b247cf89b9..b778bd525f 100644 --- a/packages/core/src/renderer/components/list/list.tsx +++ b/packages/core/src/renderer/components/list/list.tsx @@ -24,7 +24,8 @@ export function List({ columns, data, title, items, filters const query = search.toLowerCase(); const filteredData = data.filter((item, index) => ( - filters.some(getText => String(getText(items[index])).toLowerCase().includes(query)) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + filters.some(getText => String(getText(items[index]!)).toLowerCase().includes(query)) )); return ( diff --git a/packages/core/src/renderer/components/network-ingresses/ingress-charts.tsx b/packages/core/src/renderer/components/network-ingresses/ingress-charts.tsx index cc8d02ee76..79765bf726 100644 --- a/packages/core/src/renderer/components/network-ingresses/ingress-charts.tsx +++ b/packages/core/src/renderer/components/network-ingresses/ingress-charts.tsx @@ -21,7 +21,7 @@ export const IngressCharts = observer(() => { const id = object.getId(); const values = Object.values(metrics) .map(normalizeMetrics) - .map(({ data }) => data.result[0].values); + .map(({ data }) => data.result[0]?.values); const [ bytesSentSuccess, bytesSentFailure, @@ -36,14 +36,14 @@ export const IngressCharts = observer(() => { label: `Bytes sent, status 2xx`, tooltip: `Bytes sent by Ingress controller with successful status`, borderColor: "#46cd9e", - data: bytesSentSuccess.map(([x, y]) => ({ x, y })), + data: bytesSentSuccess?.map(([x, y]) => ({ x, y })), }, { id: `${id}-bytesSentFailure`, label: `Bytes sent, status 5xx`, tooltip: `Bytes sent by Ingress controller with error status`, borderColor: "#cd465a", - data: bytesSentFailure.map(([x, y]) => ({ x, y })), + data: bytesSentFailure?.map(([x, y]) => ({ x, y })), }, ], Duration: [ @@ -52,14 +52,14 @@ export const IngressCharts = observer(() => { label: `Request`, tooltip: `Request duration in seconds`, borderColor: "#48b18d", - data: requestDurationSeconds.map(([x, y]) => ({ x, y })), + data: requestDurationSeconds?.map(([x, y]) => ({ x, y })), }, { id: `${id}-responseDurationSeconds`, label: `Response`, tooltip: `Response duration in seconds`, borderColor: "#73ba3c", - data: responseDurationSeconds.map(([x, y]) => ({ x, y })), + data: responseDurationSeconds?.map(([x, y]) => ({ x, y })), }, ], }; diff --git a/packages/core/src/renderer/components/nodes/node-charts.tsx b/packages/core/src/renderer/components/nodes/node-charts.tsx index 6360a7bad0..c79732b85d 100644 --- a/packages/core/src/renderer/components/nodes/node-charts.tsx +++ b/packages/core/src/renderer/components/nodes/node-charts.tsx @@ -44,7 +44,7 @@ const NonInjectedNodeCharts = observer((props: Dependencies) => { podCapacity, fsSize, fsUsage, - } = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values); + } = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0]?.values); const datasets: Partial> = { CPU: [ @@ -53,28 +53,28 @@ const NonInjectedNodeCharts = observer((props: Dependencies) => { label: `Usage`, tooltip: `CPU cores usage`, borderColor: "#3D90CE", - data: cpuUsage.map(([x, y]) => ({ x, y })), + data: cpuUsage?.map(([x, y]) => ({ x, y })), }, { id: `${id}-cpuRequests`, label: `Requests`, tooltip: `CPU requests`, borderColor: "#30b24d", - data: cpuRequests.map(([x, y]) => ({ x, y })), + data: cpuRequests?.map(([x, y]) => ({ x, y })), }, { id: `${id}-cpuAllocatableCapacity`, label: `Allocatable Capacity`, tooltip: `CPU allocatable capacity`, borderColor: "#032b4d", - data: cpuAllocatableCapacity.map(([x, y]) => ({ x, y })), + data: cpuAllocatableCapacity?.map(([x, y]) => ({ x, y })), }, { id: `${id}-cpuCapacity`, label: `Capacity`, tooltip: `CPU capacity`, borderColor: chartCapacityColor, - data: cpuCapacity.map(([x, y]) => ({ x, y })), + data: cpuCapacity?.map(([x, y]) => ({ x, y })), }, ], Memory: [ @@ -83,35 +83,35 @@ const NonInjectedNodeCharts = observer((props: Dependencies) => { label: `Usage`, tooltip: `Memory usage`, borderColor: "#c93dce", - data: memoryUsage.map(([x, y]) => ({ x, y })), + data: memoryUsage?.map(([x, y]) => ({ x, y })), }, { id: `${id}-workloadMemoryUsage`, label: `Workload Memory Usage`, tooltip: `Workload memory usage`, borderColor: "#9cd3ce", - data: workloadMemoryUsage.map(([x, y]) => ({ x, y })), + data: workloadMemoryUsage?.map(([x, y]) => ({ x, y })), }, { id: "memoryRequests", label: `Requests`, tooltip: `Memory requests`, borderColor: "#30b24d", - data: memoryRequests.map(([x, y]) => ({ x, y })), + data: memoryRequests?.map(([x, y]) => ({ x, y })), }, { id: `${id}-memoryAllocatableCapacity`, label: `Allocatable Capacity`, tooltip: `Memory allocatable capacity`, borderColor: "#032b4d", - data: memoryAllocatableCapacity.map(([x, y]) => ({ x, y })), + data: memoryAllocatableCapacity?.map(([x, y]) => ({ x, y })), }, { id: `${id}-memoryCapacity`, label: `Capacity`, tooltip: `Memory capacity`, borderColor: chartCapacityColor, - data: memoryCapacity.map(([x, y]) => ({ x, y })), + data: memoryCapacity?.map(([x, y]) => ({ x, y })), }, ], Disk: [ @@ -120,14 +120,14 @@ const NonInjectedNodeCharts = observer((props: Dependencies) => { label: `Usage`, tooltip: `Node filesystem usage in bytes`, borderColor: "#ffc63d", - data: fsUsage.map(([x, y]) => ({ x, y })), + data: fsUsage?.map(([x, y]) => ({ x, y })), }, { id: `${id}-fsSize`, label: `Size`, tooltip: `Node filesystem size in bytes`, borderColor: chartCapacityColor, - data: fsSize.map(([x, y]) => ({ x, y })), + data: fsSize?.map(([x, y]) => ({ x, y })), }, ], Pods: [ @@ -136,14 +136,14 @@ const NonInjectedNodeCharts = observer((props: Dependencies) => { label: `Usage`, tooltip: `Number of running Pods`, borderColor: "#30b24d", - data: podUsage.map(([x, y]) => ({ x, y })), + data: podUsage?.map(([x, y]) => ({ x, y })), }, { id: `${id}-podCapacity`, label: `Capacity`, tooltip: `Node Pods capacity`, borderColor: chartCapacityColor, - data: podCapacity.map(([x, y]) => ({ x, y })), + data: podCapacity?.map(([x, y]) => ({ x, y })), }, ], }; diff --git a/packages/core/src/renderer/components/nodes/route.tsx b/packages/core/src/renderer/components/nodes/route.tsx index 1cca8e6e0f..9d79874c1b 100644 --- a/packages/core/src/renderer/components/nodes/route.tsx +++ b/packages/core/src/renderer/components/nodes/route.tsx @@ -6,7 +6,7 @@ import "./nodes.scss"; import React from "react"; import { observer } from "mobx-react"; -import { bytesToUnits, cssNames, hasLengthGreaterThan, interval } from "@k8slens/utilities"; +import { bytesToUnits, cssNames, hasLengthAtLeast, interval } from "@k8slens/utilities"; import { TabLayout } from "../layout/tab-layout-2"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import type { Node } from "@k8slens/kube-object"; @@ -104,7 +104,7 @@ class NonInjectedNodesRoute extends React.Component { private renderUsage({ node, title, metricNames, formatters }: UsageArgs) { const metrics = this.getLastMetricValues(node, metricNames); - if (!hasLengthGreaterThan(metrics, 2)) { + if (!hasLengthAtLeast(metrics, 2)) { return ; } diff --git a/packages/core/src/renderer/components/scroll-spy/scroll-spy.tsx b/packages/core/src/renderer/components/scroll-spy/scroll-spy.tsx index 9fb763d607..55ebb4851c 100644 --- a/packages/core/src/renderer/components/scroll-spy/scroll-spy.tsx +++ b/packages/core/src/renderer/components/scroll-spy/scroll-spy.tsx @@ -38,7 +38,7 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 }; const getSectionsParentElement = () => { - return sections.current?.[0].parentElement; + return sections.current?.[0]?.parentElement; }; const updateNavigation = () => { @@ -76,9 +76,9 @@ export const ScrollSpy = observer(({ render, htmlFor, rootMargin = "0px 0px -100 }; const handleIntersect = ([entry]: IntersectionObserverEntry[]) => { - const closest = entry.target.closest("section[id]"); + const closest = entry?.target.closest("section[id]"); - if (entry.isIntersecting && closest) { + if (entry?.isIntersecting && closest) { setActiveElementId(closest.id); } }; diff --git a/packages/core/src/renderer/components/select/select.test.tsx b/packages/core/src/renderer/components/select/select.test.tsx index 0c3738744f..ce66e2c494 100644 --- a/packages/core/src/renderer/components/select/select.test.tsx +++ b/packages/core/src/renderer/components/select/select.test.tsx @@ -67,14 +67,14 @@ describe(" )); const selectedValueContainer = container.querySelector(".Select__single-value"); - expect(selectedValueContainer?.textContent).toBe(options[0].label); + expect(selectedValueContainer?.textContent).toBe(options[0]?.label); }); it("should reflect to change value", () => { @@ -93,24 +93,24 @@ describe(" )); const selectedValueContainer = container.querySelector(".Select__single-value"); - expect(selectedValueContainer?.textContent).toBe(options[0].label); + expect(selectedValueContainer?.textContent).toBe(options[0]?.label); rerender(( ", () => { const { container, rerender } = render(( ", () => { const { container, rerender } = render((