From 542c8a6d5f4a3ba34a23d9dfe12fe7768d6460b7 Mon Sep 17 00:00:00 2001 From: Pedro Crespo-Valero <32402063+pcrespov@users.noreply.github.com> Date: Sat, 16 Mar 2024 19:11:46 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=20Adds=20countries=20on=20stati?= =?UTF-8?q?c=20entrypoint=20(#5506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/specs/web-server/_statics.py | 35 +++ api/specs/web-server/openapi.py | 3 +- services/web/server/VERSION | 2 +- services/web/server/setup.cfg | 2 +- .../simcore_service_webserver/_constants.py | 2 +- .../api/v0/openapi.yaml | 241 ++++++++++++------ .../statics/_events.py | 3 +- .../statics/_handlers.py | 9 +- .../statics/plugin.py | 4 +- .../statics/settings.py | 29 ++- .../unit/isolated/test_statics_settings.py | 4 +- .../unit/with_dbs/03/test__openapi_specs.py | 2 - 12 files changed, 236 insertions(+), 100 deletions(-) create mode 100644 api/specs/web-server/_statics.py diff --git a/api/specs/web-server/_statics.py b/api/specs/web-server/_statics.py new file mode 100644 index 00000000000..cf3b846f7d7 --- /dev/null +++ b/api/specs/web-server/_statics.py @@ -0,0 +1,35 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument +# pylint: disable=unused-variable +# pylint: disable=too-many-arguments + + +from typing import Any + +from fastapi import APIRouter +from fastapi.responses import HTMLResponse +from simcore_service_webserver._constants import INDEX_RESOURCE_NAME +from simcore_service_webserver.statics.settings import FrontEndInfoDict + +router = APIRouter( + tags=["statics"], +) + + +@router.get("/", response_class=HTMLResponse) +async def get_cached_frontend_index(): + ... + + +assert get_cached_frontend_index.__name__ == INDEX_RESOURCE_NAME + + +class StaticFrontEndDict(FrontEndInfoDict, total=False): + issues: Any + vendor: Any + manuals: Any + + +@router.get("/static-frontend-data.json", response_model=StaticFrontEndDict) +async def static_frontend_data(): + """Generic static info on the product's app""" diff --git a/api/specs/web-server/openapi.py b/api/specs/web-server/openapi.py index 903ff12b9e1..2c6da619860 100644 --- a/api/specs/web-server/openapi.py +++ b/api/specs/web-server/openapi.py @@ -30,17 +30,18 @@ "_nih_sparc_redirections", "_nih_sparc", "_products", + "_project_nodes_pricing_unit", "_projects_comments", "_projects_crud", "_projects_metadata", "_projects_nodes", - "_project_nodes_pricing_unit", "_projects_ports", "_projects_states", "_projects_tags", "_projects_wallet", "_publications", "_resource_usage", + "_statics", "_storage", "_tags", "_users", diff --git a/services/web/server/VERSION b/services/web/server/VERSION index 4ef2eb086f5..9b0025a7850 100644 --- a/services/web/server/VERSION +++ b/services/web/server/VERSION @@ -1 +1 @@ -0.39.0 +0.40.0 diff --git a/services/web/server/setup.cfg b/services/web/server/setup.cfg index ac722be3e21..3beccbaa365 100644 --- a/services/web/server/setup.cfg +++ b/services/web/server/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.39.0 +current_version = 0.40.0 commit = True message = services/webserver api version: {current_version} → {new_version} tag = False diff --git a/services/web/server/src/simcore_service_webserver/_constants.py b/services/web/server/src/simcore_service_webserver/_constants.py index c4c51365200..91b70f45307 100644 --- a/services/web/server/src/simcore_service_webserver/_constants.py +++ b/services/web/server/src/simcore_service_webserver/_constants.py @@ -18,7 +18,7 @@ RQ_PRODUCT_KEY: Final[str] = f"{__name__}.RQ_PRODUCT_KEY" # main index route name = front-end -INDEX_RESOURCE_NAME: Final[str] = "statics.index" +INDEX_RESOURCE_NAME: Final[str] = "get_cached_frontend_index" # Public config per product returned in /config APP_PUBLIC_CONFIG_PER_PRODUCT: Final[str] = f"{__name__}.APP_PUBLIC_CONFIG_PER_PRODUCT" diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 26a493330c0..69a048eaf01 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.2 info: title: simcore-service-webserver description: Main service with an interface (http-API & websockets) to the web front-end - version: 0.39.0 + version: 0.40.0 servers: - url: '' description: webserver @@ -1884,6 +1884,75 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_InvitationGenerated_' + /v0/projects/{project_id}/nodes/{node_id}/pricing-unit: + get: + tags: + - projects + summary: Get currently connected pricing unit to the project node. + operationId: get_project_node_pricing_unit + parameters: + - required: true + schema: + title: Project Id + type: string + format: uuid + name: project_id + in: path + - required: true + schema: + title: Node Id + type: string + format: uuid + name: node_id + in: path + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Union_PricingUnitGet__NoneType__' + /v0/projects/{project_id}/nodes/{node_id}/pricing-plan/{pricing_plan_id}/pricing-unit/{pricing_unit_id}: + put: + tags: + - projects + summary: Connect pricing unit to the project node (Project node can have only + one pricing unit) + operationId: connect_pricing_unit_to_project_node + parameters: + - required: true + schema: + title: Project Id + type: string + format: uuid + name: project_id + in: path + - required: true + schema: + title: Node Id + type: string + format: uuid + name: node_id + in: path + - required: true + schema: + title: Pricing Plan Id + exclusiveMinimum: true + type: integer + minimum: 0 + name: pricing_plan_id + in: path + - required: true + schema: + title: Pricing Unit Id + exclusiveMinimum: true + type: integer + minimum: 0 + name: pricing_unit_id + in: path + responses: + '204': + description: Successful Response /v0/projects/{project_uuid}/comments: get: tags: @@ -2682,75 +2751,6 @@ paths: $ref: '#/components/schemas/Envelope__ProjectNodePreview_' '404': description: Node has no preview - /v0/projects/{project_id}/nodes/{node_id}/pricing-unit: - get: - tags: - - projects - summary: Get currently connected pricing unit to the project node. - operationId: get_project_node_pricing_unit - parameters: - - required: true - schema: - title: Project Id - type: string - format: uuid - name: project_id - in: path - - required: true - schema: - title: Node Id - type: string - format: uuid - name: node_id - in: path - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Envelope_Union_PricingUnitGet__NoneType__' - /v0/projects/{project_id}/nodes/{node_id}/pricing-plan/{pricing_plan_id}/pricing-unit/{pricing_unit_id}: - put: - tags: - - projects - summary: Connect pricing unit to the project node (Project node can have only - one pricing unit) - operationId: connect_pricing_unit_to_project_node - parameters: - - required: true - schema: - title: Project Id - type: string - format: uuid - name: project_id - in: path - - required: true - schema: - title: Node Id - type: string - format: uuid - name: node_id - in: path - - required: true - schema: - title: Pricing Plan Id - exclusiveMinimum: true - type: integer - minimum: 0 - name: pricing_plan_id - in: path - - required: true - schema: - title: Pricing Unit Id - exclusiveMinimum: true - type: integer - minimum: 0 - name: pricing_unit_id - in: path - responses: - '204': - description: Successful Response /v0/projects/{project_id}/inputs: get: tags: @@ -2893,7 +2893,7 @@ paths: '403': description: ProjectInvalidRightsError '404': - description: UserDefaultWalletNotFoundError, ProjectNotFoundError + description: ProjectNotFoundError, UserDefaultWalletNotFoundError '409': description: ProjectTooManyProjectOpenedError '422': @@ -3205,6 +3205,33 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_PricingUnitGet_' + /: + get: + tags: + - statics + summary: Get Cached Frontend Index + operationId: get_cached_frontend_index + responses: + '200': + description: Successful Response + content: + text/html: + schema: + type: string + /static-frontend-data.json: + get: + tags: + - statics + summary: Static Frontend Data + description: Generic static info on the product's app + operationId: static_frontend_data + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/StaticFrontEndDict' /v0/storage/locations: get: tags: @@ -5323,6 +5350,19 @@ components: title: Cluster Id minimum: 0 type: integer + CountryInfoDict: + title: CountryInfoDict + required: + - name + - alpha2 + type: object + properties: + name: + title: Name + type: string + alpha2: + title: Alpha2 + type: string CreateWalletBodyParams: title: CreateWalletBodyParams required: @@ -6625,7 +6665,7 @@ components: exclusiveMaximum: true minimum: 0 type: integer - maximum: 200 + maximum: 500 GetCreditPrice: title: GetCreditPrice required: @@ -9600,6 +9640,29 @@ components: f_lineno: title: F Lineno type: string + StaticFrontEndDict: + title: StaticFrontEndDict + required: + - third_party_references + - countries + type: object + properties: + third_party_references: + title: Third Party References + type: array + items: + $ref: '#/components/schemas/ThirdPartyInfoDict' + countries: + title: Countries + type: array + items: + $ref: '#/components/schemas/CountryInfoDict' + issues: + title: Issues + vendor: + title: Vendor + manuals: + title: Manuals Stats: title: Stats required: @@ -9941,6 +10004,27 @@ components: description: minimum Height of the textarea minimum: 0 additionalProperties: false + ThirdPartyInfoDict: + title: ThirdPartyInfoDict + required: + - name + - version + - url + - thumbnail + type: object + properties: + name: + title: Name + type: string + version: + title: Version + type: string + url: + title: Url + type: string + thumbnail: + title: Thumbnail + type: string ThirdPartyToken: title: ThirdPartyToken required: @@ -10163,20 +10247,13 @@ components: UserStatus: title: UserStatus enum: - - PENDING + - CONFIRMATION_PENDING - ACTIVE - EXPIRED - BANNED - DELETED - description: 'pending: user registered but not confirmed - - active: user is confirmed and can use the platform - - expired: user is not authorized because it expired after a trial period - - banned: user is not authorized - - deleted: this account is marked for deletion' + type: string + description: An enumeration. UsersGroup: title: UsersGroup required: diff --git a/services/web/server/src/simcore_service_webserver/statics/_events.py b/services/web/server/src/simcore_service_webserver/statics/_events.py index 1a400228b6b..2df52102483 100644 --- a/services/web/server/src/simcore_service_webserver/statics/_events.py +++ b/services/web/server/src/simcore_service_webserver/statics/_events.py @@ -89,7 +89,7 @@ async def create_cached_indexes(app: web.Application) -> None: app[APP_FRONTEND_CACHED_INDEXES_KEY] = cached_indexes -async def create_statics_json(app: web.Application) -> None: +async def create_and_cache_statics_json(app: web.Application) -> None: # NOTE: in devel model, the folder might be under construction # (qx-compile takes time), therefore we create statics.json # on_startup instead of upon setup @@ -108,7 +108,6 @@ async def create_statics_json(app: web.Application) -> None: assert products # nosec app[APP_FRONTEND_CACHED_STATICS_JSON_KEY] = {} - for product in products.values(): data = deepcopy(common) diff --git a/services/web/server/src/simcore_service_webserver/statics/_handlers.py b/services/web/server/src/simcore_service_webserver/statics/_handlers.py index 0d1196052da..e3ac6c1b5b4 100644 --- a/services/web/server/src/simcore_service_webserver/statics/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/statics/_handlers.py @@ -1,6 +1,7 @@ import logging from aiohttp import web +from servicelib.mimetype_constants import MIMETYPE_TEXT_HTML from ..products.api import get_product_name from ._constants import ( @@ -33,14 +34,14 @@ async def get_cached_frontend_index(request: web.Request): raise web.HTTPNotFound(reason=f"No index.html found for {product_name}") return web.Response( - body=cached_index_per_product[product_name], content_type="text/html" + body=cached_index_per_product[product_name], content_type=MIMETYPE_TEXT_HTML ) async def get_statics_json(request: web.Request): product_name = get_product_name(request) - statics_json = request.app[APP_FRONTEND_CACHED_STATICS_JSON_KEY].get( - product_name, {} + return web.Response( + body=request.app[APP_FRONTEND_CACHED_STATICS_JSON_KEY].get(product_name, None), + content_type="application/json", ) - return web.Response(body=statics_json, content_type="application/json") diff --git a/services/web/server/src/simcore_service_webserver/statics/plugin.py b/services/web/server/src/simcore_service_webserver/statics/plugin.py index 1a8c4bc2f05..f354ef305a5 100644 --- a/services/web/server/src/simcore_service_webserver/statics/plugin.py +++ b/services/web/server/src/simcore_service_webserver/statics/plugin.py @@ -13,7 +13,7 @@ from .._constants import INDEX_RESOURCE_NAME from ..products.plugin import setup_products -from ._events import create_cached_indexes, create_statics_json +from ._events import create_and_cache_statics_json, create_cached_indexes from ._handlers import get_cached_frontend_index, get_statics_json from .settings import StaticWebserverModuleSettings, get_plugin_settings @@ -41,7 +41,7 @@ def setup_statics(app: web.Application) -> None: ) # compute statics.json content - app.on_startup.append(create_statics_json) + app.on_startup.append(create_and_cache_statics_json) # fetch all index.html for various frontends app.on_startup.append(create_cached_indexes) diff --git a/services/web/server/src/simcore_service_webserver/statics/settings.py b/services/web/server/src/simcore_service_webserver/statics/settings.py index c8f6565bb2f..354f6b12c6f 100644 --- a/services/web/server/src/simcore_service_webserver/statics/settings.py +++ b/services/web/server/src/simcore_service_webserver/statics/settings.py @@ -4,6 +4,7 @@ """ from typing import Any, TypedDict +import pycountry from aiohttp import web from models_library.utils.change_case import snake_to_camel from pydantic import AnyHttpUrl, Field, parse_obj_as @@ -19,7 +20,7 @@ class ThirdPartyInfoDict(TypedDict): thumbnail: str -THIRD_PARTY_REFERENCES = [ +_THIRD_PARTY_REFERENCES = [ ThirdPartyInfoDict( name="adminer", version="4.8.1", @@ -71,6 +72,19 @@ class ThirdPartyInfoDict(TypedDict): ] +# NOTE: syncs info on countries with UI + + +class CountryInfoDict(TypedDict): + name: str + alpha2: str + + +class FrontEndInfoDict(TypedDict, total=True): + third_party_references: list[ThirdPartyInfoDict] + countries: list[CountryInfoDict] + + class FrontEndAppSettings(BaseCustomSettings): """ Any settings to be transmitted to the front-end via statics goes here @@ -83,7 +97,18 @@ def to_statics(self) -> dict[str, Any]: exclude_none=True, by_alias=True, ) - data["third_party_references"] = THIRD_PARTY_REFERENCES + data.update( + FrontEndInfoDict( + third_party_references=_THIRD_PARTY_REFERENCES, + countries=[ + CountryInfoDict( + name=c.name, + alpha2=c.alpha_2, + ) + for c in pycountry.countries + ], + ) + ) return { snake_to_camel(k.replace("WEBSERVER_", "").lower()): v diff --git a/services/web/server/tests/unit/isolated/test_statics_settings.py b/services/web/server/tests/unit/isolated/test_statics_settings.py index 0a80e0fcf8f..376a8330eb9 100644 --- a/services/web/server/tests/unit/isolated/test_statics_settings.py +++ b/services/web/server/tests/unit/isolated/test_statics_settings.py @@ -6,7 +6,7 @@ from pydantic import AnyHttpUrl, BaseModel, parse_obj_as from simcore_service_webserver.statics.settings import ( - THIRD_PARTY_REFERENCES, + _THIRD_PARTY_REFERENCES, FrontEndAppSettings, StaticWebserverModuleSettings, ) @@ -23,7 +23,7 @@ class OsparcDependency(BaseModel): def test_valid_osparc_dependencies(): - deps = parse_obj_as(list[OsparcDependency], THIRD_PARTY_REFERENCES) + deps = parse_obj_as(list[OsparcDependency], _THIRD_PARTY_REFERENCES) assert deps diff --git a/services/web/server/tests/unit/with_dbs/03/test__openapi_specs.py b/services/web/server/tests/unit/with_dbs/03/test__openapi_specs.py index 1eb60a745f7..2b4ad41b803 100644 --- a/services/web/server/tests/unit/with_dbs/03/test__openapi_specs.py +++ b/services/web/server/tests/unit/with_dbs/03/test__openapi_specs.py @@ -33,8 +33,6 @@ def app_environment( return mock_env_devel_environment | setenvs_from_dict( monkeypatch, { - # disable index and statics routings - "WEBSERVER_STATICWEB": "null", # disable bundle configs "WEBSERVER_DB_LISTENER": "0", "WEBSERVER_GARBAGE_COLLECTOR": "null",