Cascade different refactorings to eliminate global shared state from app paths

Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne SavolainenandMikko Aspiala committed 2022-01-05 13:26:49 +02:00
1 parent 908a40975e
commit 9d33fff906
336 files changed
+9105 -3307

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export * from "./port-forward.store";
export * from "./port-forward-store/port-forward-store";
export * from "./port-forward-item";
export * from "./port-forward-dialog";
export * from "./port-forward-notify";
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { PortForwardDialogModel } from "./port-forward-dialog-model";
const portForwardDialogModelInjectable = getInjectable({
instantiate: () => new PortForwardDialogModel(),
lifecycle: lifecycleEnum.singleton,
});
export default portForwardDialogModelInjectable;
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { action, computed, observable, makeObservable } from "mobx";
import type { ForwardedPort } from "../port-forward-item";
interface PortForwardDialogOpenOptions {
openInBrowser: boolean
}
export class PortForwardDialogModel {
portForward: ForwardedPort = null;
useHttps = false;
openInBrowser = false;
constructor() {
makeObservable(this, {
isOpen: computed,
portForward: observable,
useHttps: observable,
openInBrowser: observable,
open: action,
close: action,
});
}
get isOpen() {
return !!this.portForward;
}
open = (portForward: ForwardedPort, options: PortForwardDialogOpenOptions = { openInBrowser: false }) => {
this.portForward = portForward;
this.useHttps = portForward.protocol === "https";
this.openInBrowser = options.openInBrowser;
};
close = () => {
this.portForward = null;
this.useHttps = false;
this.openInBrowser = false;
};
}
@@ -29,58 +29,37 @@ import { Wizard, WizardStep } from "../components/wizard";
import { Input } from "../components/input";
import { Notifications } from "../components/notifications";
import { cssNames } from "../utils";
import { addPortForward, getPortForwards, modifyPortForward } from "./port-forward.store";
import { getPortForwards } from "./port-forward-store/port-forward-store";
import type { ForwardedPort } from "./port-forward-item";
import { aboutPortForwarding, openPortForward } from ".";
import { Checkbox } from "../components/checkbox";
import { withInjectables } from "@ogre-tools/injectable-react";
import modifyPortForwardInjectable from "./port-forward-store/modify-port-forward/modify-port-forward.injectable";
import type { PortForwardDialogModel } from "./port-forward-dialog-model/port-forward-dialog-model";
import portForwardDialogModelInjectable from "./port-forward-dialog-model/port-forward-dialog-model.injectable";
import addPortForwardInjectable from "./port-forward-store/add-port-forward/add-port-forward.injectable";
interface Props extends Partial<DialogProps> {
}
interface PortForwardDialogOpenOptions {
openInBrowser: boolean
interface Dependencies {
modifyPortForward: (item: ForwardedPort, desiredPort: number) => Promise<number>,
addPortForward: (item: ForwardedPort) => Promise<number>,
model: PortForwardDialogModel
}
const dialogState = observable.object({
isOpen: false,
data: null as ForwardedPort,
useHttps: false,
openInBrowser: false,
});
@observer
export class PortForwardDialog extends Component<Props> {
class NonInjectedPortForwardDialog extends Component<Props & Dependencies> {
@observable currentPort = 0;
@observable desiredPort = 0;
constructor(props: Props) {
constructor(props: Props & Dependencies) {
super(props);
makeObservable(this);
}
static open(portForward: ForwardedPort, options: PortForwardDialogOpenOptions = { openInBrowser: false }) {
dialogState.isOpen = true;
dialogState.data = portForward;
dialogState.useHttps = portForward.protocol === "https";
dialogState.openInBrowser = options.openInBrowser;
}
static close() {
dialogState.isOpen = false;
}
get portForward() {
return dialogState.data;
}
close = () => {
PortForwardDialog.close();
};
onOpen = async () => {
const { portForward } = this;
this.currentPort = +portForward.forwardPort;
this.currentPort = +this.props.model.portForward.forwardPort;
this.desiredPort = this.currentPort;
};
@@ -92,8 +71,8 @@ export class PortForwardDialog extends Component<Props> {
};
startPortForward = async () => {
const { portForward } = this;
const { currentPort, desiredPort, close } = this;
const portForward = this.props.model.portForward;
const { currentPort, desiredPort } = this;
try {
// determine how many port-forwards are already active
@@ -101,13 +80,13 @@ export class PortForwardDialog extends Component<Props> {
let port: number;
portForward.protocol = dialogState.useHttps ? "https" : "http";
portForward.protocol = this.props.model.useHttps ? "https" : "http";
if (currentPort) {
port = await modifyPortForward(portForward, desiredPort);
port = await this.props.modifyPortForward(portForward, desiredPort);
} else {
portForward.forwardPort = desiredPort;
port = await addPortForward(portForward);
port = await this.props.addPortForward(portForward);
// if this is the first port-forward show the about notification
if (!length) {
@@ -115,7 +94,7 @@ export class PortForwardDialog extends Component<Props> {
}
}
if (dialogState.openInBrowser) {
if (this.props.model.openInBrowser) {
portForward.forwardPort = port;
openPortForward(portForward);
}
@@ -146,14 +125,14 @@ export class PortForwardDialog extends Component<Props> {
<Checkbox
data-testid="port-forward-https"
label="https"
value={dialogState.useHttps}
onChange={value => dialogState.useHttps = value}
value={this.props.model.useHttps}
onChange={value => this.props.model.useHttps = value}
/>
<Checkbox
data-testid="port-forward-open"
label="Open in Browser"
value={dialogState.openInBrowser}
onChange={value => dialogState.openInBrowser = value}
value={this.props.model.openInBrowser}
onChange={value => this.props.model.openInBrowser = value}
/>
</div>
</>
@@ -161,8 +140,8 @@ export class PortForwardDialog extends Component<Props> {
}
render() {
const { className, ...dialogProps } = this.props;
const resourceName = this.portForward?.name ?? "";
const { className, modifyPortForward, model, ...dialogProps } = this.props;
const resourceName = this.props.model.portForward?.name ?? "";
const header = (
<h5>
Port Forwarding for <span>{resourceName}</span>
@@ -172,13 +151,13 @@ export class PortForwardDialog extends Component<Props> {
return (
<Dialog
{...dialogProps}
isOpen={dialogState.isOpen}
isOpen={this.props.model.isOpen}
className={cssNames("PortForwardDialog", className)}
onOpen={this.onOpen}
onClose={this.onClose}
close={this.close}
close={this.props.model.close}
>
<Wizard header={header} done={this.close}>
<Wizard header={header} done={this.props.model.close}>
<WizardStep
contentClass="flex gaps column"
next={this.startPortForward}
@@ -191,3 +170,16 @@ export class PortForwardDialog extends Component<Props> {
);
}
}
export const PortForwardDialog = withInjectables<Dependencies, Props>(
NonInjectedPortForwardDialog,
{
getProps: (di, props) => ({
modifyPortForward: di.inject(modifyPortForwardInjectable),
addPortForward: di.inject(addPortForwardInjectable),
model: di.inject(portForwardDialogModelInjectable),
...props,
}),
},
);
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import portForwardStoreInjectable from "../port-forward-store.injectable";
const addPortForwardInjectable = getInjectable({
instantiate: (di) => di.inject(portForwardStoreInjectable).add,
lifecycle: lifecycleEnum.singleton,
});
export default addPortForwardInjectable;
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import portForwardStoreInjectable from "../port-forward-store.injectable";
const modifyPortForwardInjectable = getInjectable({
instantiate: (di) => di.inject(portForwardStoreInjectable).modify,
lifecycle: lifecycleEnum.singleton,
});
export default modifyPortForwardInjectable;
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import { PortForwardStore } from "./port-forward-store";
import type { ForwardedPort } from "../port-forward-item";
import createStorageInjectable from "../../utils/create-storage/create-storage.injectable";
const portForwardStoreInjectable = getInjectable({
instantiate: (di) => {
const createStorage = di.inject(createStorageInjectable);
const storage = createStorage<ForwardedPort[] | undefined>(
"port_forwards",
undefined,
);
return new PortForwardStore({ storage });
},
lifecycle: lifecycleEnum.singleton,
});
export default portForwardStoreInjectable;
@@ -21,19 +21,21 @@
import { makeObservable, observable, reaction } from "mobx";
import { ItemStore } from "../../common/item.store";
import { autoBind, createStorage, disposer, getHostedClusterId } from "../utils";
import { ForwardedPort, PortForwardItem } from "./port-forward-item";
import { apiBase } from "../api";
import { ItemStore } from "../../../common/item.store";
import { autoBind, disposer, getHostedClusterId, StorageHelper } from "../../utils";
import { ForwardedPort, PortForwardItem } from "../port-forward-item";
import { apiBase } from "../../api";
import logger from "../../../common/logger";
import { waitUntilFree } from "tcp-port-used";
import logger from "../../common/logger";
interface Dependencies {
storage: StorageHelper<ForwardedPort[] | undefined>
}
export class PortForwardStore extends ItemStore<PortForwardItem> {
private storage = createStorage<ForwardedPort[] | undefined>("port_forwards", undefined);
@observable portForwards: PortForwardItem[];
constructor() {
constructor(private dependencies: Dependencies) {
super();
makeObservable(this);
autoBind(this);
@@ -42,13 +44,13 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
}
private async init() {
await this.storage.whenReady;
await this.dependencies.storage.whenReady;
const savedPortForwards = this.storage.get(); // undefined on first load
const savedPortForwards = this.dependencies.storage.get(); // undefined on first load
if (Array.isArray(savedPortForwards)) {
logger.info("[PORT-FORWARD-STORE] starting saved port-forwards");
await Promise.all(savedPortForwards.map(addPortForward));
await Promise.all(savedPortForwards.map(this.add));
}
}
@@ -62,7 +64,7 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
return this.loadItems(async () => {
const portForwards = await getPortForwards(getHostedClusterId());
this.storage.set(portForwards);
this.dependencies.storage.set(portForwards);
this.reset();
portForwards.map(pf => this.portForwards.push(new PortForwardItem(pf)));
@@ -71,12 +73,12 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
});
}
reset() {
reset = () => {
this.portForwards = [];
}
};
async removeSelectedItems() {
return Promise.all(this.selectedItems.map(removePortForward));
return Promise.all(this.selectedItems.map(this.remove));
}
getById(id: string) {
@@ -88,9 +90,58 @@ export class PortForwardStore extends ItemStore<PortForwardItem> {
return this.getItems()[index];
}
add = async (portForward: ForwardedPort): Promise<number> => {
const { port, forwardPort } = portForward;
let response: PortForwardResult;
try {
const protocol = portForward.protocol ?? "http";
response = await apiBase.post<PortForwardResult>(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort, protocol }});
// expecting the received port to be the specified port, unless the specified port is 0, which indicates any available port is suitable
if (portForward.forwardPort && response?.port && response.port != +portForward.forwardPort) {
logger.warn(`[PORT-FORWARD-STORE] specified ${portForward.forwardPort} got ${response.port}`);
}
} catch (error) {
logger.warn("[PORT-FORWARD-STORE] Error adding port-forward:", error, portForward);
throw (error);
}
this.reset();
return response?.port;
};
remove = async (portForward: ForwardedPort) => {
const { port, forwardPort } = portForward;
try {
await apiBase.del(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort }});
await waitUntilFree(+forwardPort, 200, 1000);
} catch (error) {
logger.warn("[PORT-FORWARD-STORE] Error removing port-forward:", error, portForward);
throw (error);
}
this.reset();
};
modify = async (portForward: ForwardedPort, desiredPort: number): Promise<number> => {
await this.remove(portForward);
portForward.forwardPort = desiredPort;
const port = await this.add(portForward);
this.reset();
return port;
};
}
interface PortForwardResult {
export interface PortForwardResult {
port: number;
}
@@ -98,28 +149,6 @@ interface PortForwardsResult {
portForwards: ForwardedPort[];
}
export async function addPortForward(portForward: ForwardedPort): Promise<number> {
const { port, forwardPort } = portForward;
let response: PortForwardResult;
try {
const protocol = portForward.protocol ?? "http";
response = await apiBase.post<PortForwardResult>(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort, protocol }});
// expecting the received port to be the specified port, unless the specified port is 0, which indicates any available port is suitable
if (portForward.forwardPort && response?.port && response.port != +portForward.forwardPort) {
logger.warn(`[PORT-FORWARD-STORE] specified ${portForward.forwardPort} got ${response.port}`);
}
} catch (error) {
logger.warn("[PORT-FORWARD-STORE] Error adding port-forward:", error, portForward);
throw (error);
}
portForwardStore.reset();
return response?.port;
}
export async function getPortForward(portForward: ForwardedPort): Promise<number> {
const { port, forwardPort, protocol } = portForward;
let response: PortForwardResult;
@@ -134,31 +163,6 @@ export async function getPortForward(portForward: ForwardedPort): Promise<number
return response?.port;
}
export async function modifyPortForward(portForward: ForwardedPort, desiredPort: number): Promise<number> {
let port = 0;
await removePortForward(portForward);
portForward.forwardPort = desiredPort;
port = await addPortForward(portForward);
portForwardStore.reset();
return port;
}
export async function removePortForward(portForward: ForwardedPort) {
const { port, forwardPort } = portForward;
try {
await apiBase.del(`/pods/port-forward/${portForward.namespace}/${portForward.kind}/${portForward.name}`, { query: { port, forwardPort }});
await waitUntilFree(+forwardPort, 200, 1000);
} catch (error) {
logger.warn("[PORT-FORWARD-STORE] Error removing port-forward:", error, portForward);
throw (error);
}
portForwardStore.reset();
}
export async function getPortForwards(clusterId?: string): Promise<ForwardedPort[]> {
try {
@@ -171,5 +175,3 @@ export async function getPortForwards(clusterId?: string): Promise<ForwardedPort
return [];
}
}
export const portForwardStore = new PortForwardStore();
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import portForwardStoreInjectable from "../port-forward-store.injectable";
const removePortForwardInjectable = getInjectable({
instantiate: (di) => di.inject(portForwardStoreInjectable).remove,
lifecycle: lifecycleEnum.singleton,
});
export default removePortForwardInjectable;