Skip to content

Commit 76bbea5

Browse files
authored
feat: Modify UI stream & team form (caraml-dev#97)
* Minor features to mlp * Modify project creation form to conditionally allow custom team/stream * Add new validation for team and stream to match k8s valid label values, except for capital letters * Fix naming convention * Refactor code to Stream and Team components * Fix formatting * Fix linting issue * Fix formatting * Address review comments * Fix pr comments * Remove unnecessary useEffect() * Fix label validation function
1 parent fc18b5c commit 76bbea5

File tree

10 files changed

+52
-33
lines changed

10 files changed

+52
-33
lines changed

api/cmd/bootstrap_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package cmd
33
import (
44
"testing"
55

6-
"github.com/caraml-dev/mlp/api/pkg/authz/enforcer"
7-
enforcerMock "github.com/caraml-dev/mlp/api/pkg/authz/enforcer/mocks"
86
"github.com/stretchr/testify/mock"
97
"github.com/stretchr/testify/require"
8+
9+
"github.com/caraml-dev/mlp/api/pkg/authz/enforcer"
10+
enforcerMock "github.com/caraml-dev/mlp/api/pkg/authz/enforcer/mocks"
1011
)
1112

1213
func TestStartKetoBootsrap(t *testing.T) {

api/config/config.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,12 @@ type UIConfig struct {
116116
StaticPath string `validated:"required"`
117117
IndexPath string `validated:"required"`
118118

119-
ClockworkUIHomepage string `json:"REACT_APP_CLOCKWORK_UI_HOMEPAGE"`
120-
KubeflowUIHomepage string `json:"REACT_APP_KUBEFLOW_UI_HOMEPAGE"`
121-
ProjectInfoUpdateEnabled bool `json:"REACT_APP_PROJECT_INFO_UPDATE_ENABLED"`
119+
ClockworkUIHomepage string `json:"REACT_APP_CLOCKWORK_UI_HOMEPAGE"`
120+
KubeflowUIHomepage string `json:"REACT_APP_KUBEFLOW_UI_HOMEPAGE"`
121+
122+
AllowCustomStream bool `json:"REACT_APP_ALLOW_CUSTOM_STREAM"`
123+
AllowCustomTeam bool `json:"REACT_APP_ALLOW_CUSTOM_TEAM"`
124+
ProjectInfoUpdateEnabled bool `json:"REACT_APP_PROJECT_INFO_UPDATE_ENABLED"`
122125
}
123126

124127
// Transform env variables to the format consumed by koanf.
@@ -214,8 +217,10 @@ var defaultConfig = &Config{
214217
TrackingURL: "",
215218
},
216219
UI: &UIConfig{
217-
IndexPath: "index.html",
218-
StaticPath: "ui/build",
220+
IndexPath: "index.html",
221+
StaticPath: "ui/build",
222+
AllowCustomTeam: true,
223+
AllowCustomStream: true,
219224
},
220225
DefaultSecretStorage: &SecretStorage{
221226
Name: "internal",

api/config/config_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ func TestLoad(t *testing.T) {
8585
"EmptyStream": {},
8686
},
8787
UI: &config.UIConfig{
88-
StaticPath: "ui/build",
89-
IndexPath: "index.html",
88+
StaticPath: "ui/build",
89+
IndexPath: "index.html",
90+
AllowCustomTeam: true,
91+
AllowCustomStream: true,
9092
},
9193
DefaultSecretStorage: &config.SecretStorage{
9294
Name: "default-secret-storage",
@@ -142,8 +144,10 @@ func TestLoad(t *testing.T) {
142144
"EmptyStream": {},
143145
},
144146
UI: &config.UIConfig{
145-
StaticPath: "ui/build",
146-
IndexPath: "index.html",
147+
StaticPath: "ui/build",
148+
IndexPath: "index.html",
149+
AllowCustomTeam: true,
150+
AllowCustomStream: true,
147151
},
148152
DefaultSecretStorage: &config.SecretStorage{
149153
Name: "default-secret-storage",
@@ -240,6 +244,8 @@ func TestLoad(t *testing.T) {
240244

241245
ClockworkUIHomepage: "http://clockwork.dev",
242246
KubeflowUIHomepage: "http://kubeflow.org",
247+
AllowCustomTeam: true,
248+
AllowCustomStream: true,
243249
ProjectInfoUpdateEnabled: true,
244250
},
245251
DefaultSecretStorage: &config.SecretStorage{

ui/packages/app/src/config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const config = {
3434

3535
CLOCKWORK_UI_HOMEPAGE: getEnv("REACT_APP_CLOCKWORK_UI_HOMEPAGE"),
3636
KUBEFLOW_UI_HOMEPAGE: getEnv("REACT_APP_KUBEFLOW_UI_HOMEPAGE"),
37+
ALLOW_CUSTOM_STREAM: getEnv("REACT_APP_ALLOW_CUSTOM_STREAM") || true,
38+
ALLOW_CUSTOM_TEAM: getEnv("REACT_APP_ALLOW_CUSTOM_TEAM") || true,
3739
PROJECT_INFO_UPDATE_ENABLED:
3840
getEnv("REACT_APP_PROJECT_INFO_UPDATE_ENABLED") || false
3941
};

ui/packages/app/src/project_setting/form/Labels.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
EuiButton,
99
EuiFormRow
1010
} from "@elastic/eui";
11-
import { isDNS1123Label } from "../../validation/validation";
11+
import { isValidK8sLabelKeyValue } from "../../validation/validation";
1212

1313
export const Labels = ({
1414
labels,
@@ -52,7 +52,7 @@ export const Labels = ({
5252
newItems[idx] = {
5353
...newItems[idx],
5454
key: newKey,
55-
isKeyValid: isDNS1123Label(newKey)
55+
isKeyValid: isValidK8sLabelKeyValue(newKey)
5656
};
5757
setItems(newItems);
5858
onChange(newItems);
@@ -66,7 +66,7 @@ export const Labels = ({
6666
newItems[idx] = {
6767
...newItems[idx],
6868
value: newValue,
69-
isValueValid: isDNS1123Label(newValue)
69+
isValueValid: isValidK8sLabelKeyValue(newValue)
7070
};
7171
setItems(newItems);
7272
onChange(newItems);

ui/packages/app/src/project_setting/form/ProjectForm.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import { addToast, useMlpApi } from "@caraml-dev/ui-lib";
1313
import { ProjectFormContext } from "./context";
1414
import { EmailTextArea } from "./EmailTextArea";
1515
import { Labels } from "./Labels";
16+
import { isValidK8sLabelKeyValue } from "../../validation/validation";
1617
import { Stream } from "./Stream";
1718
import { Team } from "./Team";
18-
import { isDNS1123Label } from "../../validation/validation";
1919
import { useNavigate } from "react-router-dom";
2020

2121
const ProjectForm = () => {
@@ -35,7 +35,7 @@ const ProjectForm = () => {
3535
const [isValidProject, setIsValidProject] = useState(false);
3636
const onProjectChange = e => {
3737
const newValue = e.target.value;
38-
let isValid = isDNS1123Label(newValue);
38+
let isValid = isValidK8sLabelKeyValue(newValue);
3939
if (!isValid) {
4040
setProjectError(
4141
"Project name is invalid. It should contain only lowercase alphanumeric and dash ('-')"
@@ -46,6 +46,7 @@ const ProjectForm = () => {
4646
};
4747

4848
const [isValidStream, setIsValidStream] = useState(false);
49+
4950
const [isValidTeam, setIsValidTeam] = useState(false);
5051

5152
const onAdminValueChange = emails => {

ui/packages/app/src/project_setting/form/Stream.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useMemo } from "react";
22
import { EuiFormRow } from "@elastic/eui";
33
import { EuiComboBoxSelect } from "@caraml-dev/ui-lib";
4-
import { isDNS1123Label } from "../../validation/validation";
4+
import { isValidK8sLabelKeyValue } from "../../validation/validation";
55
import config from "../../config";
66

77
export const Stream = ({
@@ -21,10 +21,10 @@ export const Stream = ({
2121
const [streamError, setStreamError] = useState("");
2222

2323
const onStreamChange = stream => {
24-
let isValid = isDNS1123Label(stream);
24+
let isValid = isValidK8sLabelKeyValue(stream);
2525
if (!isValid) {
2626
setStreamError(
27-
"Stream name is invalid. It should contain only lowercase alphanumeric and dash (-), and must start and end with an alphanumeric character"
27+
"Stream name is invalid. It should contain only lowercase alphanumeric and dash (-), or underscore (_) or period (.), and must start and end with an alphanumeric character"
2828
);
2929
}
3030
setIsValidStream(isValid);
@@ -37,8 +37,8 @@ export const Stream = ({
3737
value={stream}
3838
options={streamOptions}
3939
onChange={onStreamChange}
40-
onCreateOption={onStreamChange}
41-
isDisabled={isDisabled}
40+
onCreateOption={config.ALLOW_CUSTOM_STREAM ? onStreamChange : undefined}
41+
isDiasbled={isDisabled}
4242
/>
4343
</EuiFormRow>
4444
);

ui/packages/app/src/project_setting/form/Team.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useState, useEffect, useMemo } from "react";
22
import { EuiFormRow } from "@elastic/eui";
33
import { EuiComboBoxSelect } from "@caraml-dev/ui-lib";
4-
import { isDNS1123Label } from "../../validation/validation";
4+
import { isValidK8sLabelKeyValue } from "../../validation/validation";
55
import config from "../../config";
66

77
export const Team = ({
@@ -21,10 +21,10 @@ export const Team = ({
2121
const [teamError, setTeamError] = useState("");
2222

2323
const onTeamChange = team => {
24-
let isValid = isDNS1123Label(team);
24+
let isValid = isValidK8sLabelKeyValue(team);
2525
if (!isValid) {
2626
setTeamError(
27-
"Team name is invalid. It should contain only lowercase alphanumeric and dash (-), and must start and end with an alphanumeric character"
27+
"Team name is invalid. It should contain only lowercase alphanumeric and dash (-) or underscore (_) or period (.), and must start and end with an alphanumeric character"
2828
);
2929
}
3030
setIsValidTeam(isValid);
@@ -43,8 +43,8 @@ export const Team = ({
4343
value={team}
4444
options={teamOptions}
4545
onChange={onTeamChange}
46-
onCreateOption={onTeamChange}
47-
isDisabled={isDisabled}
46+
onCreateOption={config.ALLOW_CUSTOM_TEAM ? onTeamChange : undefined}
47+
isDiasbled={isDisabled}
4848
/>
4949
</EuiFormRow>
5050
);

ui/packages/app/src/project_setting/project_info/ProjectInfoForm.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "@elastic/eui";
1010
import SubmitProjectInfoForm from "./SubmitProjectInfoForm";
1111
import config from "../../config";
12-
import { isDNS1123Label } from "../../validation/validation";
12+
import { isValidK8sLabelKeyValue } from "../../validation/validation";
1313
import { ProjectFormContext } from "../form/context";
1414
import { Labels } from "../form/Labels";
1515
import { Stream } from "../form/Stream";
@@ -21,18 +21,20 @@ const ProjectInfoForm = ({ originalProject, fetchUpdates }) => {
2121
);
2222

2323
const [isValidStream, setIsValidStream] = useState(
24-
isDNS1123Label(project.stream)
24+
isValidK8sLabelKeyValue(project.stream)
25+
);
26+
const [isValidTeam, setIsValidTeam] = useState(
27+
isValidK8sLabelKeyValue(project.team)
2528
);
26-
const [isValidTeam, setIsValidTeam] = useState(isDNS1123Label(project.team));
2729

2830
const [isValidLabels, setIsValidLabels] = useState(
2931
project.labels.length === 0
3032
? true
3133
: project.labels.reduce((labelsValid, label) => {
3234
return (
3335
labelsValid &&
34-
isDNS1123Label(label.key) &&
35-
isDNS1123Label(label.value)
36+
isValidK8sLabelKeyValue(label.key) &&
37+
isValidK8sLabelKeyValue(label.value)
3638
);
3739
}, true)
3840
);

ui/packages/app/src/validation/validation.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Test whether the value follow RFC1123 format
22
const DNS1123LabelMaxLength = 63;
3-
export const isDNS1123Label = value => {
4-
const expression = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;
3+
4+
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set for full details
5+
export const isValidK8sLabelKeyValue = value => {
6+
const expression = /^[a-z0-9]([_.\-a-z0-9]*[a-z0-9])?$/;
57
if (value === undefined || value.length > DNS1123LabelMaxLength) {
68
return false;
79
}

0 commit comments

Comments
 (0)