Skip to content

Commit 0761445

Browse files
Merge branch 'add-conversation-messages-ws-events' of github.com:giancarloromeo/osparc-simcore into add-conversation-messages-ws-events
2 parents 1046d53 + 87cd2cc commit 0761445

File tree

7 files changed

+99
-41
lines changed

7 files changed

+99
-41
lines changed

services/invitations/src/simcore_service_invitations/cli.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import getpass
22
import logging
3+
import os
34

45
import typer
56
from cryptography.fernet import Fernet
@@ -14,7 +15,6 @@
1415
print_as_envfile,
1516
)
1617

17-
from . import web_server
1818
from ._meta import PROJECT_NAME, __version__
1919
from .core.settings import ApplicationSettings, MinimalApplicationSettings
2020
from .services.invitations import (
@@ -50,7 +50,7 @@ def generate_key(
5050
export INVITATIONS_SECRET_KEY=$(invitations-maker generate-key)
5151
"""
5252
assert ctx # nosec
53-
print(Fernet.generate_key().decode()) # noqa: T201
53+
typer.echo(Fernet.generate_key().decode())
5454

5555

5656
@main.command()
@@ -106,6 +106,10 @@ def invite(
106106
None,
107107
help=InvitationInputs.model_fields["trial_account_days"].description,
108108
),
109+
extra_credits_in_usd: int = typer.Option(
110+
None,
111+
help=InvitationInputs.model_fields["extra_credits_in_usd"].description,
112+
),
109113
product: str = typer.Option(
110114
None,
111115
help=InvitationInputs.model_fields["product"].description,
@@ -119,7 +123,7 @@ def invite(
119123
issuer=issuer,
120124
guest=TypeAdapter(EmailStr).validate_python(email),
121125
trial_account_days=trial_account_days,
122-
extra_credits_in_usd=None,
126+
extra_credits_in_usd=extra_credits_in_usd,
123127
product=product,
124128
)
125129

@@ -129,7 +133,7 @@ def invite(
129133
base_url=settings.INVITATIONS_OSPARC_URL,
130134
default_product=settings.INVITATIONS_DEFAULT_PRODUCT,
131135
)
132-
print(invitation_link) # noqa: T201
136+
typer.echo(invitation_link)
133137

134138

135139
@main.command()
@@ -149,18 +153,8 @@ def extract(ctx: typer.Context, invitation_url: str):
149153
)
150154
assert invitation.product is not None # nosec
151155

152-
print(invitation.model_dump_json(indent=1)) # noqa: T201
153-
154-
except (InvalidInvitationCodeError, ValidationError):
155-
_err_console.print("[bold red]Invalid code[/bold red]")
156-
156+
typer.echo(invitation.model_dump_json(indent=1))
157157

158-
@main.command()
159-
def serve(
160-
ctx: typer.Context,
161-
*,
162-
reload: bool = False,
163-
):
164-
"""Starts server with http API"""
165-
assert ctx # nosec
166-
web_server.start(log_level="info", reload=reload)
158+
except (InvalidInvitationCodeError, ValidationError) as err:
159+
typer.secho("Invalid code", fg=typer.colors.RED, bold=True, err=True)
160+
raise typer.Exit(os.EX_DATAERR) from err

services/invitations/tests/unit/api/test_api_invitations.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def test_create_invitation(
4040
assert invitation.issuer == invitation_input.issuer
4141
assert invitation.guest == invitation_input.guest
4242
assert invitation.trial_account_days == invitation_input.trial_account_days
43+
assert invitation.extra_credits_in_usd == invitation_input.extra_credits_in_usd
4344

4445
# checks issue with `//` reported in https://github.com/ITISFoundation/osparc-simcore/issues/7055
4546
assert invitation.invitation_url
@@ -61,6 +62,7 @@ def test_check_invitation(
6162
"issuer": invitation_data.issuer,
6263
"guest": invitation_data.guest,
6364
"trial_account_days": invitation_data.trial_account_days,
65+
"extra_credits_in_usd": invitation_data.extra_credits_in_usd,
6466
},
6567
auth=basic_auth,
6668
)
@@ -85,6 +87,7 @@ def test_check_invitation(
8587
assert invitation.issuer == invitation_data.issuer
8688
assert invitation.guest == invitation_data.guest
8789
assert invitation.trial_account_days == invitation_data.trial_account_days
90+
assert invitation.extra_credits_in_usd == invitation_data.extra_credits_in_usd
8891

8992

9093
def test_check_valid_invitation(

services/invitations/tests/unit/conftest.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from cryptography.fernet import Fernet
1111
from faker import Faker
1212
from models_library.products import ProductName
13+
from pydantic import PositiveInt, TypeAdapter
1314
from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict
1415
from pytest_simcore.helpers.typing_env import EnvVarsDict
1516
from simcore_service_invitations.services.invitations import InvitationInputs
@@ -85,6 +86,11 @@ def is_trial_account(request: pytest.FixtureRequest) -> bool:
8586
return request.param
8687

8788

89+
@pytest.fixture
90+
def extra_credits_in_usd(is_trial_account: bool) -> PositiveInt | None:
91+
return TypeAdapter(PositiveInt).validate_python(123) if is_trial_account else None
92+
93+
8894
@pytest.fixture
8995
def default_product() -> ProductName:
9096
return "s4llite"
@@ -98,7 +104,10 @@ def product(request: pytest.FixtureRequest) -> ProductName | None:
98104

99105
@pytest.fixture
100106
def invitation_data(
101-
is_trial_account: bool, faker: Faker, product: ProductName | None
107+
is_trial_account: bool,
108+
faker: Faker,
109+
product: ProductName | None,
110+
extra_credits_in_usd: PositiveInt | None,
102111
) -> InvitationInputs:
103112
# first version
104113
kwargs = {
@@ -110,4 +119,7 @@ def invitation_data(
110119
if product:
111120
kwargs["product"] = product
112121

122+
if extra_credits_in_usd is not None:
123+
kwargs["extra_credits_in_usd"] = extra_credits_in_usd
124+
113125
return InvitationInputs.model_validate(kwargs)

services/invitations/tests/unit/test_cli.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,24 @@ def test_invite_user_and_check_invitation(
4545
"INVITATIONS_DEFAULT_PRODUCT": default_product,
4646
}
4747

48-
expected = {
48+
expected_invitation = {
4949
**invitation_data.model_dump(exclude={"product"}),
5050
"product": environs["INVITATIONS_DEFAULT_PRODUCT"],
5151
}
5252

5353
# invitations-maker invite [email protected] --issuer=me --trial-account-days=3
54-
trial_account = ""
54+
other_options = ""
5555
if invitation_data.trial_account_days:
56-
trial_account = f"--trial-account-days={invitation_data.trial_account_days}"
56+
other_options = f"--trial-account-days={invitation_data.trial_account_days}"
57+
58+
if invitation_data.extra_credits_in_usd:
59+
other_options += (
60+
f" --extra-credits-in-usd={invitation_data.extra_credits_in_usd}"
61+
)
5762

5863
result = cli_runner.invoke(
5964
main,
60-
f"invite {invitation_data.guest} --issuer={invitation_data.issuer} {trial_account}",
65+
f"invite {invitation_data.guest} --issuer={invitation_data.issuer} {other_options}",
6166
env=environs,
6267
)
6368
assert result.exit_code == os.EX_OK, result.output
@@ -73,7 +78,7 @@ def test_invite_user_and_check_invitation(
7378
)
7479
assert result.exit_code == os.EX_OK, result.output
7580
assert (
76-
expected
81+
expected_invitation
7782
== TypeAdapter(InvitationInputs).validate_json(result.stdout).model_dump()
7883
)
7984

@@ -99,3 +104,24 @@ def test_list_settings(cli_runner: CliRunner, app_environment: EnvVarsDict):
99104
print(result.output)
100105
settings = ApplicationSettings.model_validate_json(result.output)
101106
assert settings == ApplicationSettings.create_from_envs()
107+
108+
109+
def test_extract_invalid_invitation_code(
110+
cli_runner: CliRunner, faker: Faker, app_environment: EnvVarsDict
111+
):
112+
"""Test that extract command handles invalid invitation codes properly"""
113+
# Create an invalid invitation URL
114+
invalid_invitation_url = f"{faker.url()}#invitation=invalid_code_123"
115+
116+
# Run extract command with invalid invitation URL
117+
result = cli_runner.invoke(
118+
main,
119+
f'extract "{invalid_invitation_url}"',
120+
env=app_environment,
121+
)
122+
123+
# Verify command exits with correct error code
124+
assert result.exit_code == os.EX_DATAERR
125+
126+
# Verify error message is displayed via stderr
127+
assert "Invalid code" in result.stdout

services/invitations/tests/unit/test_core_settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ def test_valid_application_settings(app_environment: EnvVarsDict):
4242
assert settings
4343

4444
assert settings == ApplicationSettings.create_from_envs()
45+
46+
assert settings.LOG_LEVEL == "INFO"

services/static-webserver/client/source/class/osparc/study/CreateFunction.js

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ qx.Class.define("osparc.study.CreateFunction", {
3333
},
3434

3535
statics: {
36-
createFunctionData: function(projectData, name, description, exposedInputs, exposedOutputs) {
36+
createFunctionData: function(projectData, name, description, defaultInputs = {}, exposedInputs = {}, exposedOutputs = {}) {
3737
const functionData = {
3838
"projectId": projectData["uuid"],
3939
"title": name,
@@ -60,18 +60,19 @@ qx.Class.define("osparc.study.CreateFunction", {
6060

6161
const parameters = osparc.study.Utils.extractFunctionableParameters(projectData["workbench"]);
6262
parameters.forEach(parameter => {
63-
const parameterLabel = parameter["label"];
64-
if (exposedInputs[parameterLabel]) {
63+
const parameterKey = parameter["label"];
64+
if (exposedInputs[parameterKey]) {
6565
const parameterMetadata = osparc.store.Services.getMetadata(parameter["key"], parameter["version"]);
6666
if (parameterMetadata) {
6767
const type = osparc.service.Utils.getParameterType(parameterMetadata);
68-
functionData["inputSchema"]["schema_content"]["properties"][parameterLabel] = {
68+
functionData["inputSchema"]["schema_content"]["properties"][parameterKey] = {
6969
"type": type,
7070
};
71-
functionData["inputSchema"]["schema_content"]["required"].push(parameterLabel);
71+
functionData["inputSchema"]["schema_content"]["required"].push(parameterKey);
7272
}
73-
} else {
74-
functionData["defaultInputs"][parameterLabel] = osparc.service.Utils.getParameterValue(parameter);
73+
}
74+
if (parameterKey in defaultInputs) {
75+
functionData["defaultInputs"][parameterKey] = defaultInputs[parameterKey];
7576
}
7677
});
7778

@@ -120,11 +121,12 @@ qx.Class.define("osparc.study.CreateFunction", {
120121
form.add(description, this.tr("Description"), null, "description");
121122

122123

124+
const defaultInputs = {};
123125
const exposedInputs = {};
124126
const exposedOutputs = {};
125127

126128
// INPUTS
127-
const inGrid = new qx.ui.layout.Grid(10, 6);
129+
const inGrid = new qx.ui.layout.Grid(12, 6);
128130
const inputsLayout = new qx.ui.container.Composite(inGrid).set({
129131
allowGrowX: false,
130132
alignX: "left",
@@ -163,7 +165,8 @@ qx.Class.define("osparc.study.CreateFunction", {
163165

164166
const parameters = osparc.study.Utils.extractFunctionableParameters(this.__studyData["workbench"]);
165167
parameters.forEach(parameter => {
166-
const parameterLabel = new qx.ui.basic.Label(parameter["label"]);
168+
const parameterKey = parameter["label"];
169+
const parameterLabel = new qx.ui.basic.Label(parameterKey);
167170
inputsLayout.add(parameterLabel, {
168171
row,
169172
column,
@@ -185,11 +188,28 @@ qx.Class.define("osparc.study.CreateFunction", {
185188
row,
186189
column,
187190
});
188-
exposedInputs[parameter["label"]] = true;
189-
parameterExposed.addListener("changeValue", e => exposedInputs[parameter["label"]] = e.getData());
191+
exposedInputs[parameterKey] = true;
192+
parameterExposed.addListener("changeValue", e => exposedInputs[parameterKey] = e.getData());
190193
column++;
191194

192-
const parameterDefaultValue = new qx.ui.basic.Label(String(osparc.service.Utils.getParameterValue(parameter)));
195+
const paramValue = osparc.service.Utils.getParameterValue(parameter);
196+
defaultInputs[parameterKey] = paramValue;
197+
let parameterDefaultValue = null;
198+
if (parameterMetadata && osparc.service.Utils.getParameterType(parameterMetadata) === "number") {
199+
parameterDefaultValue = new qx.ui.form.TextField(String(paramValue));
200+
parameterDefaultValue.addListener("changeValue", e => {
201+
const newValue = e.getData();
202+
const oldValue = e.getOldData();
203+
if (newValue === oldValue) {
204+
return;
205+
}
206+
const curatedValue = (!isNaN(parseFloat(newValue))) ? parseFloat(newValue) : parseFloat(oldValue);
207+
defaultInputs[parameterKey] = curatedValue;
208+
parameterDefaultValue.setValue(String(curatedValue));
209+
});
210+
} else {
211+
parameterDefaultValue = new qx.ui.basic.Label(String(paramValue));
212+
}
193213
inputsLayout.add(parameterDefaultValue, {
194214
row,
195215
column,
@@ -274,12 +294,12 @@ qx.Class.define("osparc.study.CreateFunction", {
274294
});
275295
createFunctionBtn.addListener("execute", () => {
276296
if (this.__form.validate()) {
277-
this.__createFunction(exposedInputs, exposedOutputs);
297+
this.__createFunction(defaultInputs, exposedInputs, exposedOutputs);
278298
}
279299
}, this);
280300
},
281301

282-
__createFunction: function(exposedInputs, exposedOutputs) {
302+
__createFunction: function(defaultInputs, exposedInputs, exposedOutputs) {
283303
this.__createFunctionBtn.setFetching(true);
284304

285305
// first publish it as a hidden template
@@ -300,7 +320,7 @@ qx.Class.define("osparc.study.CreateFunction", {
300320
task.addListener("resultReceived", e => {
301321
const templateData = e.getData();
302322
this.__updateTemplateMetadata(templateData);
303-
this.__registerFunction(templateData, exposedInputs, exposedOutputs);
323+
this.__registerFunction(templateData, defaultInputs, exposedInputs, exposedOutputs);
304324
});
305325
})
306326
.catch(err => {
@@ -325,11 +345,11 @@ qx.Class.define("osparc.study.CreateFunction", {
325345
.catch(err => console.error(err));
326346
},
327347

328-
__registerFunction: function(templateData, exposedInputs, exposedOutputs) {
348+
__registerFunction: function(templateData, defaultInputs, exposedInputs, exposedOutputs) {
329349
const nameField = this.__form.getItem("name");
330350
const descriptionField = this.__form.getItem("description");
331351

332-
const functionData = this.self().createFunctionData(templateData, nameField.getValue(), descriptionField.getValue(), exposedInputs, exposedOutputs);
352+
const functionData = this.self().createFunctionData(templateData, nameField.getValue(), descriptionField.getValue(), defaultInputs, exposedInputs, exposedOutputs);
333353
const params = {
334354
data: functionData,
335355
};

services/web/server/src/simcore_service_webserver/wallets/_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
async def _auto_add_default_wallet(
2121
app: web.Application,
22+
*,
2223
user_id: UserID,
2324
product_name: ProductName,
2425
extra_credits_in_usd: PositiveInt | None = None,

0 commit comments

Comments
 (0)