Skip to content

Commit fc18b5c

Browse files
jonathanv28Jonathan Victor Goklas
andauthored
Add Edit Metadata Page & Codecov integration (caraml-dev#93)
* first initialization of projectinfo with codecov integration * add project info form with submit * add feature flag with update project info modal * reuse components for project creation and project info * change label error * update form fields error message * fix feature flag for labels' add button * fix feature flag for update labels' add button --------- Co-authored-by: Jonathan Victor Goklas <[email protected]>
1 parent b89dab5 commit fc18b5c

File tree

15 files changed

+578
-154
lines changed

15 files changed

+578
-154
lines changed

.github/workflows/ci-pipeline.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ jobs:
124124
- name: Run Integration Test
125125
run: make it-test-api
126126

127+
- uses: codecov/codecov-action@v4
128+
with:
129+
flags: api-test
130+
name: api-test
131+
token: ${{ secrets.CODECOV_TOKEN }}
132+
working-directory: ./api
133+
127134
e2e-test:
128135
runs-on: ubuntu-latest
129136
needs:
@@ -359,4 +366,4 @@ jobs:
359366
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
360367
run: |
361368
yarn set-version-from-git
362-
yarn lib publish
369+
yarn lib publish

api/config/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,9 @@ 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"`
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"`
121122
}
122123

123124
// Transform env variables to the format consumed by koanf.

api/config/config_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,9 @@ func TestLoad(t *testing.T) {
238238
StaticPath: "ui/build",
239239
IndexPath: "index.html",
240240

241-
ClockworkUIHomepage: "http://clockwork.dev",
242-
KubeflowUIHomepage: "http://kubeflow.org",
241+
ClockworkUIHomepage: "http://clockwork.dev",
242+
KubeflowUIHomepage: "http://kubeflow.org",
243+
ProjectInfoUpdateEnabled: true,
243244
},
244245
DefaultSecretStorage: &config.SecretStorage{
245246
Name: "default-secret-storage",
@@ -322,6 +323,9 @@ func TestValidate(t *testing.T) {
322323
},
323324
},
324325
},
326+
UI: &config.UIConfig{
327+
ProjectInfoUpdateEnabled: true,
328+
},
325329
},
326330
},
327331
"extended | success": {
@@ -366,6 +370,9 @@ func TestValidate(t *testing.T) {
366370
},
367371
},
368372
},
373+
UI: &config.UIConfig{
374+
ProjectInfoUpdateEnabled: true,
375+
},
369376
},
370377
},
371378
"default config | failure": {
@@ -414,6 +421,9 @@ func TestValidate(t *testing.T) {
414421
},
415422
},
416423
},
424+
UI: &config.UIConfig{
425+
ProjectInfoUpdateEnabled: true,
426+
},
417427
},
418428
error: errors.New(
419429
"failed to validate configuration: " +
@@ -459,6 +469,9 @@ func TestValidate(t *testing.T) {
459469
},
460470
},
461471
},
472+
UI: &config.UIConfig{
473+
ProjectInfoUpdateEnabled: true,
474+
},
462475
},
463476
error: errors.New(
464477
"failed to validate configuration: " +

api/config/testdata/config-2.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ authorization:
2828
ui:
2929
clockworkUIHomepage: http://clockwork.dev
3030
kubeflowUIHomepage: http://kubeflow.org
31+
projectinfoUpdateEnabled: true
3132

3233
defaultSecretStorage:
3334
name: default-secret-storage

ui/packages/app/src/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ const config = {
3333
),
3434

3535
CLOCKWORK_UI_HOMEPAGE: getEnv("REACT_APP_CLOCKWORK_UI_HOMEPAGE"),
36-
KUBEFLOW_UI_HOMEPAGE: getEnv("REACT_APP_KUBEFLOW_UI_HOMEPAGE")
36+
KUBEFLOW_UI_HOMEPAGE: getEnv("REACT_APP_KUBEFLOW_UI_HOMEPAGE"),
37+
PROJECT_INFO_UPDATE_ENABLED:
38+
getEnv("REACT_APP_PROJECT_INFO_UPDATE_ENABLED") || false
3739
};
3840

