Skip to content

Commit dad2ee6

Browse files
committed
Fixed the settings page
1 parent 9ba5454 commit dad2ee6

File tree

4 files changed

+120
-149
lines changed

4 files changed

+120
-149
lines changed

server/src/main/java/com/objectcomputing/checkins/services/settings/SettingsController.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,23 @@ public SettingsResponseDTO findByName(@PathVariable @NotNull String name) {
7373
*/
7474
@Get("/options")
7575
@RequiredPermission(Permission.CAN_VIEW_SETTINGS)
76-
public List<SettingOption> getOptions() {
77-
return SettingOption.getOptions();
76+
public List<SettingsResponseDTO> getOptions() {
77+
List<SettingOption> options = SettingOption.getOptions();
78+
return options.stream().map(option -> {
79+
String value = "";
80+
UUID uuid = null;
81+
try {
82+
Setting s = settingsServices.findByName(option.name());
83+
uuid = s.getId();
84+
value = s.getValue();
85+
} catch(NotFoundException ex) {
86+
}
87+
return new SettingsResponseDTO(
88+
uuid, option.name(), option.getDescription(),
89+
option.getCategory(), option.getType(),
90+
option.getValues(),
91+
value);
92+
}).toList();
7893
}
7994

8095
/**

server/src/main/resources/logback.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
<!-- <logger name="com.objectcomputing.checkins.services" level="DEBUG" />-->
1717
<!-- <logger name="io.micronaut.data.query" level="TRACE"/>-->
18-
<logger name="io.micronaut.http.server" level="TRACE" />
19-
<logger name="io.micronaut.http.client" level="TRACE" />
18+
<!-- <logger name="io.micronaut.http.server" level="TRACE" />-->
19+
<!-- <logger name="io.micronaut.http.client" level="TRACE" />-->
2020

2121
</configuration>

web-ui/src/api/settings.js

+13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ export const getAllOptions = async cookie => {
1414
});
1515
};
1616

17+
export const getSettings = async (option, cookie) => {
18+
return resolve({
19+
method: 'PUT',
20+
url: settingsURL,
21+
headers: {
22+
'X-CSRF-Header': cookie,
23+
Accept: 'application/json',
24+
'Content-Type': 'application/json;charset=UTF-8'
25+
},
26+
data: option
27+
});
28+
};
29+
1730
export const putOption = async (option, cookie) => {
1831
return resolve({
1932
method: 'PUT',

web-ui/src/pages/SettingsPage.jsx

+88-145
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect, useState } from 'react';
1+
import React, { useCallback, useContext, useEffect, useState } from 'react';
22
import { UPDATE_TOAST } from '../context/actions';
33
import { AppContext } from '../context/AppContext';
44
import { Button, Typography } from '@mui/material';
@@ -29,177 +29,120 @@ const componentMapping = {
2929
STRING: SettingsString
3030
};
3131

32+
const constructFileHandler = setting => {
33+
return (file) => {
34+
console.log(`Pretend we saved that file for ${setting.name} somewhere (we didn't)...`);
35+
};
36+
};
37+
3238
const SettingsPage = () => {
3339
const fileRef = React.useRef(null);
3440
const { state, dispatch } = useContext(AppContext);
3541
const csrf = selectCsrfToken(state);
36-
const [settingsControls, setSettingsControls] = useState([]);
37-
const [update, setState] = useState();
42+
const [settings, setSettings] = useState([]);
43+
const [handlers, setHandlers] = useState({});
44+
const [update, setUpdate] = useState(true);
45+
const canView = selectHasViewSettingsPermission(state) ||
46+
selectHasAdministerSettingsPermission(state);
3847

3948
useEffect(() => {
4049
const fetchData = async () => {
4150
// Get the options from the server
42-
const allOptions =
43-
selectHasViewSettingsPermission(state) ||
44-
selectHasAdministerSettingsPermission(state)
51+
const allOptions = canView
4552
? (await getAllOptions(csrf)).payload.data
4653
: [];
4754

48-
if (allOptions) {
49-
// Sort the options by category, store them, and upate the state.
50-
setSettingsControls(
51-
allOptions.sort((l, r) => {
52-
if (l.category === r.category) {
53-
return l.name.localeCompare(r.name);
54-
} else {
55-
return l.category.localeCompare(r.category);
56-
}
57-
})
58-
);
55+
if (allOptions?.length !== 0) {
56+
// Sort the options by category, store them, and update the state.
57+
const sorted = allOptions.sort((l, r) => {
58+
if (l.category === r.category) {
59+
return l.name.localeCompare(r.name);
60+
} else {
61+
return l.category.localeCompare(r.category);
62+
}
63+
});
64+
65+
setSettings(sorted);
66+
setUpdate(false);
5967
}
6068
};
61-
if (csrf) {
69+
if (csrf && update) {
6270
fetchData();
6371
}
64-
}, [state, csrf]);
65-
66-
const handleLogoUrl = file => {
67-
if (csrf) {
68-
// TODO: Need to upload the file to a storage bucket...
69-
dispatch({
70-
type: UPDATE_TOAST,
71-
payload: {
72-
severity: 'warning',
73-
toast: 'The Logo URL setting has yet to be implemented.'
74-
}
75-
});
76-
}
77-
};
72+
}, [csrf, canView, update, setUpdate, setSettings]);
7873

79-
const keyedHandler = (key, event) => {
80-
if (handlers[key]) {
81-
handlers[key].setting.value = event.target.value;
82-
setState({ update: true });
83-
}
84-
};
85-
86-
const handlePulseEmailFrequency = event => {
87-
keyedHandler('PULSE_EMAIL_FREQUENCY', event);
88-
};
89-
90-
const handlers = {
91-
// File handlers do not modify settings values and, therefore, do not
92-
// need to keep a reference to the setting object. However, they do need
93-
// a file reference object.
94-
LOGO_URL: {
95-
onChange: handleLogoUrl,
96-
setting: fileRef
97-
},
98-
99-
// All others need to provide an `onChange` method and a `setting` object.
100-
PULSE_EMAIL_FREQUENCY: {
101-
onChange: handlePulseEmailFrequency,
102-
setting: undefined
103-
}
104-
};
105-
106-
const addHandlersToSettings = settings => {
107-
return settings
108-
? settings.map(setting => {
109-
const handler = handlers[setting.name.toUpperCase()];
110-
if (handler) {
111-
if (setting.type.toUpperCase() === 'FILE') {
112-
return {
113-
...setting,
114-
handleFunction: handler.onChange,
115-
fileRef: handler.setting
116-
};
117-
}
118-
119-
handler.setting = setting;
120-
return { ...setting, handleChange: handler.onChange };
121-
}
122-
123-
console.warn(`WARNING: No handler for ${setting.name}`);
124-
return setting;
125-
})
126-
: [];
127-
};
128-
129-
const save = async () => {
130-
let errors;
131-
let saved = 0;
132-
for (let key of Object.keys(handlers)) {
133-
const setting = handlers[key].setting;
134-
// The settings controller does not allow blank values.
135-
if (setting?.name && `${setting.value}` != '') {
136-
let res;
137-
if (setting.id) {
138-
res = await putOption(
139-
{ name: setting.name, value: setting.value },
140-
csrf
141-
);
74+
useEffect(() => {
75+
if(settings?.length !== 0) {
76+
setHandlers(settings.reduce((acc, curr) =>{
77+
if(curr.type.toUpperCase() === "FILE") {
78+
acc[curr.name] = constructFileHandler(curr);
14279
} else {
143-
res = await postOption(
144-
{ name: setting.name, value: setting.value },
145-
csrf
146-
);
147-
if (res?.payload?.data) {
148-
setting.id = res.payload.data.id;
149-
}
150-
}
151-
if (res?.error) {
152-
const error = res?.error?.message;
153-
if (errors) {
154-
errors += '\n' + error;
155-
} else {
156-
errors = error;
80+
acc[curr.name] = (event) => {
81+
const newSettings = [...settings];
82+
const setting = newSettings.find(test => test.name === curr.name);
83+
setting.value = event.target.value;
84+
setSettings(newSettings);
15785
}
15886
}
159-
if (res?.payload?.data) {
160-
saved++;
161-
}
162-
} else {
163-
console.warn(`WARNING: ${setting.name} not sent to the server`);
164-
}
87+
return acc;
88+
}, {}));
16589
}
90+
}, [setHandlers, setSettings, settings])
16691

167-
if (errors) {
168-
dispatch({
169-
type: UPDATE_TOAST,
170-
payload: {
171-
severity: 'error',
172-
toast: errors
173-
}
92+
const save = useCallback(async () => {
93+
if(settings && settings.length > 0) {
94+
let errors;
95+
let promises = settings.filter(setting => setting.type.toUpperCase() !== "FILE").map(setting => {
96+
let promise = setting.id ?
97+
putOption({ name: setting.name, value: setting.value }, csrf) :
98+
postOption({ name: setting.name, value: setting.value }, csrf);
99+
100+
return promise;
174101
});
175-
} else if (saved > 0) {
176-
dispatch({
177-
type: UPDATE_TOAST,
178-
payload: {
179-
severity: 'success',
180-
toast: 'Settings have been saved'
102+
103+
Promise.all(promises).then((results) => {
104+
results.forEach((res) => {
105+
if (res?.error) {
106+
const error = res?.error?.message;
107+
if (errors) {
108+
errors += '\n' + error;
109+
} else {
110+
errors = error;
111+
}
112+
}
113+
});
114+
115+
// Trigger load of updated settings values and display errors or success.
116+
setUpdate(true);
117+
if (errors) {
118+
dispatch({
119+
type: UPDATE_TOAST,
120+
payload: {
121+
severity: 'error',
122+
toast: errors
123+
}
124+
});
125+
} else {
126+
dispatch({
127+
type: UPDATE_TOAST,
128+
payload: {
129+
severity: 'success',
130+
toast: 'Settings have been saved'
131+
}
132+
});
181133
}
182134
});
183135
}
184-
};
185-
186-
/**
187-
* @typedef {Object} Controls
188-
* @property {ComponentName} componentName - The name of the component.
189-
*
190-
* @typedef {('SettingsBoolean'|'SettingsColor'|'SettingsFile'|'SettingsNumber'|'SettingsString')} ComponentName
191-
*/
136+
},[settings]);
192137

