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:
parent
0956760626
commit
746c0a73a2
@ -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,
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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(),
|
||||
});
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
Loading…
Reference in New Issue
Block a user