Skip to content

Commit 70d2d4f

Browse files
author
Krish Agarwal
authored
CONSOLE-4522: Create a create form for CronTab resource in console-crontab-plugin (#31)
* Replaced YAML to Form View for creating CronTabs * WIP: Fixing CI and Form Component * WIP: ran i18n
1 parent 8868e38 commit 70d2d4f

10 files changed

Lines changed: 276 additions & 11 deletions

File tree

console-extensions.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,23 @@
6262
}
6363
}
6464
},
65+
{
66+
"type": "console.resource/create",
67+
"properties": {
68+
"name": "default",
69+
"model": {
70+
"group": "stable.example.com",
71+
"kind": "CronTab",
72+
"version": "v1"
73+
},
74+
"component": {
75+
"$codeRef": "CronTabForm"
76+
},
77+
"flags": {
78+
"required": ["CRONTAB"]
79+
}
80+
}
81+
},
6582
{
6683
"type": "console.resource/details-item",
6784
"properties": {

integration-tests/e2e/crud.cy.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { nav } from "../views/nav";
1010
import { guidedTour } from "../views/guided-tour";
1111

1212
const cronTabName = "my-new-cron-object";
13+
const cronSpecValue = "* * * * */5";
14+
const imageValue = "my-awesome-cron-image";
15+
const replicasValue = 1;
1316
const testLabel = "key1=value1";
1417
const annotations = [
1518
{
@@ -47,8 +50,14 @@ describe(`${PLUGIN_NAME}`, () => {
4750
cy.log("Create CronTab");
4851
nav.sidenav.clickNavLink(["Workloads", "CronTabs"]);
4952
common.resourceTitleShouldHaveText("CronTabs");
50-
listPage.clickCreateYAMLbutton();
51-
cy.get('[data-test="page-heading"] h1').should("contain", "Create CronTab");
53+
listPage.clickCreateForm();
54+
cy.byTestID("page-heading").should("contain", "Create CronTab");
55+
cy.log("Filling CronTab form");
56+
cy.byTestID("crontab-name").type(cronTabName);
57+
cy.byTestID("crontab-cronSpec").type(cronSpecValue);
58+
cy.byTestID("crontab-image").type(imageValue);
59+
cy.byTestID("crontab-replicas").find("input").clear();
60+
cy.byTestID("crontab-replicas").find("input").type(`${replicasValue}`);
5261
cy.byTestID("save-changes").click();
5362
common.inlineDangerAlert().should("not.exist");
5463
common.resourceTitleShouldHaveText(cronTabName);

integration-tests/views/list-page.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const listPage = {
2-
clickCreateYAMLbutton: () => {
2+
clickCreateForm: () => {
33
cy.byTestID("item-create").click();
44
},
55
rows: {
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
11
{
2+
"A unique identifier for this CronTab within the project.": "A unique identifier for this CronTab within the project.",
23
"Actions": "Actions",
4+
"Cancel": "Cancel",
5+
"Create": "Create",
36
"Create CronTab": "Create CronTab",
47
"Created": "Created",
58
"CronSpec": "CronSpec",
69
"CronTabs": "CronTabs",
10+
"Decrease replicas": "Decrease replicas",
11+
"Defines the schedule on which the job will run (e.g., */5 * * * *).": "Defines the schedule on which the job will run (e.g., */5 * * * *).",
712
"Delete CronTab": "Delete CronTab",
813
"Edit annotations": "Edit annotations",
914
"Edit CronTab": "Edit CronTab",
1015
"Edit labels": "Edit labels",
16+
"Error creating CronTab: {{err}}": "Error creating CronTab: {{err}}",
1117
"Image": "Image",
18+
"Increase replicas": "Increase replicas",
1219
"Name": "Name",
1320
"Namespace": "Namespace",
14-
"Replicas": "Replicas"
15-
}
21+
"Number of replicas": "Number of replicas",
22+
"replicas": "replicas",
23+
"Replicas": "Replicas",
24+
"Specifies the container image to be executed by the CronTab.": "Specifies the container image to be executed by the CronTab.",
25+
"The desired number of instances of this CronTab to run.": "The desired number of instances of this CronTab to run."
26+
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"react-i18next": "^11.8.11",
5757
"react-router": "^5.2.0",
5858
"react-router-dom": "^5.2.0",
59+
"react-router-dom-v5-compat": "^6.22.2",
5960
"resolve-url-loader": "^5.0.0",
6061
"sass": "^1.58.0",
6162
"sass-loader": "^13.2.0",
@@ -76,7 +77,8 @@
7677
"description": "Sample plugin for the OpenShift Console. This plugin adds basic functionality that allows users to manage CronTab custom resources.",
7778
"exposedModules": {
7879
"CronTabList": "./views/CronTabList/CronTabList.tsx",
79-
"yamlTemplates": "src/templates/index.ts"
80+
"yamlTemplates": "src/templates/index.ts",
81+
"CronTabForm": "src/views/CronTabForm/CronTabForm.tsx"
8082
},
8183
"dependencies": {
8284
"@console/pluginAPI": "^4.11"

src/const.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const CRONTAB_APIGROUP = "stable.example.com";
22
export const CRONTAB_APIVERSION = "v1";
3+
export const CRONTAB_APIGROUP_VERSION = `${CRONTAB_APIGROUP}/${CRONTAB_APIVERSION}`;
34
export const CRONTAB_KIND = "CronTab";
45
export const CRONTAB_KIND_PLURAL = "CronTabs";
56
export const PLUGIN_NAME = "console-crontab-plugin";
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import React, { useState } from "react";
2+
import {
3+
Form,
4+
FormGroup,
5+
TextInput,
6+
ActionGroup,
7+
Button,
8+
PageSection,
9+
Title,
10+
Alert,
11+
NumberInput,
12+
FormHelperText,
13+
HelperText,
14+
HelperTextItem,
15+
} from "@patternfly/react-core";
16+
import { CRONTAB_APIGROUP_VERSION, CRONTAB_KIND } from "src/const";
17+
import {
18+
useK8sModel,
19+
k8sCreate,
20+
useActiveNamespace,
21+
} from "@openshift-console/dynamic-plugin-sdk";
22+
import { useNavigate } from "react-router-dom-v5-compat";
23+
import { useCronTabTranslation } from "@crontab-utils/hooks/useCronTabTranslation";
24+
import { cronTabGroupVersionKind } from "src/utils/utils";
25+
import { CronTabKind } from "@crontab-model/types";
26+
27+
export const CronTabForm: React.FC = () => {
28+
const [model] = useK8sModel(cronTabGroupVersionKind);
29+
const [name, setName] = useState("");
30+
const [cronSpec, setCronSpec] = useState("");
31+
const [image, setImage] = useState("");
32+
const [replicas, setReplicas] = useState<number | "">(0);
33+
const [loading, setLoading] = useState(false);
34+
const [error, setError] = useState("");
35+
const navigate = useNavigate();
36+
const { t } = useCronTabTranslation();
37+
const [namespace] = useActiveNamespace();
38+
39+
const onChangeReplicas = (event: React.FormEvent<HTMLInputElement>) => {
40+
const value = (event.target as HTMLInputElement).value;
41+
setReplicas(value === "" ? value : +value);
42+
};
43+
const onReplicasMinus = () => {
44+
setReplicas((currentReplicas) => (currentReplicas || 0) - 1);
45+
};
46+
const onReplicasPlus = () => {
47+
setReplicas((currentReplicas) => (currentReplicas || 0) + 1);
48+
};
49+
50+
const handleSubmit = (e: React.FormEvent) => {
51+
e.preventDefault();
52+
setLoading(true);
53+
setError("");
54+
55+
const data: CronTabKind = {
56+
apiVersion: CRONTAB_APIGROUP_VERSION,
57+
kind: CRONTAB_KIND,
58+
metadata: {
59+
name,
60+
namespace,
61+
},
62+
spec: {
63+
cronSpec,
64+
image,
65+
replicas: replicas ? replicas : 0,
66+
},
67+
};
68+
69+
k8sCreate({ model, data })
70+
.then(() => {
71+
setLoading(false);
72+
navigate(`/k8s/ns/${namespace}/stable.example.com~v1~CronTab/${name}`);
73+
})
74+
.catch((err) => {
75+
setLoading(false);
76+
setError(t("Error creating CronTab: {{err}}", { err: err.toString() }));
77+
});
78+
};
79+
return (
80+
<PageSection>
81+
<Title headingLevel="h1" data-test="page-heading">
82+
{t("Create CronTab")}
83+
</Title>
84+
<Form>
85+
<FormGroup label={t("Name")} fieldId="crontab-name" isRequired>
86+
<TextInput
87+
id="crontab-name"
88+
data-test="crontab-name"
89+
name="name"
90+
onChange={(_e, value) => setName(value)}
91+
value={name}
92+
required
93+
/>
94+
<FormHelperText>
95+
<HelperText>
96+
<HelperTextItem>
97+
{t("A unique identifier for this CronTab within the project.")}
98+
</HelperTextItem>
99+
</HelperText>
100+
</FormHelperText>
101+
</FormGroup>
102+
<FormGroup label={t("CronSpec")} fieldId="crontab-cronSpec" isRequired>
103+
<TextInput
104+
id="crontab-cronSpec"
105+
data-test="crontab-cronSpec"
106+
value={cronSpec || ""}
107+
onChange={(_e, value) => setCronSpec(value)}
108+
required
109+
/>
110+
<FormHelperText>
111+
<HelperText>
112+
<HelperTextItem>
113+
{t(
114+
"Defines the schedule on which the job will run (e.g., */5 * * * *)."
115+
)}
116+
</HelperTextItem>
117+
</HelperText>
118+
</FormHelperText>
119+
</FormGroup>
120+
<FormGroup label={t("Image")} fieldId="crontab-image" isRequired>
121+
<TextInput
122+
id="crontab-image"
123+
data-test="crontab-image"
124+
value={image || ""}
125+
onChange={(_e, value) => setImage(value)}
126+
required
127+
/>
128+
<FormHelperText>
129+
<HelperText>
130+
<HelperTextItem>
131+
{t(
132+
"Specifies the container image to be executed by the CronTab."
133+
)}
134+
</HelperTextItem>
135+
</HelperText>
136+
</FormHelperText>
137+
</FormGroup>
138+
<FormGroup label={t("Replicas")} fieldId="crontab-replicas" isRequired>
139+
<NumberInput
140+
id="crontab-replicas"
141+
data-test="crontab-replicas"
142+
value={replicas}
143+
onChange={onChangeReplicas}
144+
onMinus={onReplicasMinus}
145+
onPlus={onReplicasPlus}
146+
inputName={t("replicas")}
147+
inputAriaLabel={t("Number of replicas")}
148+
minusBtnAriaLabel={t("Decrease replicas")}
149+
plusBtnAriaLabel={t("Increase replicas")}
150+
required
151+
min={0}
152+
/>
153+
<FormHelperText>
154+
<HelperText>
155+
<HelperTextItem>
156+
{t("The desired number of instances of this CronTab to run.")}
157+
</HelperTextItem>
158+
</HelperText>
159+
</FormHelperText>
160+
</FormGroup>
161+
{error && <Alert variant="danger" title={error} />}
162+
<ActionGroup>
163+
<Button
164+
type="button"
165+
variant="primary"
166+
isDisabled={loading || !name || !cronSpec || !image}
167+
onClick={handleSubmit}
168+
isLoading={loading}
169+
data-test="save-changes"
170+
>
171+
{t("Create")}
172+
</Button>
173+
<Button
174+
variant="secondary"
175+
isDisabled={loading}
176+
onClick={() => navigate(-1)}
177+
>
178+
{t("Cancel")}
179+
</Button>
180+
</ActionGroup>
181+
</Form>
182+
</PageSection>
183+
);
184+
};
185+
186+
export default CronTabForm;

src/views/CronTabList/CronTabList.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CronTabKind } from "@crontab-model/types";
33
import {
44
K8sResourceCommon,
55
ListPageBody,
6-
ListPageCreate,
6+
ListPageCreateLink,
77
ListPageFilter,
88
ListPageHeader,
99
Timestamp,
@@ -66,13 +66,24 @@ const CronTabList: React.FC<CronTabListProps> = ({
6666
const { t } = useCronTabTranslation();
6767
const columns = useCronTabColumns();
6868
const [data, filteredData, onFilterChange] = useListPageFilter(cronTabs);
69+
const createAccessReview = {
70+
groupVersionKind: cronTabGroupVersionKind,
71+
namespace: namespace || "default",
72+
};
73+
74+
const createURL = `/k8s/ns/${namespace || "default"}/${
75+
cronTabGroupVersionKind.group
76+
}~${cronTabGroupVersionKind.version}~${cronTabGroupVersionKind.kind}/~new`;
6977

7078
return (
7179
<>
7280
<ListPageHeader title={showTitle ? t("CronTabs") : undefined}>
73-
<ListPageCreate groupVersionKind={cronTabGroupVersionKind}>
81+
<ListPageCreateLink
82+
to={createURL}
83+
createAccessReview={createAccessReview}
84+
>
7485
{t("Create CronTab")}
75-
</ListPageCreate>
86+
</ListPageCreateLink>
7687
</ListPageHeader>
7788
<ListPageBody>
7889
<ListPageFilter

tsconfig.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@
77
"moduleResolution": "node",
88
"outDir": "dist",
99
"target": "es2016",
10-
"lib": ["es2016", "es2019", "es2020.promise", "dom", "dom.iterable", "ES2021.String"],
10+
"lib": [
11+
"es2016",
12+
"es2019",
13+
"es2020.promise",
14+
"dom",
15+
"dom.iterable",
16+
"ES2021.String"
17+
],
1118
"jsx": "react",
1219
"allowJs": true,
1320
"experimentalDecorators": true,

0 commit comments

Comments
 (0)