Skip to content

preserve settings state in db #147

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

Merged
merged 7 commits into from
Jul 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions client/src/Annotation/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getImagesAnnotation } from "../utils/send-data-to-server"
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import AlertDialog from "../AlertDialog";
import { clear_db } from "../utils/get-data-from-server"
import { clear_db, getSettings } from "../utils/get-data-from-server"
import colors from "../colors.js";
import {useTranslation} from "react-i18next"

Expand Down Expand Up @@ -57,7 +57,7 @@ export default () => {
const [selectedImageIndex, changeSelectedImageIndex] = useState(0)
const [open, setOpen] = useState(false);
const {t} = useTranslation();
const [showLabel, setShowLabel] = useState(false)
const [showLabel, setShowLabel] = useState(true)
const [isSettingsOpen, setIsSettingsOpen] = useState(false)
const [imageNames, setImageNames] = useState([])
const settingsConfig = useSettings()
Expand Down Expand Up @@ -230,20 +230,31 @@ export default () => {
reloadApp()
};

const preloadConfiguration = () => {
// get last saved configuration
const savedConfiguration = settingsConfig.settings|| {};
const lastSavedImageIndex = savedConfiguration.lastSavedImageIndex || 0;
if (savedConfiguration.configuration && savedConfiguration.configuration.labels.length > 0) {
setSettings(savedConfiguration);
if (savedConfiguration.images.length > 0) {
fetchImages(savedConfiguration.images, lastSavedImageIndex);
}
}
const showLab = settingsConfig.settings?.showLab || false;
if(!isSettingsOpen && showLab) {
setShowLabel(showLab)
}
const preloadConfiguration = async () => {
try {
const settings = await getSettings();
setShowLabel(false)
const localConfiguration = settingsConfig.settings || {};
// get last saved configuration
const savedConfiguration = settings || {};
let lastSavedImageIndex = savedConfiguration.lastSavedImageIndex || 0;
if(localConfiguration.lastSavedImageIndex){
lastSavedImageIndex = localConfiguration.lastSavedImageIndex;
}
setSettings(savedConfiguration);
if (savedConfiguration.configuration && savedConfiguration.configuration.labels.length > 0) {
setSettings(savedConfiguration);
if (savedConfiguration.images.length > 0) {
fetchImages(savedConfiguration.images, lastSavedImageIndex);
}
}
const showLab = settingsConfig.settings?.showLab || false;
if(!isSettingsOpen && showLab) {
setShowLabel(showLab)
}
} catch (error) {
console.error(error);
}
}

const showAnnotationLab = (newSettings) => {
Expand All @@ -257,7 +268,6 @@ export default () => {
preloadConfiguration();
}, [ ]);


return (
<>
{!showLabel ? (
Expand Down
11 changes: 7 additions & 4 deletions client/src/ConfigurationTask/index.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import React, { useMemo } from "react"
import Survey from "material-survey/components/Survey"
import { setIn } from "seamless-immutable"
import { setIn, asMutable } from "seamless-immutable"
import { CssBaseline, GlobalStyles } from "@mui/material";
import {useTranslation} from "react-i18next"

Expand All @@ -28,12 +28,15 @@ export default ({ config, onChange }) => {

],
}

const defaultAnswers = useMemo(
() => ({
() => asMutable({
taskDescription: config.taskDescription || "",
taskChoice: config.taskChoice !== undefined ? config.taskChoice : "image_classification",
}),
[config.taskDescription, config.taskChoice]
},
{deep: true}
),
[config.taskDescription, config.taskChoice]
);


Expand Down
6 changes: 4 additions & 2 deletions client/src/SetupPage/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import NoteSection from "../NoteSection";
import CloseIcon from "@mui/icons-material/Close";
import useMediaQuery from '@mui/material/useMediaQuery';
import config from '../config.js';
import { saveSettings } from "../utils/send-data-to-server.js";

const theme = createTheme()

Expand Down Expand Up @@ -128,12 +129,13 @@ export const SetupPage = ({setConfiguration, settings, setShowLabel, showAnnotat
}
}, [currentTab]);

const showLab = ()=> {
const showLab = async ()=> {
const hasLabels = configuration.labels.length > 0;
setShowLabel(hasLabels)
if(hasLabels) {
const newSettings = {...settings, showLab: true}
settingsConfig.changeSetting('settings',newSettings);
await saveSettings(newSettings)
showAnnotationLab(newSettings)
}
}
Expand Down Expand Up @@ -163,7 +165,7 @@ export const SetupPage = ({setConfiguration, settings, setShowLabel, showAnnotat
sx={{
position: 'absolute',
top: isLargeDevice ? '2rem' : '1rem',
right: isLargeDevice ? '10rem' : '1rem',
left: isLargeDevice ? 'calc(100vw - 10rem)' : 'calc(100vw - 3rem)',
fontSize: isLargeDevice ? '2rem' : '1.5rem'
}}>
{hasShowLab && hasConfig && <CloseIcon fontSize={isLargeDevice ? "large" : "medium"} />}
Expand Down
12 changes: 12 additions & 0 deletions client/src/utils/get-data-from-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ export const getLabels = () => {
})
}

export const getSettings = () => {
return new Promise((resolve, reject) => {
axios.get(`${config.SERVER_URL}/settings`)
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error?.response); // Reject with error data
});
})
}

export const clear_db = () => {
return new Promise((resolve, reject) => {
axios.post(`${config.SERVER_URL}/clearSession`)
Expand Down
12 changes: 12 additions & 0 deletions client/src/utils/send-data-to-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ export const getImagesAnnotation = (imageData) => {
});
};

export const saveSettings = (settings) => {
return new Promise((resolve, reject) => {
axios.post(`${config.SERVER_URL}/settings`, settings)
.then(response => {
resolve(response.data); // Resolve with response data
})
.catch(error => {
reject(error.response.data); // Reject with error data
});
});
};

export const saveActiveImage = (activeImage) => {
if (activeImage === null)
return
Expand Down
61 changes: 61 additions & 0 deletions server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,39 @@
os.makedirs(UPLOAD_FOLDER, exist_ok=True)


# File path to store task configuration
JSON_FILE = 'settings.json'


default_settings = {
"taskDescription": "",
"taskChoice": "image_classification",
"images": [],
"showLab": False,
"lastSavedImageIndex": None,
"configuration": {
"labels": [],
"multipleRegions": True,
"multipleRegionLabels": True,
"regionTypesAllowed": []
}
}

# Load initial data from JSON file if exists
try:
with open(JSON_FILE, 'r') as f:
initial_settings = json.load(f)
except FileNotFoundError:
initial_settings = default_settings.copy()

# Save initial settings to JSON file
with open(JSON_FILE, 'w') as f:
json.dump(initial_settings, f, indent=4)

def save_settings(settings):
with open(JSON_FILE, 'w') as f:
json.dump(settings, f, indent=4)

dbModule = Module()
path = os.path.abspath('./uploads')

Expand Down Expand Up @@ -67,6 +100,31 @@ def get_uploaded_files():
files.append({'filename': filename, 'url': file_url})
return files

def save_settings(settings):
with open(JSON_FILE, 'w') as f:
json.dump(settings, f, indent=4)

@app.route('/settings', methods=['GET'])
@cross_origin(origin=client_url, headers=['Content-Type'])
def get_settings():
return jsonify(initial_settings)

@app.route('/settings', methods=['POST'])
@cross_origin(origin=client_url, headers=['Content-Type'])
def update_settings():
new_settings = request.json
initial_settings.update(new_settings)
save_settings(initial_settings)
return jsonify({'message': 'Settings updated successfully'})

@app.route('/settings/reset', methods=['POST'])
@cross_origin(origin=client_url, headers=['Content-Type'])
def reset_settings():
global initial_settings
initial_settings = default_settings.copy()
save_settings(initial_settings)
return jsonify({'message': 'Settings reset to default values'})

@app.route('/upload', methods=['POST'])
@cross_origin(origin=client_url, headers=['Content-Type'])
def upload_file():
Expand Down Expand Up @@ -237,8 +295,11 @@ def clear_upload_folder():
@app.route('/clearSession', methods=['POST'])
@cross_origin(origin=client_url, headers=['Content-Type'])
def clear_session():
global initial_settings
try:
dbModule.clear_db()
initial_settings = default_settings.copy()
save_settings(initial_settings)
clear_upload_folder()
return jsonify({"message": "Database cleared successfully."}), 200

Expand Down
13 changes: 13 additions & 0 deletions server/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"taskDescription": "Updated task description",
"taskChoice": "image_classification",
"images": [],
"showLab": true,
"lastSavedImageIndex": null,
"configuration": {
"labels": [],
"multipleRegions": true,
"multipleRegionLabels": true,
"regionTypesAllowed": []
}
}
42 changes: 41 additions & 1 deletion server/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# import os
import json
from io import BytesIO
from app import app
from app import app, default_settings

class FlaskTestCase(unittest.TestCase):

Expand All @@ -29,6 +29,46 @@ def test_upload_file_invalid_file_type(self):
self.assertEqual(response.status_code, 400)
self.assertIn(b'File type not allowed', response.data)

def test_get_initial_settings(self):
response = self.app.get('/settings')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertEqual(data['taskDescription'], '')
self.assertEqual(data['taskChoice'], 'image_classification')

def test_update_settings(self):
updated_settings = {
'taskDescription': 'Updated task description',
'showLab': True
}
response = self.app.post('/settings', json=updated_settings)
self.assertEqual(response.status_code, 200)
response = self.app.get('/settings')
data = json.loads(response.data)
self.assertEqual(data['taskDescription'], 'Updated task description')
self.assertTrue(data['showLab'])

def test_reset_settings(self):
# Update settings first to ensure they are not default
updated_settings = {
'taskDescription': '',
'showLab': False
}
response = self.app.post('/settings', json=updated_settings)
self.assertEqual(response.status_code, 200)

# Reset settings to default
response = self.app.post('/settings/reset')
self.assertEqual(response.status_code, 200)
self.assertIn(b'Settings reset to default values', response.data)

# Verify settings are reset to default
response = self.app.get('/settings')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
for key, value in default_settings.items():
self.assertEqual(data[key], value)

def test_upload_file_success(self):
data = {
'file': (BytesIO(b'some file data'), 'test.png')
Expand Down
Loading