chore: Fixup remaining type errors from core

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton committed 2023-06-01 09:27:46 -04:00
1 parent 894553cdea
commit 271f8860e2
72 files changed
+345 -278

No files matched your search

@@ -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");
});
});
});
@@ -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<T extends CatalogCategory>(data: CatalogEntityData & CatalogEntityKindData): T | undefined {
const splitApiVersion = data.apiVersion.split("/");
const group = splitApiVersion[0];
getCategoryForEntity<T extends CatalogCategory>({ 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) {
+5 -5
View File
@@ -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 {
@@ -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<undefined, string>;
export type ValidateDirectory = (path: string) => AsyncResult<void, string>;
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 };
}
@@ -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/");
});
});
@@ -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<HelmChart[]>;
@@ -25,6 +25,7 @@ const requestHelmChartsInjectable = getInjectable({
return Object
.values(data)
.reduce((allCharts, repoCharts) => allCharts.concat(Object.values(repoCharts)), new Array<RawHelmChart[]>())
.filter((charts): charts is [HelmChart, ...HelmChart[]] => hasLengthAtLeast(charts, 1))
.map(([chart]) => HelmChart.create(chart, { onError: "log" }))
.filter(isDefined);
};
@@ -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];
@@ -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
}
@@ -8,7 +8,7 @@ const _findComposite = <T>(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[]]) => (
<T>(composite: Composite<T>): Composite<T> => {
const [currentId, ...rightIds] = path;
const leftIds: string[] = [];
return _findComposite(leftIds, currentId, rightIds, composite);
};
}
);
@@ -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 = <T>({
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"));
};
@@ -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 = <T>(predicate: (item: T) => boolean) => (collection: T[]): T => {
const itemsFound = collection.filter(predicate);
@@ -11,7 +13,7 @@ export const findExactlyOne = <T>(predicate: (item: T) => boolean) => (collectio
);
}
if (itemsFound.length > 1) {
if (!hasLengthAtLeast(itemsFound, 1)) {
throw new Error(
"Tried to find exactly one, but found many",
);
@@ -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", () => {
@@ -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,
);
@@ -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,
@@ -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", () => {
@@ -546,7 +546,7 @@ metadata:
) as Record<string, Record<string, unknown>>;
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
@@ -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,/);
});
});
});
@@ -49,9 +49,9 @@ const NonInjectedHelmFileInput = ({
name: placeholder,
extensions: fileExtensions,
},
onPick: (filePaths) => {
if (filePaths.length) {
setValue(filePaths[0]);
onPick: ([filePath]) => {
if (filePath) {
setValue(filePath);
}
},
})}
@@ -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");
}
@@ -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",
@@ -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,
@@ -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");
});
});
@@ -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,
@@ -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`);
}
@@ -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,
},
},
],
@@ -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}`,
};
}
});
@@ -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}`,
};
}
});
@@ -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");
});
});
@@ -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);
});
});
@@ -126,10 +126,10 @@ export class Chart extends React.Component<ChartProps> {
// 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<ChartProps> {
void _data;
datasets[index] = {
...datasets[index],
...props,
};
Object.assign(dataset, props);
} else {
datasets[datasetIndex] = next;
}
@@ -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);
}
},
});
};
@@ -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 "<unknown>";
}
const value = data.datasets?.[0].data?.[index] as ChartPoint;
const value = data.datasets?.[0]?.data?.[index];
if (!isObject(value) || isArray(value)) {
return "<unknown>";
}
return value.y?.toString() ?? "<unknown>";
},
@@ -94,7 +98,11 @@ const NonInjectedClusterMetrics = observer((props: Dependencies) => {
return "<unknown>";
}
const value = data.datasets?.[0].data?.[index] as ChartPoint;
const value = data.datasets?.[0]?.data?.[index];
if (!isObject(value) || isArray(value)) {
return "<unknown>";
}
return bytesToUnits(parseInt(value.y as string), { precision: 3 });
},
@@ -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 {
@@ -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");
@@ -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<DockTabCreate>;
this.selectTab(newTab.id);
} else {
@@ -48,7 +48,8 @@ function getOnePodViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependenc
return mockLogTabViewModel(tabId, {
getLogTabData: () => ({
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<LogTabViewModelDependenci
name: "super-deployment",
},
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,
@@ -43,7 +43,8 @@ const getOnePodViewModel = (tabId: TabId, deps: Partial<LogTabViewModelDependenc
return mockLogTabViewModel(tabId, {
getLogTabData: () => ({
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,
@@ -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 });
@@ -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: {
@@ -199,7 +199,7 @@ class NonForwardedLogList extends React.Component<Dependencies & LogListProps &
getLogRow = (rowIndex: number) => {
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 <span>
const contents = searchQuery
@@ -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];
};
@@ -30,16 +30,14 @@ class NonInjectedKubeEventIcon extends React.Component<KubeEventIconProps & Depe
}
render() {
const { object, filterEvents, eventStore } = this.props;
const { object, filterEvents = (events) => 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 (
<Icon
@@ -54,9 +54,9 @@ export class EventStore extends KubeObjectStore<KubeEvent, KubeEventApi> {
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)) {
@@ -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]);
@@ -17,7 +17,7 @@ export async function validatePackage(filePath: string): Promise<LensExtensionMa
throw new Error(`invalid extension bundle, ${manifestFilename} not found`);
}
const rootFolder = path.normalize(firstFile).split(path.sep)[0];
const rootFolder = path.normalize(firstFile).split(path.sep)[0] ?? "";
const packedInRootFolder = tarFiles.every(entry => entry.startsWith(rootFolder));
const manifestLocation = packedInRootFolder
? path.join(rootFolder, manifestFilename)
@@ -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);
@@ -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 };
}
@@ -24,7 +24,8 @@ export function List<I, T extends object>({ 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 (
@@ -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 })),
},
],
};
@@ -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<Record<MetricsTab, ChartDataSets[]>> = {
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 })),
},
],
};
@@ -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<Dependencies> {
private renderUsage({ node, title, metricNames, formatters }: UsageArgs) {
const metrics = this.getLastMetricValues(node, metricNames);
if (!hasLengthGreaterThan(metrics, 2)) {
if (!hasLengthAtLeast(metrics, 2)) {
return <LineProgress value={0}/>;
}
@@ -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);
}
};
@@ -67,14 +67,14 @@ describe("<Select />", () => {
const { container } = render((
<Select
value={options[0].value}
value={options[0]?.value}
onChange={onChange}
options={options}
/>
));
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("<Select />", () => {
const { container, rerender } = render((
<Select
value={options[0].value}
value={options[0]?.value}
onChange={onChange}
options={options}
/>
));
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer?.textContent).toBe(options[0].label);
expect(selectedValueContainer?.textContent).toBe(options[0]?.label);
rerender((
<Select
value={options[1].value}
value={options[1]?.value}
onChange={onChange}
options={options}
/>
));
expect(container.querySelector(".Select__single-value")?.textContent).toBe(options[1].label);
expect(container.querySelector(".Select__single-value")?.textContent).toBe(options[1]?.label);
});
it("should unselect value if null is passed as a value", () => {
@@ -129,14 +129,14 @@ describe("<Select />", () => {
const { container, rerender } = render((
<Select
value={options[0].value}
value={options[0]?.value}
onChange={onChange}
options={options}
/>
));
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer?.textContent).toBe(options[0].label);
expect(selectedValueContainer?.textContent).toBe(options[0]?.label);
rerender((
<Select<string, SelectOption<string>>
@@ -165,14 +165,14 @@ describe("<Select />", () => {
const { container, rerender } = render((
<Select
value={options[0].value}
value={options[0]?.value}
onChange={onChange}
options={options}
/>
));
const selectedValueContainer = container.querySelector(".Select__single-value");
expect(selectedValueContainer?.textContent).toBe(options[0].label);
expect(selectedValueContainer?.textContent).toBe(options[0]?.label);
rerender((
<Select<string, SelectOption<string>>
@@ -29,8 +29,8 @@ const NonInjectedVolumeClaimDiskChart = observer((props: Dependencies) => {
const id = object.getId();
const { chartCapacityColor } = activeTheme.get().colors;
const { diskUsage, diskCapacity } = metrics;
const usage = normalizeMetrics(diskUsage).data.result[0].values;
const capacity = normalizeMetrics(diskCapacity).data.result[0].values;
const usage = normalizeMetrics(diskUsage).data.result[0]?.values;
const capacity = normalizeMetrics(diskCapacity).data.result[0]?.values;
const datasets: ChartDataSets[] = [
{
@@ -38,14 +38,14 @@ const NonInjectedVolumeClaimDiskChart = observer((props: Dependencies) => {
label: `Usage`,
tooltip: `Volume disk usage`,
borderColor: "#ffc63d",
data: usage.map(([x, y]) => ({ x, y })),
data: usage?.map(([x, y]) => ({ x, y })),
},
{
id: `${id}-diskCapacity`,
label: `Capacity`,
tooltip: `Volume disk capacity`,
borderColor: chartCapacityColor,
data: capacity.map(([x, y]) => ({ x, y })),
data: capacity?.map(([x, y]) => ({ x, y })),
},
];
@@ -49,7 +49,7 @@ export function ReactTable<Data extends object>({ columns, data, headless }: Rea
<div
{...cell.getCellProps()}
key={cell.getCellProps().key}
className={cssNames(styles.td, columns[index].accessor)}
className={cssNames(styles.td, columns[index]?.accessor)}
>
{cell.render("Cell")}
</div>
@@ -43,7 +43,11 @@ export function getSorted<T>(rawItems: T[], sortingCallback: TableSortCallback<T
const res = [];
for (const { index } of sortData) {
res.push(rawItems[index]);
const item = rawItems[index];
if (item !== undefined) {
res.push(item);
}
}
return res;
@@ -18,8 +18,7 @@ interface Dependencies {
export class TableModel {
constructor(private dependencies: Dependencies) {}
getSortParams = (tableId: string): Partial<TableSortParams> =>
this.dependencies.storage.get().sortParams[tableId];
getSortParams = (tableId: string): Partial<TableSortParams> => this.dependencies.storage.get().sortParams[tableId] ?? {};
setSortParams = (
tableId: string,
@@ -429,7 +429,7 @@ export const getApplicationBuilder = () => {
return getCompositePaths(composite);
},
click: (...path: string[]) => {
click: (...path: [string, ...string[]]) => {
const composite = mainDi.inject(
applicationMenuItemCompositeInjectable,
).get();
@@ -73,7 +73,8 @@ function VirtualListInner<T extends { getId(): string } | string>({
listRef.current?.scrollToItem(index, "smart");
}
}, [selectedItemId, [items]]);
const getItemSize = (index: number) => rowHeights[index];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const getItemSize = (index: number) => rowHeights[index]!;
useImperativeHandle(forwardedRef, () => ({
scrollToItem: (index, align) => listRef.current?.scrollToItem(index, align),
@@ -147,11 +148,16 @@ const Row = observer(<T extends { getId(): string } | string>(props: RowProps<T>
const { index, style, data } = props;
const { items, getRow } = data;
const item = items[index];
if (item == null) {
return null;
}
const row = getRow?.((
typeof item == "string"
? index
: item.getId()
) as never);
) as T extends string ? number : string);
if (!row) return null;
@@ -12,6 +12,7 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting";
import type { DiRender } from "../../test-utils/renderFor";
import { renderFor } from "../../test-utils/renderFor";
import directoryForLensLocalStorageInjectable from "../../../../common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable";
import assert from "assert";
const tolerations: Toleration[] =[
{
@@ -49,6 +50,9 @@ describe("<PodTolerations />", () => {
const { container } = render(<PodTolerations tolerations={tolerations} />);
const rows = container.querySelectorAll(".TableRow");
assert(rows[0]);
assert(rows[1]);
expect(rows[0].querySelector(".key")?.textContent).toBe("CriticalAddonsOnly");
expect(rows[0].querySelector(".operator")?.textContent).toBe("Exist");
expect(rows[0].querySelector(".effect")?.textContent).toBe("NoExecute");
@@ -69,6 +73,6 @@ describe("<PodTolerations />", () => {
const rows = container.querySelectorAll(".TableRow");
expect(rows[0].querySelector(".key")?.textContent).toBe("node.kubernetes.io/not-ready");
expect(rows[0]?.querySelector(".key")?.textContent).toBe("node.kubernetes.io/not-ready");
});
});
@@ -39,7 +39,7 @@ const NonInjectedContainerCharts = observer((props: Dependencies) => {
fsUsage,
fsWrites,
fsReads,
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0]?.values);
const datasets: Partial<Record<MetricsTab, ChartDataSets[]>> = {
CPU: [
@@ -48,21 +48,21 @@ const NonInjectedContainerCharts = 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: "cpuRequests",
label: `Requests`,
tooltip: `CPU requests`,
borderColor: "#30b24d",
data: cpuRequests.map(([x, y]) => ({ x, y })),
data: cpuRequests?.map(([x, y]) => ({ x, y })),
},
{
id: "cpuLimits",
label: `Limits`,
tooltip: `CPU limits`,
borderColor: chartCapacityColor,
data: cpuLimits.map(([x, y]) => ({ x, y })),
data: cpuLimits?.map(([x, y]) => ({ x, y })),
},
],
Memory: [
@@ -71,21 +71,21 @@ const NonInjectedContainerCharts = 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: "memoryRequests",
label: `Requests`,
tooltip: `Memory requests`,
borderColor: "#30b24d",
data: memoryRequests.map(([x, y]) => ({ x, y })),
data: memoryRequests?.map(([x, y]) => ({ x, y })),
},
{
id: "memoryLimits",
label: `Limits`,
tooltip: `Memory limits`,
borderColor: chartCapacityColor,
data: memoryLimits.map(([x, y]) => ({ x, y })),
data: memoryLimits?.map(([x, y]) => ({ x, y })),
},
],
Filesystem: [
@@ -94,21 +94,21 @@ const NonInjectedContainerCharts = observer((props: Dependencies) => {
label: `Usage`,
tooltip: `Bytes consumed on this filesystem`,
borderColor: "#ffc63d",
data: fsUsage.map(([x, y]) => ({ x, y })),
data: fsUsage?.map(([x, y]) => ({ x, y })),
},
{
id: "fsWrites",
label: `Writes`,
tooltip: `Bytes written on this filesystem`,
borderColor: "#ff963d",
data: fsWrites.map(([x, y]) => ({ x, y })),
data: fsWrites?.map(([x, y]) => ({ x, y })),
},
{
id: "fsReads",
label: `Reads`,
tooltip: `Bytes read on this filesystem`,
borderColor: "#fff73d",
data: fsReads.map(([x, y]) => ({ x, y })),
data: fsReads?.map(([x, y]) => ({ x, y })),
},
],
};
@@ -37,7 +37,7 @@ export const PodCharts = observer(() => {
fsReads,
networkReceive,
networkTransmit,
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0].values);
} = mapValues(metrics, metric => normalizeMetrics(metric).data.result[0]?.values);
const datasets: Partial<Record<MetricsTab, ChartDataSets[]>> = {
CPU: [
@@ -46,7 +46,7 @@ export const PodCharts = observer(() => {
label: `Usage`,
tooltip: `Container CPU cores usage`,
borderColor: "#3D90CE",
data: cpuUsage.map(([x, y]) => ({ x, y })),
data: cpuUsage?.map(([x, y]) => ({ x, y })),
},
],
Memory: [
@@ -55,7 +55,7 @@ export const PodCharts = observer(() => {
label: `Usage`,
tooltip: `Container memory usage`,
borderColor: "#c93dce",
data: memoryUsage.map(([x, y]) => ({ x, y })),
data: memoryUsage?.map(([x, y]) => ({ x, y })),
},
],
Network: [
@@ -64,14 +64,14 @@ export const PodCharts = observer(() => {
label: `Receive`,
tooltip: `Bytes received by all containers`,
borderColor: "#64c5d6",
data: networkReceive.map(([x, y]) => ({ x, y })),
data: networkReceive?.map(([x, y]) => ({ x, y })),
},
{
id: `${id}-networkTransmit`,
label: `Transmit`,
tooltip: `Bytes transmitted from all containers`,
borderColor: "#46cd9e",
data: networkTransmit.map(([x, y]) => ({ x, y })),
data: networkTransmit?.map(([x, y]) => ({ x, y })),
},
],
Filesystem: [
@@ -80,21 +80,21 @@ export const PodCharts = observer(() => {
label: `Usage`,
tooltip: `Bytes consumed on this filesystem`,
borderColor: "#ffc63d",
data: fsUsage.map(([x, y]) => ({ x, y })),
data: fsUsage?.map(([x, y]) => ({ x, y })),
},
{
id: `${id}-fsWrites`,
label: `Writes`,
tooltip: `Bytes written on this filesystem`,
borderColor: "#ff963d",
data: fsWrites.map(([x, y]) => ({ x, y })),
data: fsWrites?.map(([x, y]) => ({ x, y })),
},
{
id: `${id}-fsReads`,
label: `Reads`,
tooltip: `Bytes read on this filesystem`,
borderColor: "#fff73d",
data: fsReads.map(([x, y]) => ({ x, y })),
data: fsReads?.map(([x, y]) => ({ x, y })),
},
],
};
@@ -122,7 +122,7 @@ export class PageParam<Value> {
this.dependencies.history.searchParams.append(this.name, value);
});
} else {
this.dependencies.history.searchParams.set(this.name, values[0]);
this.dependencies.history.searchParams.set(this.name, values[0] ?? "");
}
}
@@ -132,7 +132,7 @@ export class PageParam<Value> {
getRaw(): string | string[] {
const values: string[] = this.dependencies.history.searchParams.getAll(this.name);
return this.isMulti ? values : values[0];
return this.isMulti ? values : values[0] ?? "";
}
@action
@@ -16,14 +16,33 @@ export const routeSpecificComponentInjectionToken = getInjectionToken<{
export interface GetRouteSpecificComponentOptions<Path extends string> {
id: string;
routeInjectable: Injectable<Route<Path>, Route<string>, void>;
Component: React.ComponentType<{ params?: InferParamFromPath<Path> }>;
Component: React.ComponentType<{ params: InferParamFromPath<Path> }>;
}
export const getRouteSpecificComponentInjectable = <Path extends string>(options: GetRouteSpecificComponentOptions<Path>) => getInjectable({
export interface GetRouteSpecificComponentInjectable {
<Path extends string>(options: {
id: string;
routeInjectable: Injectable<Route<Path>, Route<string>, void>;
Component: React.ComponentType<{ params: InferParamFromPath<Path> }>;
}): Injectable<{
route: Route<Path>;
Component: React.ComponentType<{ params: InferParamFromPath<Path> }>;
}, unknown, void>;
<Path extends string>(options: {
id: string;
routeInjectable: Injectable<Route<Path>, Route<string>, void>;
Component: React.ComponentType<{ params?: InferParamFromPath<Path> }>;
}): Injectable<{
route: Route<Path>;
Component: React.ComponentType<{ params?: InferParamFromPath<Path> }>;
}, unknown, void>;
}
export const getRouteSpecificComponentInjectable = ((options) => getInjectable({
id: options.id,
instantiate: (di) => ({
route: di.inject(options.routeInjectable),
Component: options.Component as React.ComponentType<{ params: InferParamFromPath<string> }>,
}),
injectionToken: routeSpecificComponentInjectionToken,
});
})) as GetRouteSpecificComponentInjectable;