Skip to content

✨ Introduce license item checkout & release functionality (🗃️) #6960

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
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9c815b8
renaming wallets to credit account for frontend
matusdrobuliak66 Dec 13, 2024
6abf9bd
fix rut export CSV usage
matusdrobuliak66 Dec 13, 2024
a99b7cd
Merge branch 'master' into introduce-vip-models-pricing-6-part
matusdrobuliak66 Dec 13, 2024
abccfab
Merge branch 'master' into introduce-vip-models-pricing-6-part
matusdrobuliak66 Dec 13, 2024
e060492
implement rut part of checkout/release license
matusdrobuliak66 Dec 13, 2024
200b1db
implement rut part of checkout/release license
matusdrobuliak66 Dec 13, 2024
7406371
adding tests to rut
matusdrobuliak66 Dec 13, 2024
c4e56df
checkout release functionality
matusdrobuliak66 Dec 13, 2024
504d2c8
webserver functionality
matusdrobuliak66 Dec 16, 2024
7839e46
Merge branch 'master' into introduce-vip-models-pricing-6-part
matusdrobuliak66 Dec 16, 2024
68068fb
improve playwright e2e test
matusdrobuliak66 Dec 16, 2024
5dd8efa
Merge branch 'master' into introduce-vip-models-pricing-6-part
matusdrobuliak66 Dec 16, 2024
b803038
merge master
matusdrobuliak66 Dec 17, 2024
3f30f05
add get and list license usages
matusdrobuliak66 Dec 17, 2024
c0d5c8e
wire checkout and release in webserver
matusdrobuliak66 Dec 17, 2024
4c269c3
wire checkout and release in webserver
matusdrobuliak66 Dec 17, 2024
4eaaf16
wire checkout and release in webserver
matusdrobuliak66 Dec 17, 2024
34d4577
adding unit tests in webserver
matusdrobuliak66 Dec 17, 2024
4826fa8
renaming
matusdrobuliak66 Dec 17, 2024
1eaba29
renaming
matusdrobuliak66 Dec 17, 2024
a43626d
renaming
matusdrobuliak66 Dec 17, 2024
7a1dcf7
renaming
matusdrobuliak66 Dec 17, 2024
2a0b64f
renaming
matusdrobuliak66 Dec 17, 2024
f8c6567
imporve error handling
matusdrobuliak66 Dec 17, 2024
f38de47
fix api server
matusdrobuliak66 Dec 17, 2024
a150857
fix failing tests
matusdrobuliak66 Dec 17, 2024
043b78b
Merge branch 'master' into introduce-vip-models-pricing-6-part
matusdrobuliak66 Dec 17, 2024
8b41ebf
fix available tests
matusdrobuliak66 Dec 17, 2024
de312d3
fix mypy
matusdrobuliak66 Dec 17, 2024
68ee78c
fix e2e tests
matusdrobuliak66 Dec 18, 2024
7e8313c
Merge branch 'master' into introduce-vip-models-pricing-6-part
matusdrobuliak66 Dec 18, 2024
1ee8886
review @sanderegg
matusdrobuliak66 Dec 18, 2024
84eb893
review @pcrespov
matusdrobuliak66 Dec 18, 2024
69b0dcd
fix test
matusdrobuliak66 Dec 18, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from datetime import datetime
from typing import NamedTuple

from models_library.licensed_items import LicensedItemID
from models_library.products import ProductName
from models_library.resource_tracker import ServiceRunId
from models_library.resource_tracker_licensed_items_checkouts import (
LicensedItemCheckoutID,
)
from models_library.users import UserID
from models_library.wallets import WalletID
from pydantic import BaseModel, ConfigDict, PositiveInt


class LicensedItemCheckoutGet(BaseModel):
licensed_item_checkout_id: LicensedItemCheckoutID
licensed_item_id: LicensedItemID
wallet_id: WalletID
user_id: UserID
product_name: ProductName
service_run_id: ServiceRunId
started_at: datetime
stopped_at: datetime | None
num_of_seats: int

