1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix switch pods in logs tab from crashing lens

- Add in Error boundary into the dock

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-01-26 14:17:32 -05:00
parent 0956760626
commit 746c0a73a2
13 changed files with 100 additions and 84 deletions

View File

@ -12,8 +12,9 @@ import commandOverlayInjectable from "../../renderer/components/command-palette/
import { asLegacyGlobalObjectForExtensionApiWithModifications } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api-with-modifications";
import createPodLogsTabInjectable from "../../renderer/components/dock/logs/create-pod-logs-tab.injectable";
import createWorkloadLogsTabInjectable from "../../renderer/components/dock/logs/create-workload-logs-tab.injectable";
import updateTabNameInjectable from "../../renderer/components/dock/logs/update-tab-name.injectable";
import sendCommandInjectable from "../../renderer/components/dock/terminal/send-command.injectable";
import { podsStore } from "../../renderer/components/+workloads-pods/pods.store";
import renameTabInjectable from "../../renderer/components/dock/dock/rename-tab.injectable";
// layouts
export * from "../../renderer/components/layout/main-layout";
@ -80,7 +81,13 @@ export const terminalStore = asLegacyGlobalObjectForExtensionApiWithModification
export const logTabStore = asLegacyGlobalObjectForExtensionApiWithModifications(logTabStoreInjectable, {
createPodTab: () => asLegacyGlobalFunctionForExtensionApi(createPodLogsTabInjectable),
createWorkloadTab: () => asLegacyGlobalFunctionForExtensionApi(createWorkloadLogsTabInjectable),
renameTab: () => asLegacyGlobalFunctionForExtensionApi(updateTabNameInjectable),
renameTab: () => (tabId: string): void => {
const renameTab = asLegacyGlobalFunctionForExtensionApi(renameTabInjectable);
const tabData = logTabStore.getData(tabId);
const pod = podsStore.getById(tabData.selectedPodId);
renameTab(tabId, `Pod ${pod.getName()}`);
},
tabs: () => undefined,
});

View File

@ -25,6 +25,7 @@ import { withInjectables } from "@ogre-tools/injectable-react";
import createResourceTabInjectable from "./create-resource/create-resource-tab.injectable";
import dockStoreInjectable from "./dock/store.injectable";
import createTerminalTabInjectable from "./terminal/create-terminal-tab.injectable";
import { ErrorBoundary } from "../error-boundary";
interface Props {
className?: string;
@ -160,7 +161,9 @@ class NonInjectedDock extends React.Component<Props & Dependencies> {
)}
</div>
</div>
{this.renderTabContent()}
<ErrorBoundary>
{this.renderTabContent()}
</ErrorBoundary>
</div>
);
}

View File

@ -46,7 +46,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependen
setLogTabData: jest.fn(),
loadLogs: jest.fn(),
reloadLogs: jest.fn(),
updateTabName: jest.fn(),
renameTab: jest.fn(),
stopLoadingLogs: jest.fn(),
getPodById: jest.fn(),
getPodsByOwnerId: jest.fn(),
@ -190,13 +190,13 @@ describe("<LogResourceSelector />", () => {
});
it("updates tab name if selected pod changes", async () => {
const updateTabName = jest.fn();
const model = getFewPodsTabData("foobar", { updateTabName });
const renameTab = jest.fn();
const model = getFewPodsTabData("foobar", { renameTab });
const { findByText, container } = render(<LogResourceSelector model={model} />);
selectEvent.openMenu(container.querySelector(".pod-selector"));
userEvent.click(await findByText("deploymentPod2", { selector: ".pod-selector-menu .Select__option" }));
expect(updateTabName).toBeCalled();
expect(renameTab).toBeCalledWith("foobar", "Pod deploymentPod1");
});
});

View File

@ -25,7 +25,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial<LogTabViewModelDependen
setLogTabData: jest.fn(),
loadLogs: jest.fn(),
reloadLogs: jest.fn(),
updateTabName: jest.fn(),
renameTab: jest.fn(),
stopLoadingLogs: jest.fn(),
getPodById: jest.fn(),
getPodsByOwnerId: jest.fn(),

View File