3941
export default config;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { useContext } from "react";
2+
import ProjectInfoForm from "./project_info/ProjectInfoForm";
3+
import { ProjectFormContextProvider } from "./form/context";
4+
import { ProjectsContext } from "@caraml-dev/ui-lib";
5+
import { Project } from "./form/project";
6+
import { EuiLoadingChart, EuiTextAlign } from "@elastic/eui";
7+
8+
const ProjectInfoSetting = () => {
9+
const { currentProject, refresh } = useContext(ProjectsContext);
10+
11+
return (
12+
<>
13+
{!currentProject ? (
14+
<EuiTextAlign textAlign="center">
15+
<EuiLoadingChart size="xl" mono />
16+
</EuiTextAlign>
17+
) : (
18+
<ProjectFormContextProvider project={Project.from(currentProject)}>
19+
<ProjectInfoForm
20+
originalProject={currentProject}
21+
fetchUpdates={refresh}
22+
/>
23+
</ProjectFormContextProvider>
24+
)}
25+
</>
26+
);
27+
};
28+
29+
export default ProjectInfoSetting;

ui/packages/app/src/project_setting/ProjectSetting.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { EuiSideNav, EuiIcon, EuiPageTemplate } from "@elastic/eui";
33
import { slugify } from "@caraml-dev/ui-lib/src/utils";
44
import UserRoleSetting from "./UserRoleSetting";
55
import SecretSetting from "./SecretSetting";
6+
import ProjectInfoSetting from "./ProjectInfoSetting";
67
import {
78
Navigate,
89
Route,
@@ -16,6 +17,10 @@ const sections = {
1617
iconType: "user",
1718
name: "User Roles"
1819
},
20+
"project-info": {
21+
iconType: "iInCircle",
22+
name: "Project Info"
23+
},
1924
"secrets-management": {
2025
iconType: "lock",
2126
name: "Secrets"
@@ -64,6 +69,7 @@ const ProjectSetting = () => {
6469
<Routes>
6570
<Route index element={<Navigate to="user-roles" replace={true} />} />
6671
<Route path="user-roles" element={<UserRoleSetting />} />
72+
<Route path="project-info" element={<ProjectInfoSetting />} />
6773
<Route path="secrets-management" element={<SecretSetting />} />
6874
<Route
6975
path="*"

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

Lines changed: 108 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,32 @@ import {
55
EuiFieldText,
66
EuiFlexGroup,
77
EuiFlexItem,
8-
EuiButton
8+
EuiButton,
9+
EuiFormRow
910
} from "@elastic/eui";
1011
import { isDNS1123Label } from "../../validation/validation";
1112

12-
export const Labels = ({ onChange }) => {
13-
const [items, setItems] = useState([]);
13+
export const Labels = ({
14+
labels,
15+
setLabels,
16+
setIsValidLabels,
17+
isValidLabels,
18+
isDisabled = false
19+
}) => {
20+
const [items, setItems] = useState(
21+
(e => {
22+
if (e) {
23+
return e.map((label, idx) => ({
24+
...label,
25+
idx,
26+
isKeyValid: true,
27+
isValueValid: true
28+
}));
29+
} else {
30+
return [];
31+
}
32+
})(labels)
33+
);
1434

1535
const addItem = () => {
1636
const newItems = [
@@ -62,57 +82,91 @@ export const Labels = ({ onChange }) => {
6282
};
6383
};
6484

65-
return (
66-
<EuiFlexGroup direction="column" gutterSize="m">
67-
{items.map((element, idx) => {
68-
return (
69-
<EuiFlexItem>
70-
<EuiFlexGroup gutterSize="s">
71-
<EuiFlexItem grow={1}>
72-
<EuiFieldText
73-
placeholder="key"
74-
value={element.key}
75-
onChange={onKeyChange(idx)}
76-
isInvalid={!element.isKeyValid}
77-
/>
78-
</EuiFlexItem>
79-
<EuiFlexItem grow={1}>
80-
<EuiFieldText
81-
placeholder="value"
82-
value={element.value}
83-
onChange={onValueChange(idx)}
84-
isInvalid={!element.isValueValid}
85-
/>
86-
</EuiFlexItem>
87-
<EuiFlexItem grow={false}>
88-
<EuiButtonEmpty
89-
iconType="trash"
90-
onClick={removeElement(idx)}
91-
color="danger"
92-
/>
93-
</EuiFlexItem>
94-
</EuiFlexGroup>
95-
</EuiFlexItem>
96-
);
97-
})}
85+
const [labelError, setLabelError] = useState("");
86+
const onChange = labels => {
87+
const labelsValid =
88+
labels.length === 0
89+
? true
90+
: labels.reduce((labelsValid, label) => {
91+
return labelsValid && label.isKeyValid && label.isValueValid;
92+
}, true);
93+
setIsValidLabels(labelsValid);
94+
if (!labelsValid) {
95+
setLabelError(
96+
"Invalid labels. Both key and value of a label must contain only lowercase alphanumeric and dash (-), and must start and end with an alphanumeric character"
97+
);
98+
}
99+
100+
//deep copy
101+
let newLabels = JSON.parse(JSON.stringify(labels));
102+
newLabels = newLabels.map(element => {
103+
delete element.isKeyValid;
104+
delete element.isValueValid;
105+
delete element.idx;
106+
return element;
107+
});
98108

99-
<EuiFlexItem>
100-
<EuiButton
101-
iconType="plusInCircle"
102-
onClick={addItem}
103-
disabled={
104-
items.length === 0
105-
? false
106-
: items.reduce((addButtonDisabled, currentValue) => {
107-
return (
108-
addButtonDisabled ||
109-
!currentValue.isKeyValid ||
110-
!currentValue.isValueValid
111-
);
112-
}, false)
113-
}
114-
/>
115-
</EuiFlexItem>
116-
</EuiFlexGroup>
109+
setLabels(newLabels);
110+
111+
console.log(isDisabled || items.length === 0);
112+
};
113+
114+
return (
115+
<EuiFormRow isInvalid={!isValidLabels} error={labelError}>
116+
<EuiFlexGroup direction="column" gutterSize="m">
117+
{items.map((element, idx) => {
118+
return (
119+
<EuiFlexItem>
120+
<EuiFlexGroup gutterSize="s">
121+
<EuiFlexItem grow={1}>
122+
<EuiFieldText
123+
placeholder="key"
124+
value={element.key}
125+
onChange={onKeyChange(idx)}
126+
isInvalid={!element.isKeyValid}
127+
disabled={isDisabled}
128+
/>
129+
</EuiFlexItem>
130+
<EuiFlexItem grow={1}>
131+
<EuiFieldText
132+
placeholder="value"
133+
value={element.value}
134+
onChange={onValueChange(idx)}
135+
isInvalid={!element.isValueValid}
136+
disabled={isDisabled}
137+
/>
138+
</EuiFlexItem>
139+
<EuiFlexItem grow={false}>
140+
<EuiButtonEmpty
141+
iconType="trash"
142+
onClick={removeElement(idx)}
143+
color="danger"
144+
disabled={isDisabled}
145+
/>
146+
</EuiFlexItem>
147+
</EuiFlexGroup>
148+
</EuiFlexItem>
149+
);
150+
})}
151+
<EuiFlexItem>
152+
<EuiButton
153+
iconType="plusInCircle"
154+
onClick={addItem}
155+
disabled={
156+
isDisabled ||
157+
(items.length === 0
158+
? false
159+
: items.reduce((addButtonDisabled, currentValue) => {
160+
return (
161+
addButtonDisabled ||
162+
!currentValue.isKeyValid ||
163+
!currentValue.isValueValid
164+
);
165+
}, false))
166+
}
167+
/>
168+
</EuiFlexItem>
169+
</EuiFlexGroup>
170+
</EuiFormRow>
117171
);
118172
};

0 commit comments

Comments
 (0)