Skip to content

Commit f3449ff

Browse files
authored
Merge branch 'master' into pr-osparc-connect-opentelemetry-to-missing-services
2 parents c678283 + f7e6d5b commit f3449ff

File tree

39 files changed

+1637
-1037
lines changed

39 files changed

+1637
-1037
lines changed

api/specs/web-server/_folders.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,27 @@ async def list_folders(
6363
...
6464

6565

66+
@router.get(
67+
"/folders:search",
68+
response_model=Envelope[list[FolderGet]],
69+
)
70+
async def list_folders_full_search(
71+
params: Annotated[PageQueryParameters, Depends()],
72+
order_by: Annotated[
73+
Json,
74+
Query(
75+
description="Order by field (modified_at|name|description) and direction (asc|desc). The default sorting order is ascending.",
76+
example='{"field": "name", "direction": "desc"}',
77+
),
78+
] = '{"field": "modified_at", "direction": "desc"}',
79+
filters: Annotated[
80+
Json | None,
81+
Query(description=FolderFilters.schema_json(indent=1)),
82+
] = None,
83+
):
84+
...
85+
86+
6687
@router.get(
6788
"/folders/{folder_id}",
6889
response_model=Envelope[FolderGet],

packages/models-library/src/models_library/folders.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
11
from datetime import datetime
2+
from enum import auto
23
from typing import TypeAlias
34

4-
from models_library.users import GroupID, UserID
5-
from models_library.workspaces import WorkspaceID
6-
from pydantic import BaseModel, Field, PositiveInt
5+
from pydantic import BaseModel, Field, PositiveInt, validator
6+
7+
from .access_rights import AccessRights
8+
from .users import GroupID, UserID
9+
from .utils.enums import StrAutoEnum
10+
from .workspaces import WorkspaceID
711

812
FolderID: TypeAlias = PositiveInt
913

1014

15+
class FolderScope(StrAutoEnum):
16+
ROOT = auto()
17+
SPECIFIC = auto()
18+
ALL = auto()
19+
20+
21+
class FolderQuery(BaseModel):
22+
folder_scope: FolderScope
23+
folder_id: PositiveInt | None = None
24+
25+
@validator("folder_id", pre=True, always=True)
26+
@classmethod
27+
def validate_folder_id(cls, value, values):
28+
scope = values.get("folder_scope")
29+
if scope == FolderScope.SPECIFIC and value is None:
30+
raise ValueError(
31+
"folder_id must be provided when folder_scope is SPECIFIC."
32+
)
33+
if scope != FolderScope.SPECIFIC and value is not None:
34+
raise ValueError(
35+
"folder_id should be None when folder_scope is not SPECIFIC."
36+
)
37+
return value
38+
39+
1140
#
1241
# DB
1342
#
@@ -38,3 +67,10 @@ class FolderDB(BaseModel):
3867

3968
class Config:
4069
orm_mode = True
70+
71+
72+
class UserFolderAccessRightsDB(FolderDB):
73+
my_access_rights: AccessRights
74+
75+
class Config:
76+
orm_mode = True

packages/models-library/src/models_library/workspaces.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
from datetime import datetime
2+
from enum import auto
23
from typing import TypeAlias
34

4-
from models_library.access_rights import AccessRights
5-
from models_library.users import GroupID
6-
from pydantic import BaseModel, Field, PositiveInt
5+
from pydantic import BaseModel, Field, PositiveInt, validator
6+
7+
from .access_rights import AccessRights
8+
from .users import GroupID
9+
from .utils.enums import StrAutoEnum
710

811
WorkspaceID: TypeAlias = PositiveInt
912

1013

14+
class WorkspaceScope(StrAutoEnum):
15+
PRIVATE = auto()
16+
SHARED = auto()
17+
ALL = auto()
18+
19+
20+
class WorkspaceQuery(BaseModel):
21+
workspace_scope: WorkspaceScope
22+
workspace_id: PositiveInt | None = None
23+
24+
@validator("workspace_id", pre=True, always=True)
25+
@classmethod
26+
def validate_workspace_id(cls, value, values):
27+
scope = values.get("workspace_scope")
28+
if scope == WorkspaceScope.SHARED and value is None:
29+
raise ValueError(
30+
"workspace_id must be provided when workspace_scope is SHARED."
31+
)
32+
if scope != WorkspaceScope.SHARED and value is not None:
33+
raise ValueError(
34+
"workspace_id should be None when workspace_scope is not SHARED."
35+
)
36+
return value
37+
38+
1139
#
1240
# DB
1341
#
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def assemble_array_groups(user_group_ids: list[int]) -> str:
2+
return (
3+
"array[]::text[]"
4+
if len(user_group_ids) == 0
5+
else f"""array[{', '.join(f"'{group_id}'" for group_id in user_group_ids)}]"""
6+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from simcore_postgres_database.models.groups import user_to_groups
2+
from simcore_postgres_database.models.workspaces_access_rights import (
3+
workspaces_access_rights,
4+
)
5+
from sqlalchemy import func
6+
from sqlalchemy.dialects.postgresql import BOOLEAN, INTEGER
7+
from sqlalchemy.sql import Subquery, select
8+
9+
10+
def create_my_workspace_access_rights_subquery(user_id: int) -> Subquery:
11+
return (
12+
select(
13+
workspaces_access_rights.c.workspace_id,
14+
func.json_build_object(
15+
"read",
16+
func.max(workspaces_access_rights.c.read.cast(INTEGER)).cast(BOOLEAN),
17+
"write",
18+
func.max(workspaces_access_rights.c.write.cast(INTEGER)).cast(BOOLEAN),
19+
"delete",
20+
func.max(workspaces_access_rights.c.delete.cast(INTEGER)).cast(BOOLEAN),
21+
).label("my_access_rights"),
22+
)
23+
.select_from(
24+
workspaces_access_rights.join(
25+
user_to_groups, user_to_groups.c.gid == workspaces_access_rights.c.gid
26+
)
27+
)
28+
.where(user_to_groups.c.uid == user_id)
29+
.group_by(workspaces_access_rights.c.workspace_id)
30+
).subquery("my_workspace_access_rights_subquery")

services/autoscaling/tests/unit/test_core_settings.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import datetime
66
import json
7+
import os
78

89
import pytest
910
from faker import Faker
@@ -197,11 +198,42 @@ def test_EC2_INSTANCES_ALLOWED_TYPES_passing_valid_image_tags( # noqa: N802
197198
def test_EC2_INSTANCES_ALLOWED_TYPES_empty_not_allowed( # noqa: N802
198199
app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch
199200
):
201+
assert app_environment["AUTOSCALING_EC2_INSTANCES"] == "{}"
200202
monkeypatch.setenv("EC2_INSTANCES_ALLOWED_TYPES", "{}")
201203

202-
with pytest.raises(ValidationError):
204+
# test child settings
205+
with pytest.raises(ValidationError) as err_info:
206+
EC2InstancesSettings.create_from_envs()
207+
208+
assert err_info.value.errors()[0]["loc"] == ("EC2_INSTANCES_ALLOWED_TYPES",)
209+
210+
211+
def test_EC2_INSTANCES_ALLOWED_TYPES_empty_not_allowed_with_main_field_env_var( # noqa: N802
212+
app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch
213+
):
214+
assert os.environ["AUTOSCALING_EC2_INSTANCES"] == "{}"
215+
monkeypatch.setenv("EC2_INSTANCES_ALLOWED_TYPES", "{}")
216+
217+
# now as part of AUTOSCALING_EC2_INSTANCES: EC2InstancesSettings | None
218+
with pytest.raises(ValidationError) as exc_before:
219+
ApplicationSettings.create_from_envs(AUTOSCALING_EC2_INSTANCES={})
220+
221+
with pytest.raises(ValidationError) as exc_after:
203222
ApplicationSettings.create_from_envs()
204223

224+
assert exc_before.value.errors() == exc_after.value.errors()
225+
226+
227+
def test_EC2_INSTANCES_ALLOWED_TYPES_empty_not_allowed_without_main_field_env_var( # noqa: N802
228+
app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch
229+
):
230+
monkeypatch.delenv("AUTOSCALING_EC2_INSTANCES")
231+
monkeypatch.setenv("EC2_INSTANCES_ALLOWED_TYPES", "{}")
232+
233+
# removing any value for AUTOSCALING_EC2_INSTANCES
234+
settings = ApplicationSettings.create_from_envs()
235+
assert settings.AUTOSCALING_EC2_INSTANCES is None
236+
205237

206238
def test_invalid_instance_names(
207239
app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch, faker: Faker

services/static-webserver/client/source/class/osparc/dashboard/ResourceBrowserBase.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ qx.Class.define("osparc.dashboard.ResourceBrowserBase", {
116116
};
117117
osparc.data.Resources.fetch("studies", "getWallet", params)
118118
.then(wallet => {
119-
if (isStudyCreation || wallet === null || osparc.desktop.credits.Utils.getWallet(wallet["walletId"]) === null) {
119+
if (
120+
isStudyCreation ||
121+
wallet === null ||
122+
osparc.desktop.credits.Utils.getWallet(wallet["walletId"]) === null
123+
) {
120124
// pop up study options if the study was just created or if it has no wallet assigned or user has no access to it
121125
const resourceSelector = new osparc.study.StudyOptions(studyId);
122126
const win = osparc.study.StudyOptions.popUpInWindow(resourceSelector);

services/static-webserver/client/source/class/osparc/dashboard/ResourceDetails.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ qx.Class.define("osparc.dashboard.ResourceDetails", {
364364
const resourceData = this.__resourceData;
365365
if (osparc.utils.Resources.isStudy(resourceData)) {
366366
const id = "Billing";
367-
const title = this.tr("Billing Settings");
367+
const title = this.tr("Tier Settings");
368368
const iconSrc = "@FontAwesome5Solid/cogs/22";
369369
const page = this.__billingSettings = new osparc.dashboard.resources.pages.BasePage(title, iconSrc, id);
370370
this.__addOpenButton(page);

services/static-webserver/client/source/class/osparc/dashboard/StudyBrowser.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,11 @@ qx.Class.define("osparc.dashboard.StudyBrowser", {
374374
newWorkspaceCard.setCardKey("new-workspace");
375375
newWorkspaceCard.subscribeToFilterGroup("searchBarFilter");
376376
[
377-
"createWorkspace",
378-
"updateWorkspace"
377+
"workspaceCreated",
378+
"workspaceDeleted",
379+
"workspaceUpdated",
379380
].forEach(e => {
380-
newWorkspaceCard.addListener(e, () => {
381-
this.__reloadWorkspaces();
382-
});
381+
newWorkspaceCard.addListener(e, () => this.__reloadWorkspaces());
383382
});
384383
this._resourcesContainer.addNewWorkspaceCard(newWorkspaceCard);
385384
},
@@ -1170,7 +1169,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", {
11701169
__newStudyBtnClicked: function(button) {
11711170
button.setValue(false);
11721171
const minStudyData = osparc.data.model.Study.createMinStudyObject();
1173-
const title = osparc.utils.Utils.getUniqueStudyName(minStudyData.name, this._resourcesList);
1172+
const existingNames = this._resourcesList.map(study => study["name"]);
1173+
const title = osparc.utils.Utils.getUniqueName(minStudyData.name, existingNames);
11741174
minStudyData["name"] = title;
11751175
minStudyData["workspaceId"] = this.getCurrentWorkspaceId();
11761176
minStudyData["folderId"] = this.getCurrentFolderId();
@@ -1190,7 +1190,8 @@ qx.Class.define("osparc.dashboard.StudyBrowser", {
11901190
__newPlanBtnClicked: function(templateData, newStudyName) {
11911191
// do not override cached template data
11921192
const templateCopyData = osparc.utils.Utils.deepCloneObject(templateData);
1193-
const title = osparc.utils.Utils.getUniqueStudyName(newStudyName, this._resourcesList);
1193+
const existingNames = this._resourcesList.map(study => study["name"]);
1194+
const title = osparc.utils.Utils.getUniqueName(newStudyName, existingNames);
11941195
templateCopyData.name = title;
11951196
this._showLoadingPage(this.tr("Creating ") + (newStudyName || osparc.product.Utils.getStudyAlias()));
11961197
const contextProps = {
@@ -1411,7 +1412,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", {
14111412
},
14121413

14131414
__getBillingMenuButton: function(card) {
1414-
const text = osparc.utils.Utils.capitalize(this.tr("Billing Settings..."));
1415+
const text = osparc.utils.Utils.capitalize(this.tr("Tier Settings..."));
14151416
const studyBillingSettingsButton = new qx.ui.menu.Button(text);
14161417
studyBillingSettingsButton["billingSettingsButton"] = true;
14171418
studyBillingSettingsButton.addListener("tap", () => card.openBilling(), this);

services/static-webserver/client/source/class/osparc/dashboard/StudyBrowserHeader.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,10 @@ qx.Class.define("osparc.dashboard.StudyBrowserHeader", {
339339

340340
__editWorkspace: function() {
341341
const workspace = osparc.store.Workspaces.getInstance().getWorkspace(this.getCurrentWorkspaceId());
342-
const permissionsView = new osparc.editor.WorkspaceEditor(workspace);
342+
const workspaceEditor = new osparc.editor.WorkspaceEditor(workspace);
343343
const title = this.tr("Edit Workspace");
344-
const win = osparc.ui.window.Window.popUpInWindow(permissionsView, title, 300, 200);
345-
permissionsView.addListener("workspaceUpdated", () => {
344+
const win = osparc.ui.window.Window.popUpInWindow(workspaceEditor, title, 300, 150);
345+
workspaceEditor.addListener("workspaceUpdated", () => {
346346
win.close();
347347
this.__buildLayout();
348348
}, this);

0 commit comments

Comments
 (0)