193-
/** @type {Controls[]} */
194-
const updatedSettingsControls = addHandlersToSettings(settingsControls);
195138
const categories = {};
196139

197-
return selectHasViewSettingsPermission(state) ||
198-
selectHasAdministerSettingsPermission(state) ? (
140+
return canView ? (
199141
<div className="settings-page">
200-
{updatedSettingsControls.map((componentInfo, index) => {
201-
const Component = componentMapping[componentInfo.type.toUpperCase()];
202-
const info = { ...componentInfo, name: titleCase(componentInfo.name) };
142+
{settings.map((setting, index) => {
143+
const Component = componentMapping[setting.type.toUpperCase()];
144+
const info = { ...setting, name: titleCase(setting.name) };
145+
setting.type === 'FILE' ? info.handleFunction = handlers[setting.name] : info.handleChange = handlers[setting.name];
203146
if (categories[info.category]) {
204147
return <Component key={index} {...info} />;
205148
} else {
@@ -222,8 +165,8 @@ const SettingsPage = () => {
222165
{
223166
// Check length against an explicit value. If length is zero, it will
224167
// be displayed instead of evaluated to false.
225-
settingsControls &&
226-
settingsControls.length > 0 &&
168+
settings &&
169+
settings.length > 0 &&
227170
selectHasAdministerSettingsPermission(state) && (
228171
<div className="buttons">
229172
<Button disableRipple color="primary" onClick={save}>

0 commit comments

Comments
 (0)