Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Modify UI stream & team form #97

Merged
merged 11 commits into from
May 28, 2024
5 changes: 3 additions & 2 deletions api/cmd/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package cmd
import (
"testing"

"github.com/caraml-dev/mlp/api/pkg/authz/enforcer"
enforcerMock "github.com/caraml-dev/mlp/api/pkg/authz/enforcer/mocks"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/caraml-dev/mlp/api/pkg/authz/enforcer"
enforcerMock "github.com/caraml-dev/mlp/api/pkg/authz/enforcer/mocks"
)

func TestStartKetoBootsrap(t *testing.T) {
Expand Down
15 changes: 10 additions & 5 deletions api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,12 @@ type UIConfig struct {
StaticPath string `validated:"required"`
IndexPath string `validated:"required"`

ClockworkUIHomepage string `json:"REACT_APP_CLOCKWORK_UI_HOMEPAGE"`
KubeflowUIHomepage string `json:"REACT_APP_KUBEFLOW_UI_HOMEPAGE"`
ProjectInfoUpdateEnabled bool `json:"REACT_APP_PROJECT_INFO_UPDATE_ENABLED"`
ClockworkUIHomepage string `json:"REACT_APP_CLOCKWORK_UI_HOMEPAGE"`
KubeflowUIHomepage string `json:"REACT_APP_KUBEFLOW_UI_HOMEPAGE"`

AllowCustomStream bool `json:"REACT_APP_ALLOW_CUSTOM_STREAM"`
AllowCustomTeam bool `json:"REACT_APP_ALLOW_CUSTOM_TEAM"`
ProjectInfoUpdateEnabled bool `json:"REACT_APP_PROJECT_INFO_UPDATE_ENABLED"`
}

// Transform env variables to the format consumed by koanf.
Expand Down Expand Up @@ -214,8 +217,10 @@ var defaultConfig = &Config{
TrackingURL: "",
},
UI: &UIConfig{
IndexPath: "index.html",
StaticPath: "ui/build",
IndexPath: "index.html",
StaticPath: "ui/build",
AllowCustomTeam: true,
AllowCustomStream: true,
},
DefaultSecretStorage: &SecretStorage{
Name: "internal",
Expand Down
14 changes: 10 additions & 4 deletions api/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ func TestLoad(t *testing.T) {
"EmptyStream": {},
},
UI: &config.UIConfig{
StaticPath: "ui/build",
IndexPath: "index.html",
StaticPath: "ui/build",
IndexPath: "index.html",
AllowCustomTeam: true,
AllowCustomStream: true,
},
DefaultSecretStorage: &config.SecretStorage{
Name: "default-secret-storage",
Expand Down Expand Up @@ -142,8 +144,10 @@ func TestLoad(t *testing.T) {
"EmptyStream": {},
},
UI: &config.UIConfig{
StaticPath: "ui/build",
IndexPath: "index.html",
StaticPath: "ui/build",
IndexPath: "index.html",
AllowCustomTeam: true,
AllowCustomStream: true,
},
DefaultSecretStorage: &config.SecretStorage{
Name: "default-secret-storage",
Expand Down Expand Up @@ -240,6 +244,8 @@ func TestLoad(t *testing.T) {

ClockworkUIHomepage: "http://clockwork.dev",
KubeflowUIHomepage: "http://kubeflow.org",
AllowCustomTeam: true,
AllowCustomStream: true,
ProjectInfoUpdateEnabled: true,
},
DefaultSecretStorage: &config.SecretStorage{
Expand Down
2 changes: 2 additions & 0 deletions ui/packages/app/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const config = {

CLOCKWORK_UI_HOMEPAGE: getEnv("REACT_APP_CLOCKWORK_UI_HOMEPAGE"),
KUBEFLOW_UI_HOMEPAGE: getEnv("REACT_APP_KUBEFLOW_UI_HOMEPAGE"),
ALLOW_CUSTOM_STREAM: getEnv("REACT_APP_ALLOW_CUSTOM_STREAM") || true,
ALLOW_CUSTOM_TEAM: getEnv("REACT_APP_ALLOW_CUSTOM_TEAM") || true,
PROJECT_INFO_UPDATE_ENABLED:
getEnv("REACT_APP_PROJECT_INFO_UPDATE_ENABLED") || false
};
Expand Down
6 changes: 3 additions & 3 deletions ui/packages/app/src/project_setting/form/Labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
EuiButton,
EuiFormRow
} from "@elastic/eui";
import { isDNS1123Label } from "../../validation/validation";
import { isValidK8sLabelKeyValue } from "../../validation/validation";

export const Labels = ({
labels,
Expand Down Expand Up @@ -52,7 +52,7 @@ export const Labels = ({
newItems[idx] = {
...newItems[idx],
key: newKey,
isKeyValid: isDNS1123Label(newKey)
isKeyValid: isValidK8sLabelKeyValue(newKey)
};
setItems(newItems);
onChange(newItems);
Expand All @@ -66,7 +66,7 @@ export const Labels = ({
newItems[idx] = {
...newItems[idx],
value: newValue,
isValueValid: isDNS1123Label(newValue)
isValueValid: isValidK8sLabelKeyValue(newValue)
};
setItems(newItems);
onChange(newItems);
Expand Down
5 changes: 3 additions & 2 deletions ui/packages/app/src/project_setting/form/ProjectForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { addToast, useMlpApi } from "@caraml-dev/ui-lib";
import { ProjectFormContext } from "./context";
import { EmailTextArea } from "./EmailTextArea";
import { Labels } from "./Labels";
import { isValidK8sLabelKeyValue } from "../../validation/validation";
import { Stream } from "./Stream";
import { Team } from "./Team";
import { isDNS1123Label } from "../../validation/validation";
import { useNavigate } from "react-router-dom";

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

const [isValidStream, setIsValidStream] = useState(false);

const [isValidTeam, setIsValidTeam] = useState(false);

const onAdminValueChange = emails => {
Expand Down
10 changes: 5 additions & 5 deletions ui/packages/app/src/project_setting/form/Stream.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useMemo } from "react";
import { EuiFormRow } from "@elastic/eui";
import { EuiComboBoxSelect } from "@caraml-dev/ui-lib";
import { isDNS1123Label } from "../../validation/validation";
import { isValidK8sLabelKeyValue } from "../../validation/validation";
import config from "../../config";

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

const onStreamChange = stream => {
let isValid = isDNS1123Label(stream);
let isValid = isValidK8sLabelKeyValue(stream);
if (!isValid) {
setStreamError(
"Stream name is invalid. It should contain only lowercase alphanumeric and dash (-), and must start and end with an alphanumeric character"
"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"
);
}
setIsValidStream(isValid);
Expand All @@ -37,8 +37,8 @@ export const Stream = ({
value={stream}
options={streamOptions}
onChange={onStreamChange}
onCreateOption={onStreamChange}
isDisabled={isDisabled}
deadlycoconuts marked this conversation as resolved.
Show resolved Hide resolved
onCreateOption={config.ALLOW_CUSTOM_STREAM ? onStreamChange : undefined}
isDiasbled={isDisabled}
/>
</EuiFormRow>
);
Expand Down
10 changes: 5 additions & 5 deletions ui/packages/app/src/project_setting/form/Team.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useMemo } from "react";
import { EuiFormRow } from "@elastic/eui";
import { EuiComboBoxSelect } from "@caraml-dev/ui-lib";
import { isDNS1123Label } from "../../validation/validation";
import { isValidK8sLabelKeyValue } from "../../validation/validation";
import config from "../../config";

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

const onTeamChange = team => {
let isValid = isDNS1123Label(team);
let isValid = isValidK8sLabelKeyValue(team);
if (!isValid) {
setTeamError(
"Team name is invalid. It should contain only lowercase alphanumeric and dash (-), and must start and end with an alphanumeric character"
"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"
);
}
setIsValidTeam(isValid);
Expand All @@ -43,8 +43,8 @@ export const Team = ({
value={team}
options={teamOptions}
onChange={onTeamChange}
onCreateOption={onTeamChange}
isDisabled={isDisabled}
onCreateOption={config.ALLOW_CUSTOM_TEAM ? onTeamChange : undefined}
isDiasbled={isDisabled}
/>
</EuiFormRow>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from "@elastic/eui";
import SubmitProjectInfoForm from "./SubmitProjectInfoForm";
import config from "../../config";
import { isDNS1123Label } from "../../validation/validation";
import { isValidK8sLabelKeyValue } from "../../validation/validation";
import { ProjectFormContext } from "../form/context";
import { Labels } from "../form/Labels";
import { Stream } from "../form/Stream";
Expand All @@ -21,18 +21,20 @@ const ProjectInfoForm = ({ originalProject, fetchUpdates }) => {
);

const [isValidStream, setIsValidStream] = useState(
isDNS1123Label(project.stream)
isValidK8sLabelKeyValue(project.stream)
);
const [isValidTeam, setIsValidTeam] = useState(
isValidK8sLabelKeyValue(project.team)
);
const [isValidTeam, setIsValidTeam] = useState(isDNS1123Label(project.team));

const [isValidLabels, setIsValidLabels] = useState(
project.labels.length === 0
? true
: project.labels.reduce((labelsValid, label) => {
return (
labelsValid &&
isDNS1123Label(label.key) &&
isDNS1123Label(label.value)
isValidK8sLabelKeyValue(label.key) &&
isValidK8sLabelKeyValue(label.value)
);
}, true)
);
Expand Down
6 changes: 4 additions & 2 deletions ui/packages/app/src/validation/validation.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Test whether the value follow RFC1123 format
const DNS1123LabelMaxLength = 63;
export const isDNS1123Label = value => {
const expression = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;

// See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set for full details
export const isValidK8sLabelKeyValue = value => {
const expression = /^[a-z0-9]([_.\-a-z0-9]*[a-z0-9])?$/;
if (value === undefined || value.length > DNS1123LabelMaxLength) {
return false;
}
Expand Down
Loading