model_config = ConfigDict(
json_schema_extra={
"examples": [
{
"licensed_item_checkout_id": "beb16d18-d57d-44aa-a638-9727fa4a72ef",
"licensed_item_id": "303942ef-6d31-4ba8-afbe-dbb1fce2a953",
"wallet_id": 1,
"user_id": 1,
"product_name": "osparc",
"service_run_id": "run_1",
"started_at": "2023-01-11 13:11:47.293595",
"stopped_at": "2023-01-11 13:11:47.293595",
"num_of_seats": 1,
}
]
}
)


class LicensedItemsCheckoutsPage(NamedTuple):
items: list[LicensedItemCheckoutGet]
total: PositiveInt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from datetime import datetime
from typing import NamedTuple

from pydantic import PositiveInt

from ..licensed_items import LicensedItemID
from ..products import ProductName
from ..resource_tracker_licensed_items_checkouts import LicensedItemCheckoutID
from ..users import UserID
from ..wallets import WalletID
from ._base import OutputSchema


class LicensedItemCheckoutGet(OutputSchema):
licensed_item_checkout_id: LicensedItemCheckoutID
licensed_item_id: LicensedItemID
wallet_id: WalletID
user_id: UserID
product_name: ProductName
started_at: datetime
stopped_at: datetime | None
num_of_seats: int


class LicensedItemUsageGetPage(NamedTuple):
items: list[LicensedItemCheckoutGet]
total: PositiveInt
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
from decimal import Decimal
from typing import NamedTuple

from models_library.licensed_items import LicensedItemID
from models_library.products import ProductName
from models_library.resource_tracker import PricingUnitCostId
from models_library.resource_tracker_licensed_items_purchases import (
LicensedItemPurchaseID,
)
from models_library.users import UserID
from models_library.wallets import WalletID
from pydantic import PositiveInt

from ..licensed_items import LicensedItemID
from ..products import ProductName
from ..resource_tracker import PricingUnitCostId
from ..resource_tracker_licensed_items_purchases import LicensedItemPurchaseID
from ..users import UserID
from ..wallets import WalletID
from ._base import OutputSchema


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from typing import TypeAlias
from uuid import UUID

LicensedItemCheckoutID: TypeAlias = UUID
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""modify licenses DB

Revision ID: 3720518f82a7
Revises: 77ac824a77ff
Create Date: 2024-12-13 12:46:38.302027+00:00

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "3720518f82a7"
down_revision = "77ac824a77ff"
branch_labels = None
depends_on = None


def upgrade():
op.drop_column("resource_tracker_licensed_items_usage", "licensed_item_id")
op.add_column(
"resource_tracker_licensed_items_usage",
sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=False),
)


def downgrade():
...
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""rename usages to checkouts

