Skip to content

Commit

Permalink
Switch OperatorNew to react-router hooks and remove OperatorNew conta…
Browse files Browse the repository at this point in the history
…iner (#6457)

### Description of the change

Part 1 in a series of prequel PRs for #6187 upgrading react-router, this
PR switches the OperatorNew component to use the react-router hooks and
removes the container.

### Benefits

1 step closer to #6187

### Possible drawbacks

Let CI decide.

### Applicable issues

- ref #6187

---------

Signed-off-by: Michael Nelson <minelson@vmware.com>
  • Loading branch information
absoludity authored Jul 13, 2023
1 parent c0bc282 commit 6f55e10
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 126 deletions.
85 changes: 55 additions & 30 deletions dashboard/src/components/OperatorNew/OperatorNew.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,9 @@ import * as ReactRedux from "react-redux";
import { defaultStore, getStore, initialState, mountWrapper } from "shared/specs/mountWrapper";
import { IStoreState } from "shared/types";
import OperatorNew from "./OperatorNew";

const defaultProps = {
operatorName: "foo",
getOperator: jest.fn(),
isFetching: false,
cluster: initialState.config.kubeappsCluster,
namespace: "kubeapps",
push: jest.fn(),
createOperator: jest.fn(),
errors: {},
};
import { IOperatorsState } from "reducers/operators";
import { IClusterState } from "reducers/cluster";
import { MemoryRouter, Route } from "react-router-dom";

const defaultOperator = {
metadata: {
Expand Down Expand Up @@ -70,18 +62,26 @@ afterEach(() => {
it("calls getOperator when mounting the component", () => {
const getOperator = jest.fn();
actions.operators.getOperator = getOperator;
mountWrapper(defaultStore, <OperatorNew {...defaultProps} />);

mountWrapper(
defaultStore,
<MemoryRouter initialEntries={["/c/default/ns/default/operators/new/foo"]}>
<Route path={"/c/:cluster/ns/:namespace/operators/new/:operator"}>
<OperatorNew />
</Route>
</MemoryRouter>,
);
expect(getOperator).toHaveBeenCalledWith(
defaultProps.cluster,
defaultProps.namespace,
defaultProps.operatorName,
initialState.clusters.currentCluster,
initialState.clusters.clusters[initialState.clusters.currentCluster].currentNamespace,
"foo",
);
});

it("parses the default channel when receiving the operator", () => {
const wrapper = mountWrapper(
getStore({ operators: { operator: defaultOperator } } as Partial<IStoreState>),
<OperatorNew {...defaultProps} />,
<OperatorNew />,
);
const input = wrapper.find("#operator-channel-beta");
expect(input).toExist();
Expand All @@ -93,7 +93,7 @@ it("renders a fetch error if present", () => {
getStore({
operators: { errors: { operator: { fetch: new Error("Boom") } } },
} as Partial<IStoreState>),
<OperatorNew {...defaultProps} />,
<OperatorNew />,
);
expect(wrapper.find(Alert)).toIncludeText("Boom");
});
Expand All @@ -103,7 +103,7 @@ it("renders a create error if present", () => {
getStore({
operators: { errors: { operator: { create: new Error("Boom") } } },
} as Partial<IStoreState>),
<OperatorNew {...defaultProps} />,
<OperatorNew />,
);
expect(wrapper.find(Alert)).toIncludeText("Boom");
});
Expand All @@ -116,23 +116,38 @@ it("shows an error if the operator doesn't have any channel defined", () => {
channels: [],
},
};
const store = getStore({
...initialState,
operators: { ...initialState.operators, operator },
} as Partial<IStoreState>);

const wrapper = mountWrapper(
getStore({
...initialState,
operators: { ...initialState.operators, operator },
} as Partial<IStoreState>),
<OperatorNew {...defaultProps} />,
store,
<MemoryRouter initialEntries={["/c/default/ns/default/operators/new/foo"]}>
<Route path={"/c/:cluster/ns/:namespace/operators/new/:operator"}>
<OperatorNew />
</Route>
</MemoryRouter>,
);

expect(wrapper.find(Alert)).toIncludeText(
"Operator foo doesn't define a valid channel. This is needed to extract required info",
);
});

it("disables the submit button if the operators ns is selected", () => {
const wrapper = mountWrapper(
getStore({ operators: { operator: defaultOperator } } as Partial<IStoreState>),
<OperatorNew {...defaultProps} namespace="operators" />,
);
const store = getStore({
operators: { operator: defaultOperator } as Partial<IOperatorsState>,
clusters: {
currentCluster: "default-cluster",
clusters: {
"default-cluster": {
currentNamespace: "operators",
} as Partial<IClusterState>,
},
},
} as Partial<IStoreState>);
const wrapper = mountWrapper(store, <OperatorNew />);
expect(wrapper.find(CdsButton)).toBeDisabled();
expect(wrapper.find(Alert)).toIncludeText(
'It\'s not possible to install a namespaced operator in the "operators" namespace',
Expand All @@ -142,14 +157,24 @@ it("disables the submit button if the operators ns is selected", () => {
it("deploys an operator", async () => {
const createOperator = jest.fn().mockReturnValue(true);
actions.operators.createOperator = createOperator;
const store = getStore({ operators: { operator: defaultOperator } } as Partial<IStoreState>);
const store = getStore({
operators: { operator: defaultOperator } as Partial<IOperatorsState>,
clusters: {
currentCluster: "default-cluster",
clusters: {
"default-cluster": {
currentNamespace: "kubeapps",
} as Partial<IClusterState>,
},
},
} as Partial<IStoreState>);

const wrapper = mountWrapper(store, <OperatorNew {...defaultProps} />);
const wrapper = mountWrapper(store, <OperatorNew />);
const onSubmit = wrapper.find("form").prop("onSubmit") as () => Promise<void>;
await onSubmit();

expect(createOperator).toHaveBeenCalledWith(
defaultProps.cluster,
initialState.clusters.currentCluster,
"kubeapps",
"foo",
"beta",
Expand Down
41 changes: 15 additions & 26 deletions dashboard/src/components/OperatorNew/OperatorNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,20 @@ import Alert from "components/js/Alert";
import Column from "components/js/Column";
import Row from "components/js/Row";
import OperatorSummary from "components/OperatorSummary/OperatorSummary";
import { push, RouterAction } from "connected-react-router";
import { push } from "connected-react-router";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Action } from "redux";
import { ThunkDispatch } from "redux-thunk";
import { Operators } from "shared/Operators";
import { IPackageManifest, IPackageManifestChannel, IStoreState } from "shared/types";
import { IPackageManifestChannel, IStoreState } from "shared/types";
import { api, app } from "shared/url";
import { IOperatorsStateError } from "../../reducers/operators";
import LoadingWrapper from "../LoadingWrapper/LoadingWrapper";
import OperatorHeader from "../OperatorView/OperatorHeader";
import "./OperatorNew.css";
import { useParams } from "react-router-dom";

export interface IOperatorNewProps {
operatorName: string;
operator?: IPackageManifest;
getOperator: (cluster: string, namespace: string, name: string) => Promise<void>;
isFetching: boolean;
cluster: string;
namespace: string;
errors: IOperatorsStateError;
createOperator: (
cluster: string,
namespace: string,
name: string,
channel: string,
installPlanApproval: string,
csv: string,
) => Promise<boolean>;
push: (location: string) => RouterAction;
}

export default function OperatorNew({ namespace, operatorName, cluster }: IOperatorNewProps) {
export default function OperatorNew() {
const dispatch: ThunkDispatch<IStoreState, null, Action> = useDispatch();

const [updateChannel, setUpdateChannel] = useState(
Expand All @@ -51,17 +32,25 @@ export default function OperatorNew({ namespace, operatorName, cluster }: IOpera
// Approval strategy: true for automatic, false for manual
const [approvalStrategyAutomatic, setApprovalStrategyAutomatic] = useState(true);

useEffect(() => {
dispatch(actions.operators.getOperator(cluster, namespace, operatorName));
}, [dispatch, cluster, namespace, operatorName]);
type OperatorNewParams = {
operator: string;
};
const { operator: operatorName } = useParams<OperatorNewParams>();

const {
operators: {
operator,
isFetching,
errors: { operator: errors },
},
clusters: { currentCluster, clusters },
} = useSelector((state: IStoreState) => state);
const namespace = clusters[currentCluster].currentNamespace;
const cluster = currentCluster;

useEffect(() => {
dispatch(actions.operators.getOperator(cluster, namespace, operatorName));
}, [dispatch, cluster, namespace, operatorName]);

useEffect(() => {
if (operator) {
Expand Down

This file was deleted.

6 changes: 0 additions & 6 deletions dashboard/src/containers/OperatorNewContainer/index.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions dashboard/src/containers/RoutesContainer/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import LoginFormContainer from "../../containers/LoginFormContainer";
import OperatorInstanceCreateContainer from "../../containers/OperatorInstanceCreateContainer";
import OperatorInstanceUpdateContainer from "../../containers/OperatorInstanceUpdateContainer";
import OperatorInstanceViewContainer from "../../containers/OperatorInstanceViewContainer";
import OperatorNewContainer from "../../containers/OperatorNewContainer";
import OperatorsListContainer from "../../containers/OperatorsListContainer";
import OperatorViewContainer from "../../containers/OperatorViewContainer";
import PrivateRouteContainer from "../../containers/PrivateRouteContainer";
import OperatorNew from "components/OperatorNew";

type IRouteComponentPropsAndRouteProps = RouteProps & RouteComponentProps<any>;

Expand All @@ -59,7 +59,7 @@ const privateRoutes = {
const operatorsRoutes = {
"/c/:cluster/ns/:namespace/operators": OperatorsListContainer,
"/c/:cluster/ns/:namespace/operators/:operator": OperatorViewContainer,
"/c/:cluster/ns/:namespace/operators/new/:operator": OperatorNewContainer,
"/c/:cluster/ns/:namespace/operators/new/:operator": OperatorNew,
"/c/:cluster/ns/:namespace/operators-instances/new/:csv/:crd": OperatorInstanceCreateContainer,
"/c/:cluster/ns/:namespace/operators-instances/:csv/:crd/:instanceName":
OperatorInstanceViewContainer,
Expand Down

0 comments on commit 6f55e10

Please sign in to comment.