Skip to content

Commit c5fe060

Browse files
Merge pull request industrial-optimization-group#545 from giomara-larraga/GDM-SCOREbands
GDM SCOREbands
2 parents d1f54db + a38b997 commit c5fe060

16 files changed

Lines changed: 1473 additions & 287 deletions

File tree

desdeo/api/config.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ test_user_dm2_password = "dm2"
1515
[auth-debug]
1616
authjwt_secret_key = "36b96a23d24cebdeadce6d98fa53356111e6f3e85b8144d7273dcba230b9eb18"
1717
authjwt_algorithm = "HS256"
18-
authjwt_access_token_expires = 15 # in minutes
19-
authjwt_refresh_token_expires = 30 # in minutes
18+
authjwt_access_token_expires = 480 # in minutes (8 hours, extended for dev/testing)
19+
authjwt_refresh_token_expires = 480 # in minutes (8 hours, extended for dev/testing)
2020
cors_origins = [
2121
"http://localhost",
2222
"http://localhost:8000",

desdeo/api/db_init_gdm.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"""This module initializes the database."""
2+
3+
import warnings
4+
5+
import numpy as np
6+
from sqlalchemy_utils import database_exists
7+
from sqlmodel import Session, SQLModel
8+
9+
from desdeo.api.config import SettingsConfig
10+
from desdeo.api.db import engine
11+
from desdeo.api.models import (
12+
ProblemDB,
13+
User,
14+
UserRole,
15+
16+
)
17+
from desdeo.api.models.gdm.gdm_aggregate import Group
18+
from desdeo.api.routers.user_authentication import get_password_hash
19+
from desdeo.problem.testproblems import river_pollution_problem_discrete
20+
21+
problems = [river_pollution_problem_discrete(five_objective_variant=False)]
22+
23+
num_analysts = 1
24+
num_dms = 2
25+
26+
usernames_analyst = [f"analyst{i + 1}" for i in range(num_analysts)]
27+
usernames_dm = [f"dm{i + 1}" for i in range(num_dms)]
28+
29+
user_owner = User(
30+
id="1",
31+
username=usernames_analyst[0],
32+
password_hash=get_password_hash("12345"),
33+
role=UserRole.analyst,
34+
group="test",
35+
)
36+
37+
id_user = 1
38+
if __name__ == "__main__":
39+
if SettingsConfig.debug:
40+
# debug stuff
41+
42+
print("Creating database tables.")
43+
if not database_exists(engine.url):
44+
SQLModel.metadata.create_all(engine)
45+
else:
46+
warnings.warn("Database already exists. Clearing it.", stacklevel=1)
47+
# Drop all tables
48+
SQLModel.metadata.reflect(bind=engine)
49+
SQLModel.metadata.drop_all(bind=engine)
50+
SQLModel.metadata.create_all(engine)
51+
print("Database tables created.")
52+
53+
with Session(engine) as session:
54+
for user in usernames_analyst:
55+
user_analyst = User(
56+
id=str(id_user),
57+
username=user,
58+
password_hash=get_password_hash(
59+
"12345"
60+
),
61+
role=UserRole.analyst,
62+
group="test",
63+
group_ids=[1],
64+
)
65+
session.add(user_analyst)
66+
session.commit()
67+
session.refresh(user_analyst)
68+
id_user += 1
69+
70+
for user in usernames_dm:
71+
user_dm = User(
72+
id=str(id_user),
73+
username=user,
74+
password_hash=get_password_hash(
75+
"12345"
76+
),
77+
role=UserRole.dm,
78+
group="test",
79+
group_ids=[1],
80+
)
81+
session.add(user_dm)
82+
session.commit()
83+
session.refresh(user_dm)
84+
id_user += 1
85+
86+
rng = np.random.default_rng(seed=42)
87+
88+
for problem in problems:
89+
#Add the problem to the analyst1
90+
problem_db = ProblemDB.from_problem(problem, user_owner)
91+
session.add(problem_db)
92+
session.commit()
93+
session.refresh(problem_db)
94+
95+
# Create a group for GDM testing
96+
group_name = "tingalinga"
97+
group = Group(
98+
name=group_name,
99+
owner_id=1,
100+
user_ids=[2,3],
101+
problem_id=1,
102+
)
103+
104+
group.model_rebuild()
105+
106+
107+
session.add(group)
108+
session.commit()
109+
session.refresh(group)
110+
session.close()
111+
112+
else:
113+
# deployment stuff
114+
pass

desdeo/api/routers/gdm/gdm_aggregate.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,18 +198,17 @@ async def websocket_endpoint(
198198
await websocket.close()
199199
return
200200

201-
# We don't need the session here any more, so we can just close it.
202-
# I believe this releases connections to the pool
203-
session.close()
204-
205201
# Get the group manager object from the manager of group managers
206-
group_manager = await manager.get_group_manager(group_id=group_id, method=method)
202+
group_manager = await manager.get_group_manager(group_id=group_id, method=method, db_session=session)
207203
if group_manager is None:
204+
session.close()
208205
await websocket.send_text(f"Unknown method: {method}")
209206
await websocket.close()
210207
return
211208

212-
await group_manager.connect(user.id, websocket)
209+
await group_manager.connect(user.id, websocket, db_session=session)
210+
# Session is only needed for manager initialization/connection bookkeeping.
211+
session.close()
213212
logger.info(f"Group ID {group_id} manager's active connections {group_manager.sockets}")
214213
logger.info(f"Existing GroupManagers: {manager.group_managers}")
215214
while True:

desdeo/api/routers/problem.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ProblemDB,
1616
ProblemInfo,
1717
ProblemInfoSmall,
18+
Group,
1819
ProblemMetaDataDB,
1920
ProblemMetaDataGetRequest,
2021
ProblemSelectSolverRequest,
@@ -84,7 +85,21 @@ def get_problems(
8485
"""
8586
if user.role in (UserRole.analyst, UserRole.admin):
8687
return list(db_session.exec(select(ProblemDB)).all())
87-
return user.problems
88+
89+
problems_by_id: dict[int, ProblemDB] = {problem.id: problem for problem in user.problems}
90+
91+
groups = db_session.exec(select(Group)).all()
92+
visible_group_problem_ids = {
93+
group.problem_id
94+
for group in groups
95+
if user.id == group.owner_id or user.id in (group.user_ids or [])
96+
}
97+
if visible_group_problem_ids:
98+
group_problems = db_session.exec(select(ProblemDB).where(ProblemDB.id.in_(visible_group_problem_ids))).all()
99+
for problem in group_problems:
100+
problems_by_id[problem.id] = problem
101+
102+
return list(problems_by_id.values())
88103

89104

90105
@router.get("/all_info")
@@ -103,7 +118,21 @@ def get_problems_info(
103118
"""
104119
if user.role in (UserRole.analyst, UserRole.admin):
105120
return list(db_session.exec(select(ProblemDB)).all())
106-
return user.problems
121+
122+
problems_by_id: dict[int, ProblemDB] = {problem.id: problem for problem in user.problems}
123+
124+
groups = db_session.exec(select(Group)).all()
125+
visible_group_problem_ids = {
126+
group.problem_id
127+
for group in groups
128+
if user.id == group.owner_id or user.id in (group.user_ids or [])
129+
}
130+
if visible_group_problem_ids:
131+
group_problems = db_session.exec(select(ProblemDB).where(ProblemDB.id.in_(visible_group_problem_ids))).all()
132+
for problem in group_problems:
133+
problems_by_id[problem.id] = problem
134+
135+
return list(problems_by_id.values())
107136

108137

109138
@router.get("/{problem_id}")

desdeo/api/routers/utils.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from desdeo.api.db import get_session
1515
from desdeo.api.models import (
1616
ENautilusStepRequest,
17+
Group,
1718
InteractiveSessionDB,
1819
NautilusNavigatorInitRequest,
1920
NautilusNavigatorNavigateRequest,
@@ -48,12 +49,26 @@ def fetch_problem_with_role_check(user: User, problem_id: int, session: Session)
4849
"""
4950
if user.role in (UserRole.analyst, UserRole.admin):
5051
statement = select(ProblemDB).where(ProblemDB.id == problem_id)
51-
else:
52-
statement = select(ProblemDB).where(
53-
ProblemDB.user_id == user.id,
54-
ProblemDB.id == problem_id,
55-
)
56-
return session.exec(statement).first()
52+
return session.exec(statement).first()
53+
54+
# Primary access path for non-analyst/admin users: own problems.
55+
statement = select(ProblemDB).where(
56+
ProblemDB.user_id == user.id,
57+
ProblemDB.id == problem_id,
58+
)
59+
own_problem = session.exec(statement).first()
60+
if own_problem is not None:
61+
return own_problem
62+
63+
# Secondary access path: problems attached to groups where the user is owner/member.
64+
group_statement = select(Group).where(Group.problem_id == problem_id)
65+
groups = session.exec(group_statement).all()
66+
for group in groups:
67+
user_ids = group.user_ids or []
68+
if user.id == group.owner_id or user.id in user_ids:
69+
return session.exec(select(ProblemDB).where(ProblemDB.id == problem_id)).first()
70+
71+
return None
5772

5873

5974
def fetch_interactive_session_with_role_check(user: User, session_id: int, session: Session) -> InteractiveSessionDB:

webui/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ Thumbs.db
1919
# Vite
2020
vite.config.js.timestamp-*
2121
vite.config.ts.timestamp-*
22+
23+
# Auth
24+
.auth

webui/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818
"storybook": "storybook dev -p 6006",
1919
"build-storybook": "storybook build",
2020
"generate:client": "orval",
21-
"svelte-check": "svelte-check"
22-
},
21+
"svelte-check": "svelte-check",
22+
"dev:gdm-score": "playwright test --project=setup --project=gdm-score --headed",
23+
"auth:setup": "npx ts-node tests/auth.setup.ts" },
2324
"dependencies": {
2425
"@cortex-js/compute-engine": "^0.30.2",
2526
"@faker-js/faker": "^10.0.0",

webui/playwright.config.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,26 @@ export default defineConfig({
55
command: 'npm run build && npm run preview',
66
port: 4173
77
},
8-
testDir: 'e2e'
8+
testDir: '.',
9+
testMatch: ['e2e/**/*.test.ts', 'tests/**/*.spec.ts'],
10+
use: {
11+
launchOptions: {
12+
args: ['--start-maximized']
13+
}
14+
},
15+
projects: [
16+
{
17+
name: 'setup',
18+
testMatch: 'tests/auth.setup.ts'
19+
},
20+
{
21+
name: 'gdm-score',
22+
testMatch: 'tests/**/*.spec.ts',
23+
dependencies: ['setup']
24+
},
25+
{
26+
name: 'e2e',
27+
testMatch: 'e2e/**/*.test.ts'
28+
}
29+
]
930
});

0 commit comments

Comments
 (0)