Revision ID: aa6da21a0055
Revises: 3720518f82a7
Create Date: 2024-12-17 13:47:09.304574+00:00

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "aa6da21a0055"
down_revision = "3720518f82a7"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"resource_tracker_licensed_items_checkouts",
sa.Column(
"licensed_item_checkout_id",
postgresql.UUID(as_uuid=True),
server_default=sa.text("gen_random_uuid()"),
nullable=False,
),
sa.Column("licensed_item_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("wallet_id", sa.BigInteger(), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("user_email", sa.String(), nullable=True),
sa.Column("product_name", sa.String(), nullable=False),
sa.Column("service_run_id", sa.String(), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("stopped_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("num_of_seats", sa.SmallInteger(), nullable=False),
sa.Column(
"modified",
sa.DateTime(timezone=True),
server_default=sa.text("now()"),
nullable=False,
),
sa.ForeignKeyConstraint(
["product_name", "service_run_id"],
[
"resource_tracker_service_runs.product_name",
"resource_tracker_service_runs.service_run_id",
],
name="resource_tracker_license_checkouts_service_run_id_fkey",
onupdate="CASCADE",
ondelete="RESTRICT",
),
sa.PrimaryKeyConstraint("licensed_item_checkout_id"),
)
op.create_index(
op.f("ix_resource_tracker_licensed_items_checkouts_wallet_id"),
"resource_tracker_licensed_items_checkouts",
["wallet_id"],
unique=False,
)
op.drop_index(
"ix_resource_tracker_licensed_items_usage_wallet_id",
table_name="resource_tracker_licensed_items_usage",
)
op.drop_table("resource_tracker_licensed_items_usage")
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"resource_tracker_licensed_items_usage",
sa.Column(
"licensed_item_usage_id",
postgresql.UUID(),
server_default=sa.text("gen_random_uuid()"),
autoincrement=False,
nullable=False,
),
sa.Column("wallet_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column("user_id", sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column("user_email", sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column("product_name", sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column("service_run_id", sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column(
"started_at",
postgresql.TIMESTAMP(timezone=True),
autoincrement=False,
nullable=False,
),
sa.Column(
"stopped_at",
postgresql.TIMESTAMP(timezone=True),
autoincrement=False,
nullable=True,
),
sa.Column("num_of_seats", sa.SMALLINT(), autoincrement=False, nullable=False),
sa.Column(
"modified",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
autoincrement=False,
nullable=False,
),
sa.Column(
"licensed_item_id", postgresql.UUID(), autoincrement=False, nullable=False
),
sa.ForeignKeyConstraint(
["product_name", "service_run_id"],
[
"resource_tracker_service_runs.product_name",
"resource_tracker_service_runs.service_run_id",
],
name="resource_tracker_license_checkouts_service_run_id_fkey",
onupdate="CASCADE",
ondelete="RESTRICT",
),
sa.PrimaryKeyConstraint(
"licensed_item_usage_id", name="resource_tracker_licensed_items_usage_pkey"
),
)
op.create_index(
"ix_resource_tracker_licensed_items_usage_wallet_id",
"resource_tracker_licensed_items_usage",
["wallet_id"],
unique=False,
)
op.drop_index(
op.f("ix_resource_tracker_licensed_items_checkouts_wallet_id"),
table_name="resource_tracker_licensed_items_checkouts",
)
op.drop_table("resource_tracker_licensed_items_checkouts")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
from ._common import RefActions, column_modified_datetime
from .base import metadata

resource_tracker_licensed_items_usage = sa.Table(
"resource_tracker_licensed_items_usage",
resource_tracker_licensed_items_checkouts = sa.Table(
"resource_tracker_licensed_items_checkouts",
metadata,
sa.Column(
"licensed_item_usage_id",
"licensed_item_checkout_id",
UUID(as_uuid=True),
nullable=False,
primary_key=True,
server_default=sa.text("gen_random_uuid()"),
),
sa.Column(
"licensed_item_id",
sa.String,
UUID(as_uuid=True),
nullable=True,
),
sa.Column(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from common_library.errors_classes import OsparcErrorMixin


class LicensesBaseError(OsparcErrorMixin, Exception):
...


class NotEnoughAvailableSeatsError(LicensesBaseError):
msg_template = "Not enough available seats. Current available seats {available_num_of_seats} for license item {license_item_id}"


class CanNotCheckoutNotEnoughAvailableSeatsError(LicensesBaseError):
msg_template = "Can not checkout license item {licensed_item_id} with num of seats {num_of_seats}. Currently available seats {available_num_of_seats}"


class CanNotCheckoutServiceIsNotRunningError(LicensesBaseError):
msg_template = "Can not checkout license item {licensed_item_id} as dynamic service is not running. Current service {service_run}"


class LicensedItemCheckoutNotFoundError(LicensesBaseError):
msg_template = "Licensed item checkout {licensed_item_checkout_id} not found."


LICENSES_ERRORS = (
NotEnoughAvailableSeatsError,
CanNotCheckoutNotEnoughAvailableSeatsError,
CanNotCheckoutServiceIsNotRunningError,
LicensedItemCheckoutNotFoundError,
)
Loading
Loading