@ -11,7 +11,7 @@ import { runInAction } from "mobx";
import createDockTabInjectable from "../dock/create-dock-tab.injectable";
import setLogTabDataInjectable from "./set-log-tab-data.injectable";
export type CreateLogsTabData = Pick<LogTabData, "ownerId" | "selectedPodId" | "selectedContainer" | "namespace"> & Omit<Partial<LogTabData>, "ownerId" | "selectedPodId" | "selectedContainer" | "namespace">;
export type CreateLogsTabData = Pick<LogTabData, "owner" | "selectedPodId" | "selectedContainer" | "namespace"> & Omit<Partial<LogTabData>, "owner" | "selectedPodId" | "selectedContainer" | "namespace">;
interface Dependencies {
createDockTab: (rawTabDesc: DockTabCreate, addNumber?: boolean) => DockTab;

View File

@ -18,10 +18,8 @@ interface Dependencies {
}
function createPodLogsTab({ createLogsTab }: Dependencies, { selectedPod, selectedContainer }: PodLogsTabData): TabId {
const podOwner = selectedPod.getOwnerRefs()[0];
return createLogsTab(`Pod ${selectedPod.getName()}`, {
ownerId: podOwner?.uid,
owner: selectedPod.getOwnerRefs()[0],
namespace: selectedPod.getNs(),
selectedContainer: selectedContainer.name,
selectedPodId: selectedPod.getId(),

View File

@ -30,7 +30,11 @@ function createWorkloadLogsTab({ createLogsTab }: Dependencies, { workload }: Wo
selectedContainer: selectedPod.getAllContainers()[0].name,
selectedPodId: selectedPod.getId(),
namespace: selectedPod.getNs(),
ownerId: workload.getId(),
owner: {
kind: workload.kind,
name: workload.getName(),
uid: workload.getId(),
},
});
}

View File

@ -3,10 +3,22 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import Joi from "joi";
import type { LogTabData, LogTabOwnerRef } from "./tab-store";
export const logTabDataValidator = Joi.object({
ownerId: Joi
.string()
export const logTabDataValidator = Joi.object<LogTabData>({
owner: Joi
.object<LogTabOwnerRef>({
uid: Joi
.string()
.required(),
name: Joi
.string()
.required(),
kind: Joi
.string()
.required(),
})
.unknown(true)
.optional(),
selectedPodId: Joi
.string()
@ -20,7 +32,7 @@ export const logTabDataValidator = Joi.object({
showTimestamps: Joi
.boolean()
.required(),
previous: Joi
showPrevious: Joi
.boolean()
.required(),
});

View File

@ -12,10 +12,10 @@ import reloadLogsInjectable from "./reload-logs.injectable";
import getLogTabDataInjectable from "./get-log-tab-data.injectable";
import loadLogsInjectable from "./load-logs.injectable";
import setLogTabDataInjectable from "./set-log-tab-data.injectable";
import updateTabNameInjectable from "./update-tab-name.injectable";
import stopLoadingLogsInjectable from "./stop-loading-logs.injectable";
import { podsStore } from "../../+workloads-pods/pods.store";
import createSearchStoreInjectable from "../../../search-store/create-search-store.injectable";
import renameTabInjectable from "../dock/rename-tab.injectable";
export interface InstantiateArgs {
tabId: TabId;
@ -30,7 +30,7 @@ const logsViewModelInjectable = getInjectable({
getLogTabData: di.inject(getLogTabDataInjectable),
setLogTabData: di.inject(setLogTabDataInjectable),
loadLogs: di.inject(loadLogsInjectable),
updateTabName: di.inject(updateTabNameInjectable),
renameTab: di.inject(renameTabInjectable),
stopLoadingLogs: di.inject(stopLoadingLogsInjectable),
getPodById: id => podsStore.getById(id),
getPodsByOwnerId: id => podsStore.getPodsByOwnerId(id),

View File

@ -16,7 +16,7 @@ export interface LogTabViewModelDependencies {
setLogTabData: (tabId: TabId, data: LogTabData) => void;
loadLogs: (tabId: TabId, logTabData: IComputedValue<LogTabData>) => Promise<void>;
reloadLogs: (tabId: TabId, logTabData: IComputedValue<LogTabData>) => Promise<void>;
updateTabName: (tabId: TabId, pod: Pod) => void;
renameTab: (tabId: TabId, title: string) => void;
stopLoadingLogs: (tabId: TabId) => void;
getPodById: (id: string) => Pod | undefined;
getPodsByOwnerId: (id: string) => Pod[];
@ -37,8 +37,8 @@ export class LogTabViewModel {
return [];
}
if (typeof data.ownerId === "string") {
return this.dependencies.getPodsByOwnerId(data.ownerId);
if (typeof data.owner?.uid === "string") {
return this.dependencies.getPodsByOwnerId(data.owner.uid);
}
return [this.dependencies.getPodById(data.selectedPodId)];
@ -60,6 +60,6 @@ export class LogTabViewModel {
loadLogs = () => this.dependencies.loadLogs(this.tabId, this.logTabData);
reloadLogs = () => this.dependencies.reloadLogs(this.tabId, this.logTabData);
updateTabName = () => this.dependencies.updateTabName(this.tabId, this.pod.get());
renameTab = (title: string) => this.dependencies.renameTab(this.tabId, title);
stopLoadingLogs = () => this.dependencies.stopLoadingLogs(this.tabId);
}

View File

@ -5,18 +5,25 @@
import "./resource-selector.scss";
import React, { useEffect } from "react";
import React from "react";
import { observer } from "mobx-react";
import { Badge } from "../../badge";
import { Select, SelectOption } from "../../select";
import type { LogTabViewModel } from "./logs-view-model";
import { action } from "mobx";
import type { IPodContainer, Pod } from "../../../../common/k8s-api/endpoints";
export interface LogResourceSelectorProps {
model: LogTabViewModel;
}
function getSelectOptions(containers: IPodContainer[]): SelectOption<string>[] {
return containers.map(container => ({
value: container.name,
label: container.name,
}));
}
export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps) => {
const tabData = model.logTabData.get();
@ -24,65 +31,61 @@ export const LogResourceSelector = observer(({ model }: LogResourceSelectorProps
return null;
}
const { selectedContainer } = tabData;
const { selectedContainer, owner } = tabData;
const pods = model.pods.get();
const pod = model.pod.get();
const containers = pod.getContainers();
const initContainers = pod.getInitContainers();
if (!pod) {
return null;
}
const onContainerChange = (option: SelectOption<string>) => {
model.updateLogTabData({
selectedContainer: option.value,
});
model.reloadLogs();
};
const onPodChange = action((option: SelectOption<string>) => {
const onPodChange = ({ value }: SelectOption<Pod>) => {
model.updateLogTabData({
selectedPodId: option.value,
});
model.updateTabName();
});
const getSelectOptions = (items: string[]) => {
return items.map(item => {
return {
value: item,
label: item,
};
selectedPodId: value.getId(),
selectedContainer: value.getAllContainers()[0]?.name,
});
model.renameTab(`Pod ${value.getName()}`);
model.reloadLogs();
};
const containerSelectOptions = [
{
label: `Containers`,
options: getSelectOptions(containers.map(container => container.name)),
label: "Containers",
options: getSelectOptions(pod.getContainers()),
},
{
label: `Init Containers`,
options: getSelectOptions(initContainers.map(container => container.name)),
label: "Init Containers",
options: getSelectOptions(pod.getInitContainers()),
},
];
const podSelectOptions = [
{
label: pod.getOwnerRefs()[0]?.name,
options: getSelectOptions(pods.map(pod => pod.metadata.name)),
},
];
useEffect(() => {
model.reloadLogs();
}, [pod.getId()]);
const podSelectOptions = pods.map(pod => ({
label: pod.getName(),
value: pod,
}));
return (
<div className="LogResourceSelector flex gaps align-center">
<span>Namespace</span> <Badge data-testid="namespace-badge" label={pod.getNs()}/>
{
owner && (
<>
<span>Owner</span> <Badge data-testid="namespace-badge" label={`${owner.kind} ${owner.name}`}/>
</>
)
}
<span>Pod</span>
<Select
options={podSelectOptions}
value={{ label: pod.getName(), value: pod.getName() }}
value={podSelectOptions.find(opt => opt.value === pod)}
formatOptionLabel={option => option.label}
onChange={onPodChange}
autoConvertOptions={false}
className="pod-selector"

View File

@ -8,11 +8,26 @@ import type { StorageHelper } from "../../../utils";
import type { TabId } from "../dock/store";
import { logTabDataValidator } from "./log-tab-data.validator";
export interface LogTabOwnerRef {
/**
* The uid of the owner
*/
uid: string;
/**
* The name of the owner
*/
name: string;
/**
* The kind of the owner
*/
kind: string;
}
export interface LogTabData {
/**
* The workload's uid for a workload logs tab
* The owning workload for this logging tab
*/
ownerId?: string;
owner?: LogTabOwnerRef;
/**
* The uid of the currently selected pod

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { Pod } from "../../../../common/k8s-api/endpoints";
import { bind } from "../../../utils";
import type { TabId } from "../dock/store";
import renameTabInjectable from "../dock/rename-tab.injectable";
interface Dependencies {
renameTab: (tabId: TabId, title: string) => void;
}
function updateTabName({ renameTab }: Dependencies, tabId: string, pod: Pod): void {
renameTab(tabId, `Pod ${pod.getName()}`);
}
const updateTabNameInjectable = getInjectable({
instantiate: (di) => bind(updateTabName, null, {
renameTab: di.inject(renameTabInjectable),
}),
lifecycle: lifecycleEnum.singleton,
});
export default updateTabNameInjectable;