From 5acf405e2de6a42e7150dcf358f184b1e51f006d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 14:36:11 +0100 Subject: [PATCH 001/200] retrieve connection state --- .../schemas/project-v0.0.1-converted.yaml | 7 + api/specs/common/schemas/project-v0.0.1.json | 9 + .../api/v0/schemas/project-v0.0.1.json | 9 + .../api/v0/openapi.yaml | 213 +-- .../api/v0/schemas/project-v0.0.1.json | 9 + .../api/v0/openapi.yaml | 1424 +++++++++-------- .../api/v0/schemas/project-v0.0.1.json | 9 + 7 files changed, 900 insertions(+), 780 deletions(-) diff --git a/api/specs/common/schemas/project-v0.0.1-converted.yaml b/api/specs/common/schemas/project-v0.0.1-converted.yaml index 5ee82d88c3b..e9264f0a57c 100644 --- a/api/specs/common/schemas/project-v0.0.1-converted.yaml +++ b/api/specs/common/schemas/project-v0.0.1-converted.yaml @@ -285,6 +285,13 @@ properties: example: - '15' deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index 713bae081d8..75fe94ec0d4 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -396,6 +396,15 @@ }, "deprecated": true }, + "connection_state": { + "title": "Status", + "description": "the node's connection state", + "default": "CHANGED", + "enum": [ + "CHANGED", + "CURRENT" + ] + }, "state": { "title": "RunningState", "description": "the node's running state", diff --git a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json index 713bae081d8..75fe94ec0d4 100644 --- a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json +++ b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json @@ -396,6 +396,15 @@ }, "deprecated": true }, + "connection_state": { + "title": "Status", + "description": "the node's connection state", + "default": "CHANGED", + "enum": [ + "CHANGED", + "CURRENT" + ] + }, "state": { "title": "RunningState", "description": "the node's running state", diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index 05686a6e1b7..274f905145b 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -8,17 +8,17 @@ info: email: support@simcore.io license: name: MIT - url: 'https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE' + url: "https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE" servers: - description: API server url: /v0 - description: Development server - url: 'http://{host}:{port}/{basePath}' + url: "http://{host}:{port}/{basePath}" variables: host: default: localhost port: - default: '8080' + default: "8080" basePath: enum: - v0 @@ -30,15 +30,15 @@ paths: description: Some general information on the API and state of the service behind operationId: health_check responses: - '200': + "200": description: Service information content: application/json: schema: - $ref: '#/components/schemas/HealthCheckEnveloped' + $ref: "#/components/schemas/HealthCheckEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/check/{action}': + $ref: "#/components/responses/DefaultErrorResponse" + "/check/{action}": post: summary: Test checkpoint to ask server to fail or echo back the transmitted data parameters: @@ -59,16 +59,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Fake' + $ref: "#/components/schemas/Fake" responses: - '200': + "200": description: Echoes response based on action content: application/json: schema: - $ref: '#/components/schemas/FakeEnveloped' + $ref: "#/components/schemas/FakeEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' + $ref: "#/components/responses/DefaultErrorResponse" /locations: get: summary: Lists available storage locations @@ -80,15 +80,15 @@ paths: schema: type: string responses: - '200': + "200": description: List of available storage locations content: application/json: schema: - $ref: '#/components/schemas/FileLocationArrayEnveloped' + $ref: "#/components/schemas/FileLocationArrayEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/locations/{location_id}/datasets': + $ref: "#/components/responses/DefaultErrorResponse" + "/locations/{location_id}/datasets": get: summary: Lists all dataset's metadata operationId: get_datasets_metadata @@ -104,15 +104,15 @@ paths: schema: type: string responses: - '200': + "200": description: list of dataset meta-datas content: application/json: schema: - $ref: '#/components/schemas/DatasetMetaDataArrayEnveloped' + $ref: "#/components/schemas/DatasetMetaDataArrayEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/locations/{location_id}/files/metadata': + $ref: "#/components/responses/DefaultErrorResponse" + "/locations/{location_id}/files/metadata": get: summary: Lists all file's metadata operationId: get_files_metadata @@ -133,15 +133,15 @@ paths: schema: type: string responses: - '200': + "200": description: list of file meta-datas content: application/json: schema: - $ref: '#/components/schemas/FileMetaDataArrayEnveloped' + $ref: "#/components/schemas/FileMetaDataArrayEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/locations/{location_id}/datasets/{dataset_id}/metadata': + $ref: "#/components/responses/DefaultErrorResponse" + "/locations/{location_id}/datasets/{dataset_id}/metadata": get: summary: Get dataset metadata operationId: get_files_metadata_dataset @@ -162,15 +162,15 @@ paths: schema: type: string responses: - '200': + "200": description: list of file meta-datas content: application/json: schema: - $ref: '#/components/schemas/FileMetaDataArrayEnveloped' + $ref: "#/components/schemas/FileMetaDataArrayEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/locations/{location_id}/files/{fileId}/metadata': + $ref: "#/components/responses/DefaultErrorResponse" + "/locations/{location_id}/files/{fileId}/metadata": get: summary: Get file metadata operationId: get_file_metadata @@ -191,14 +191,14 @@ paths: schema: type: string responses: - '200': + "200": description: Returns file metadata content: application/json: schema: - $ref: '#/components/schemas/FileMetaDataEnveloped' + $ref: "#/components/schemas/FileMetaDataEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' + $ref: "#/components/responses/DefaultErrorResponse" patch: summary: Update file metadata operationId: update_file_meta_data @@ -217,17 +217,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/FileMetaData' + $ref: "#/components/schemas/FileMetaData" responses: - '200': + "200": description: Returns file metadata content: application/json: schema: - $ref: '#/components/schemas/FileMetaDataEnveloped' + $ref: "#/components/schemas/FileMetaDataEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/locations/{location_id}/files/{fileId}': + $ref: "#/components/responses/DefaultErrorResponse" + "/locations/{location_id}/files/{fileId}": get: summary: Gets download link for file at location operationId: download_file @@ -248,14 +248,14 @@ paths: schema: type: string responses: - '200': + "200": description: Returns presigned link content: application/json: schema: - $ref: '#/components/schemas/PresignedLinkEnveloped' + $ref: "#/components/schemas/PresignedLinkEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' + $ref: "#/components/responses/DefaultErrorResponse" put: summary: Returns upload link or performs copy operation to datcore operationId: upload_file @@ -286,14 +286,14 @@ paths: schema: type: string responses: - '200': + "200": description: Returns presigned link content: application/json: schema: - $ref: '#/components/schemas/PresignedLinkEnveloped' + $ref: "#/components/schemas/PresignedLinkEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' + $ref: "#/components/responses/DefaultErrorResponse" delete: summary: Deletes file operationId: delete_file @@ -314,11 +314,11 @@ paths: schema: type: string responses: - '204': - description: 'everything is OK, but there is no content to return' + "204": + description: "everything is OK, but there is no content to return" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/simcore-s3/files/metadata:search': + $ref: "#/components/responses/DefaultErrorResponse" + "/simcore-s3/files/metadata:search": post: summary: Returns metadata for all files matching a pattern operationId: search_files_starting_with @@ -333,16 +333,16 @@ paths: in: query schema: type: string - default: '' + default: "" responses: - '200': + "200": description: list of matching files found content: application/json: schema: - $ref: '#/components/schemas/FileMetaDataArrayEnveloped' + $ref: "#/components/schemas/FileMetaDataArrayEnveloped" default: - $ref: '#/components/responses/DefaultErrorResponse' + $ref: "#/components/responses/DefaultErrorResponse" /simcore-s3/folders: post: summary: Deep copies of all data from source to destination project in s3 @@ -360,24 +360,24 @@ paths: type: object properties: source: - $ref: '#/components/schemas/Project' + $ref: "#/components/schemas/Project" destination: - $ref: '#/components/schemas/Project' + $ref: "#/components/schemas/Project" nodes_map: type: object description: maps source and destination node uuids additionalProperties: type: string responses: - '201': + "201": description: Data from destination project copied and returns project content: application/json: schema: - $ref: '#/components/schemas/Project' + $ref: "#/components/schemas/Project" default: - $ref: '#/components/responses/DefaultErrorResponse' - '/simcore-s3/folders/{folder_id}': + $ref: "#/components/responses/DefaultErrorResponse" + "/simcore-s3/folders/{folder_id}": delete: summary: Deletes all objects within a node_id or within a project_id if node_id is omitted operationId: delete_folders_of_project @@ -398,7 +398,7 @@ paths: schema: type: string responses: - '204': + "204": description: folder has been successfully deleted components: schemas: @@ -409,7 +409,7 @@ components: - error properties: data: - $ref: '#/components/schemas/HealthCheck' + $ref: "#/components/schemas/HealthCheck" error: nullable: true default: null @@ -439,7 +439,7 @@ components: nullable: true default: null error: - $ref: '#/components/schemas/Error' + $ref: "#/components/schemas/Error" Error: type: object properties: @@ -447,12 +447,12 @@ components: description: log messages type: array items: - $ref: '#/components/schemas/LogMessage' + $ref: "#/components/schemas/LogMessage" errors: description: errors metadata type: array items: - $ref: '#/components/schemas/ErrorItem' + $ref: "#/components/schemas/ErrorItem" status: description: HTTP error code type: integer @@ -497,7 +497,7 @@ components: - error properties: data: - $ref: '#/components/schemas/LogMessage' + $ref: "#/components/schemas/LogMessage" error: nullable: true default: null @@ -514,7 +514,7 @@ components: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -522,7 +522,7 @@ components: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger FakeEnveloped: @@ -532,7 +532,7 @@ components: - error properties: data: - $ref: '#/components/schemas/Fake' + $ref: "#/components/schemas/Fake" error: nullable: true default: null @@ -563,14 +563,14 @@ components: - error properties: data: - $ref: '#/components/schemas/FileLocationArray' + $ref: "#/components/schemas/FileLocationArray" error: nullable: true default: null FileLocationArray: type: array items: - $ref: '#/components/schemas/FileLocation' + $ref: "#/components/schemas/FileLocation" FileLocationEnveloped: type: object required: @@ -578,7 +578,7 @@ components: - error properties: data: - $ref: '#/components/schemas/FileLocation' + $ref: "#/components/schemas/FileLocation" error: nullable: true default: null @@ -599,7 +599,7 @@ components: - error properties: data: - $ref: '#/components/schemas/DatasetMetaDataArray' + $ref: "#/components/schemas/DatasetMetaDataArray" error: nullable: true default: null @@ -610,7 +610,7 @@ components: - error properties: data: - $ref: '#/components/schemas/DatasetMetaData' + $ref: "#/components/schemas/DatasetMetaData" error: nullable: true default: null @@ -622,12 +622,12 @@ components: display_name: type: string example: - dataset_uuid: 'N:id-aaaa' + dataset_uuid: "N:id-aaaa" display_name: simcore-testing DatasetMetaDataArray: type: array items: - $ref: '#/components/schemas/DatasetMetaData' + $ref: "#/components/schemas/DatasetMetaData" FileMetaDataEnveloped: type: object required: @@ -635,7 +635,7 @@ components: - error properties: data: - $ref: '#/components/schemas/FileMetaData' + $ref: "#/components/schemas/FileMetaData" error: nullable: true default: null @@ -689,14 +689,14 @@ components: - error properties: data: - $ref: '#/components/schemas/FileMetaDataArray' + $ref: "#/components/schemas/FileMetaDataArray" error: nullable: true default: null FileMetaDataArray: type: array items: - $ref: '#/components/schemas/FileMetaData' + $ref: "#/components/schemas/FileMetaData" PresignedLinkEnveloped: type: object required: @@ -704,7 +704,7 @@ components: - error properties: data: - $ref: '#/components/schemas/PresignedLink' + $ref: "#/components/schemas/PresignedLink" error: nullable: true default: null @@ -777,24 +777,24 @@ components: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -833,26 +833,26 @@ components: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -864,7 +864,7 @@ components: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -899,7 +899,7 @@ components: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -921,14 +921,14 @@ components: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -973,7 +973,7 @@ components: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -985,19 +985,26 @@ components: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -1021,8 +1028,8 @@ components: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -1032,24 +1039,24 @@ components: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -1073,7 +1080,7 @@ components: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -1167,4 +1174,4 @@ components: content: application/json: schema: - $ref: '#/components/schemas/ErrorEnveloped' + $ref: "#/components/schemas/ErrorEnveloped" diff --git a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json index 713bae081d8..75fe94ec0d4 100644 --- a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json +++ b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json @@ -396,6 +396,15 @@ }, "deprecated": true }, + "connection_state": { + "title": "Status", + "description": "the node's connection state", + "default": "CHANGED", + "enum": [ + "CHANGED", + "CURRENT" + ] + }, "state": { "title": "RunningState", "description": "the node's running state", 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 433f4c28991..6136d019a4f 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 @@ -8,17 +8,17 @@ info: email: support@simcore.io license: name: MIT - url: 'https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE' + url: "https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE" servers: - description: API server url: /v0 - description: Development server - url: 'http://{host}:{port}/{basePath}' + url: "http://{host}:{port}/{basePath}" variables: host: default: localhost port: - default: '8001' + default: "8001" basePath: enum: - v0 @@ -56,7 +56,7 @@ paths: summary: run check operationId: check_running responses: - '200': + "200": description: Service information content: application/json: @@ -116,7 +116,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -124,7 +124,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -174,7 +174,7 @@ paths: summary: health check operationId: check_health responses: - '200': + "200": description: Service information content: application/json: @@ -234,7 +234,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -242,7 +242,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -285,7 +285,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/check/{action}': + "/check/{action}": post: tags: - maintenance @@ -330,7 +330,7 @@ paths: key1: value1 key2: value2 responses: - '200': + "200": description: Echoes response based on action content: application/json: @@ -403,7 +403,7 @@ paths: tags: - configuration responses: - '200': + "200": description: configuration details content: application/json: @@ -454,7 +454,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -462,7 +462,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -536,7 +536,7 @@ paths: invitation: 33c451d4-17b7-4e65-9880-694559b8ffc2 required: true responses: - '200': + "200": description: User has been succesfully registered. content: application/json: @@ -558,7 +558,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -566,7 +566,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: @@ -604,7 +604,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -612,7 +612,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -675,7 +675,7 @@ paths: email: foo@mymail.com password: my secret responses: - '200': + "200": description: Succesfully logged in content: application/json: @@ -697,7 +697,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -705,7 +705,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: @@ -743,7 +743,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -751,7 +751,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -809,7 +809,7 @@ paths: type: string example: 5ac57685-c40f-448f-8711-70be1936fd63 responses: - '200': + "200": description: Succesfully logged out content: application/json: @@ -831,7 +831,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -839,7 +839,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: @@ -877,7 +877,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -885,7 +885,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -947,7 +947,7 @@ paths: example: email: foo@mymail.com responses: - '200': + "200": description: confirmation email sent to user content: application/json: @@ -969,7 +969,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -977,13 +977,13 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: nullable: true default: null - '503': + "503": description: failed to send confirmation email content: application/json: @@ -1015,7 +1015,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1023,7 +1023,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1066,7 +1066,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/auth/reset-password/{code}': + "/auth/reset-password/{code}": post: tags: - authentication @@ -1095,7 +1095,7 @@ paths: password: my secret confirm: my secret responses: - '200': + "200": description: password was successfully changed content: application/json: @@ -1117,7 +1117,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1125,13 +1125,13 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: nullable: true default: null - '401': + "401": description: unauthorized reset due to invalid token code content: application/json: @@ -1163,7 +1163,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1171,7 +1171,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1246,7 +1246,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1254,7 +1254,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1316,7 +1316,7 @@ paths: example: email: foo@mymail.com responses: - '200': + "200": description: confirmation sent to new email to complete operation content: application/json: @@ -1338,7 +1338,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1346,13 +1346,13 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: nullable: true default: null - '401': + "401": description: unauthorized user. Login required content: application/json: @@ -1384,7 +1384,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1392,7 +1392,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1435,7 +1435,7 @@ paths: message: Password is not secure field: pasword status: 400 - '503': + "503": description: unable to send confirmation email content: application/json: @@ -1467,7 +1467,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1475,7 +1475,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1550,7 +1550,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1558,7 +1558,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1628,7 +1628,7 @@ paths: new: my new secret confirm: my new secret responses: - '200': + "200": description: password was successfully changed content: application/json: @@ -1650,7 +1650,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1658,13 +1658,13 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger error: nullable: true default: null - '401': + "401": description: unauthorized user. Login required content: application/json: @@ -1696,7 +1696,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1704,7 +1704,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1747,7 +1747,7 @@ paths: message: Password is not secure field: pasword status: 400 - '409': + "409": description: mismatch between new and confirmation passwords content: application/json: @@ -1779,7 +1779,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1787,7 +1787,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1830,7 +1830,7 @@ paths: message: Password is not secure field: pasword status: 400 - '422': + "422": description: current password is invalid content: application/json: @@ -1862,7 +1862,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1870,7 +1870,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1945,7 +1945,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -1953,7 +1953,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -1996,7 +1996,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/auth/confirmation/{code}': + "/auth/confirmation/{code}": get: summary: email link sent to user to confirm an action tags: @@ -2018,7 +2018,7 @@ paths: - authentication operationId: list_api_keys responses: - '200': + "200": description: returns the display names of API keys content: application/json: @@ -2026,9 +2026,9 @@ paths: type: array items: type: string - '401': + "401": description: requires login to list keys - '403': + "403": description: not enough permissions to list keys post: summary: creates API keys to access public API @@ -2045,7 +2045,7 @@ paths: display_name: type: string responses: - '200': + "200": description: Authorization granted returning API key content: application/json: @@ -2058,11 +2058,11 @@ paths: type: string api_secret: type: string - '400': + "400": description: key name requested is invalid - '401': + "401": description: requires login to create a key - '403': + "403": description: not enough permissions to create a key delete: summary: deletes API key by name @@ -2079,11 +2079,11 @@ paths: display_name: type: string responses: - '204': + "204": description: api key successfully deleted - '401': + "401": description: requires login to delete a key - '403': + "403": description: not enough permissions to delete a key /me: get: @@ -2091,7 +2091,7 @@ paths: tags: - user responses: - '200': + "200": description: current user profile content: application/json: @@ -2167,18 +2167,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" organizations: type: array items: @@ -2227,18 +2227,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" all: type: object properties: @@ -2285,18 +2285,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" gravatar_id: type: string example: @@ -2338,7 +2338,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -2346,7 +2346,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -2411,7 +2411,7 @@ paths: first_name: Pedro last_name: Crespo responses: - '204': + "204": description: updated profile default: description: Default http error response body @@ -2445,7 +2445,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -2453,7 +2453,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -2503,7 +2503,7 @@ paths: tags: - user responses: - '200': + "200": description: list of tokens content: application/json: @@ -2569,7 +2569,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -2577,7 +2577,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -2657,7 +2657,7 @@ paths: nullable: true default: null responses: - '201': + "201": description: token created content: application/json: @@ -2721,7 +2721,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -2729,7 +2729,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -2772,7 +2772,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/me/tokens/{service}': + "/me/tokens/{service}": parameters: - name: service in: path @@ -2785,7 +2785,7 @@ paths: tags: - user responses: - '200': + "200": description: got detailed token content: application/json: @@ -2823,7 +2823,7 @@ paths: tags: - user responses: - '204': + "204": description: token has been successfully updated delete: summary: Delete token @@ -2831,7 +2831,7 @@ paths: tags: - user responses: - '204': + "204": description: token has been successfully deleted /groups: get: @@ -2840,7 +2840,7 @@ paths: tags: - group responses: - '200': + "200": description: list of the groups I belonged to content: application/json: @@ -2898,18 +2898,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" organizations: type: array items: @@ -2958,18 +2958,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" all: type: object properties: @@ -3016,18 +3016,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" error: nullable: true default: null @@ -3063,7 +3063,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -3071,7 +3071,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -3170,20 +3170,20 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" responses: - '201': + "201": description: group created content: application/json: @@ -3238,18 +3238,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" error: nullable: true default: null @@ -3285,7 +3285,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -3293,7 +3293,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -3336,7 +3336,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/groups/{gid}': + "/groups/{gid}": parameters: - name: gid in: path @@ -3349,7 +3349,7 @@ paths: summary: Gets one group details operationId: get_group responses: - '200': + "200": description: got group content: application/json: @@ -3404,18 +3404,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" error: nullable: true default: null @@ -3451,7 +3451,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -3459,7 +3459,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -3558,20 +3558,20 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" responses: - '200': + "200": description: the modified group content: application/json: @@ -3626,18 +3626,18 @@ paths: - description - accessRights example: - - gid: '27' + - gid: "27" label: A user description: A very special user - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '1' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "1" label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' - - gid: '0' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + - gid: "0" label: All description: Open to all users - thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" error: nullable: true default: null @@ -3673,7 +3673,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -3681,7 +3681,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -3730,7 +3730,7 @@ paths: summary: Deletes one group operationId: delete_group responses: - '204': + "204": description: group has been successfully deleted default: description: Default http error response body @@ -3764,7 +3764,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -3772,7 +3772,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -3815,7 +3815,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/groups/{gid}/users': + "/groups/{gid}/users": parameters: - name: gid in: path @@ -3828,7 +3828,7 @@ paths: summary: Gets list of users in group operationId: get_group_users responses: - '200': + "200": description: got list of users and their respective rights content: application/json: @@ -3868,8 +3868,8 @@ paths: last_name: Smith login: mr.smith@matrix.com gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 - id: '1' - gid: '3' + id: "1" + gid: "3" - description: defines acesss rights for the user type: object properties: @@ -3928,7 +3928,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -3936,7 +3936,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4007,7 +4007,7 @@ paths: format: email description: the user email responses: - '204': + "204": description: user successfully added default: description: Default http error response body @@ -4041,7 +4041,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4049,7 +4049,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4092,7 +4092,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/groups/{gid}/users/{uid}': + "/groups/{gid}/users/{uid}": parameters: - name: gid in: path @@ -4110,7 +4110,7 @@ paths: summary: Gets specific user in group operationId: get_group_user responses: - '200': + "200": description: got user content: application/json: @@ -4148,8 +4148,8 @@ paths: last_name: Smith login: mr.smith@matrix.com gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 - id: '1' - gid: '3' + id: "1" + gid: "3" - description: defines acesss rights for the user type: object properties: @@ -4208,7 +4208,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4216,7 +4216,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4299,7 +4299,7 @@ paths: required: - accessRights responses: - '200': + "200": description: modified user content: application/json: @@ -4337,8 +4337,8 @@ paths: last_name: Smith login: mr.smith@matrix.com gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 - id: '1' - gid: '3' + id: "1" + gid: "3" - description: defines acesss rights for the user type: object properties: @@ -4397,7 +4397,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4405,7 +4405,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4454,7 +4454,7 @@ paths: summary: Delete specific user in group operationId: delete_group_user responses: - '204': + "204": description: successfully removed user default: description: Default http error response body @@ -4488,7 +4488,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4496,7 +4496,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4539,7 +4539,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/groups/{gid}/classifiers': + "/groups/{gid}/classifiers": get: parameters: - name: gid @@ -4561,7 +4561,7 @@ paths: summary: Gets classifiers bundle for this group operationId: get_group_classifiers responses: - '200': + "200": description: got a bundle with all information about classifiers default: description: Default http error response body @@ -4595,7 +4595,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4603,7 +4603,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4646,7 +4646,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/groups/sparc/classifiers/scicrunch-resources/{rrid}': + "/groups/sparc/classifiers/scicrunch-resources/{rrid}": parameters: - name: rrid in: path @@ -4656,14 +4656,14 @@ paths: get: tags: - group - summary: 'Returns information on a valid RRID (https://www.force11.org/group/resource-identification-initiative)' + summary: "Returns information on a valid RRID (https://www.force11.org/group/resource-identification-initiative)" operationId: get_scicrunch_resource responses: - '200': + "200": description: Got information of a valid RRID - '400': + "400": description: Invalid RRID - '503': + "503": description: scircrunch.org service is not reachable default: description: Default http error response body @@ -4697,7 +4697,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4705,7 +4705,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4754,11 +4754,11 @@ paths: summary: Adds new RRID to classifiers operationId: add_scicrunch_resource responses: - '200': + "200": description: Got information of a valid RRID - '400': + "400": description: Invalid RRID - '503': + "503": description: scircrunch.org service is not reachable default: description: Default http error response body @@ -4792,7 +4792,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4800,7 +4800,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4843,7 +4843,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/groups/sparc/classifiers/scicrunch-resources:search': + "/groups/sparc/classifiers/scicrunch-resources:search": get: parameters: - name: guess_name @@ -4856,9 +4856,9 @@ paths: summary: Returns a list of related resource provided a search name operationId: search_scicrunch_resources responses: - '200': + "200": description: Got information of a valid RRID - '503': + "503": description: scircrunch.org service is not reachable default: description: Default http error response body @@ -4892,7 +4892,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -4900,7 +4900,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -4950,7 +4950,7 @@ paths: - storage operationId: get_storage_locations responses: - '200': + "200": description: List of availabe storage locations content: application/json: @@ -4998,7 +4998,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -5006,7 +5006,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -5049,7 +5049,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/storage/locations/{location_id}/files/metadata': + "/storage/locations/{location_id}/files/metadata": get: summary: Get list of file meta data tags: @@ -5062,7 +5062,7 @@ paths: schema: type: string responses: - '200': + "200": description: list of file meta-datas content: application/json: @@ -5111,24 +5111,24 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: '0' + location_id: "0" location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: '105' + project_id: "105" project_name: futurology - node_id: '10000' + node_id: "10000" node_name: alpha file_name: example.txt - user_id: '12' + user_id: "12" user_name: dennis - file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' + file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: '2019-06-19T12:29:03.308611Z' - last_modified: '2019-06-19T12:29:03.78852Z' + created_at: "2019-06-19T12:29:03.308611Z" + last_modified: "2019-06-19T12:29:03.78852Z" file_size: 73 - parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' + parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" default: description: Default http error response body content: @@ -5161,7 +5161,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -5169,7 +5169,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -5212,7 +5212,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/storage/locations/{location_id}/files/{fileId}': + "/storage/locations/{location_id}/files/{fileId}": get: summary: Returns download link for requested file tags: @@ -5230,7 +5230,7 @@ paths: schema: type: string responses: - '200': + "200": description: Returns presigned link content: application/json: @@ -5268,7 +5268,7 @@ paths: schema: type: string responses: - '200': + "200": description: Returns presigned link content: application/json: @@ -5296,9 +5296,9 @@ paths: schema: type: string responses: - '204': - description: '' - '/storage/locations/{location_id}/files/{fileId}/metadata': + "204": + description: "" + "/storage/locations/{location_id}/files/{fileId}/metadata": get: summary: Get File Metadata tags: @@ -5316,7 +5316,7 @@ paths: schema: type: string responses: - '200': + "200": description: Returns file metadata content: application/json: @@ -5363,24 +5363,24 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: '0' + location_id: "0" location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: '105' + project_id: "105" project_name: futurology - node_id: '10000' + node_id: "10000" node_name: alpha file_name: example.txt - user_id: '12' + user_id: "12" user_name: dennis - file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' + file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: '2019-06-19T12:29:03.308611Z' - last_modified: '2019-06-19T12:29:03.78852Z' + created_at: "2019-06-19T12:29:03.308611Z" + last_modified: "2019-06-19T12:29:03.78852Z" file_size: 73 - parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' + parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" patch: summary: Update File Metadata tags: @@ -5443,26 +5443,26 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: '0' + location_id: "0" location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: '105' + project_id: "105" project_name: futurology - node_id: '10000' + node_id: "10000" node_name: alpha file_name: example.txt - user_id: '12' + user_id: "12" user_name: dennis - file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' + file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: '2019-06-19T12:29:03.308611Z' - last_modified: '2019-06-19T12:29:03.78852Z' + created_at: "2019-06-19T12:29:03.308611Z" + last_modified: "2019-06-19T12:29:03.78852Z" file_size: 73 - parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' + parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" responses: - '200': + "200": description: Returns file metadata content: application/json: @@ -5509,25 +5509,25 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: '0' + location_id: "0" location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: '105' + project_id: "105" project_name: futurology - node_id: '10000' + node_id: "10000" node_name: alpha file_name: example.txt - user_id: '12' + user_id: "12" user_name: dennis - file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' + file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: '2019-06-19T12:29:03.308611Z' - last_modified: '2019-06-19T12:29:03.78852Z' + created_at: "2019-06-19T12:29:03.308611Z" + last_modified: "2019-06-19T12:29:03.78852Z" file_size: 73 - parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' - '/storage/locations/{location_id}/datasets/{dataset_id}/metadata': + parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" + "/storage/locations/{location_id}/datasets/{dataset_id}/metadata": get: summary: Get Files Metadata tags: @@ -5545,7 +5545,7 @@ paths: schema: type: string responses: - '200': + "200": description: list of file meta-datas content: application/json: @@ -5594,24 +5594,24 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: '0' + location_id: "0" location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: '105' + project_id: "105" project_name: futurology - node_id: '10000' + node_id: "10000" node_name: alpha file_name: example.txt - user_id: '12' + user_id: "12" user_name: dennis - file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' + file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: '2019-06-19T12:29:03.308611Z' - last_modified: '2019-06-19T12:29:03.78852Z' + created_at: "2019-06-19T12:29:03.308611Z" + last_modified: "2019-06-19T12:29:03.78852Z" file_size: 73 - parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' + parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" default: description: Default http error response body content: @@ -5644,7 +5644,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -5652,7 +5652,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -5695,7 +5695,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/storage/locations/{location_id}/datasets': + "/storage/locations/{location_id}/datasets": get: summary: Get datasets metadata tags: @@ -5708,7 +5708,7 @@ paths: schema: type: string responses: - '200': + "200": description: list of dataset meta-datas content: application/json: @@ -5722,7 +5722,7 @@ paths: display_name: type: string example: - dataset_uuid: 'N:id-aaaa' + dataset_uuid: "N:id-aaaa" display_name: simcore-testing default: description: Default http error response body @@ -5756,7 +5756,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -5764,7 +5764,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -5807,7 +5807,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/computation/pipeline/{project_id}:start': + "/computation/pipeline/{project_id}:start": post: description: Starts a pipeline of a given project tags: @@ -5840,7 +5840,7 @@ paths: type: string format: uuid responses: - '201': + "201": description: Successfully started the pipeline content: application/json: @@ -5892,7 +5892,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -5900,7 +5900,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -5943,7 +5943,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/computation/pipeline/{project_id}:stop': + "/computation/pipeline/{project_id}:stop": post: description: Stops a pipeline of a given project tags: @@ -5958,7 +5958,7 @@ paths: type: string example: 123e4567-e89b-12d3-a456-426655440000 responses: - '204': + "204": description: Succesffully stopped the pipeline default: description: Default http error response body @@ -5992,7 +5992,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -6000,7 +6000,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -6081,7 +6081,7 @@ paths: minItems: 1 description: maximum number of items to return responses: - '200': + "200": description: list of projects content: application/json: @@ -6153,24 +6153,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -6209,26 +6209,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -6240,7 +6240,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -6275,7 +6275,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -6297,14 +6297,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -6349,7 +6349,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -6361,19 +6361,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -6397,8 +6404,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -6408,24 +6415,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -6449,7 +6456,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -6598,7 +6605,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -6606,7 +6613,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -6659,12 +6666,12 @@ paths: in: query schema: type: string - description: 'Option to create a project from existing template: from_template={template_uuid}' + description: "Option to create a project from existing template: from_template={template_uuid}" - name: as_template in: query schema: type: string - description: 'Option to create a template from existing project: as_template={study_uuid}' + description: "Option to create a template from existing project: as_template={study_uuid}" requestBody: content: application/json: @@ -6728,24 +6735,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -6784,26 +6791,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -6815,7 +6822,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -6850,7 +6857,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -6872,14 +6879,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -6924,7 +6931,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -6936,19 +6943,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -6972,8 +6986,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -6983,24 +6997,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -7024,7 +7038,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -7113,7 +7127,7 @@ paths: title: Quality description: Object containing Quality Assessment related data responses: - '201': + "201": description: project created content: application/json: @@ -7183,24 +7197,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -7239,26 +7253,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -7270,7 +7284,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -7305,7 +7319,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -7327,14 +7341,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -7379,7 +7393,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -7391,19 +7405,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -7427,8 +7448,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -7438,24 +7459,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -7479,7 +7500,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -7628,7 +7649,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -7636,7 +7657,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -7686,7 +7707,7 @@ paths: summary: Gets active project operationId: get_active_project responses: - '200': + "200": description: returns active project content: application/json: @@ -7756,24 +7777,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -7812,26 +7833,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -7843,7 +7864,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -7878,7 +7899,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -7900,14 +7921,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -7952,7 +7973,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -7964,19 +7985,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -8000,8 +8028,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -8011,24 +8039,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -8052,7 +8080,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -8201,7 +8229,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -8209,7 +8237,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -8252,7 +8280,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/projects/{project_id}': + "/projects/{project_id}": parameters: - name: project_id in: path @@ -8265,7 +8293,7 @@ paths: summary: Gets given project operationId: get_project responses: - '200': + "200": description: got detailed project content: application/json: @@ -8335,24 +8363,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -8391,26 +8419,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -8422,7 +8450,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -8457,7 +8485,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -8479,14 +8507,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -8531,7 +8559,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -8543,19 +8571,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -8579,8 +8614,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -8590,24 +8625,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -8631,7 +8666,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -8780,7 +8815,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -8788,7 +8823,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -8905,24 +8940,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -8961,26 +8996,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -8992,7 +9027,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -9027,7 +9062,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -9049,14 +9084,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -9101,7 +9136,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -9113,19 +9148,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -9149,8 +9191,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -9160,24 +9202,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -9201,7 +9243,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -9290,7 +9332,7 @@ paths: title: Quality description: Object containing Quality Assessment related data responses: - '200': + "200": description: got detailed project content: application/json: @@ -9360,24 +9402,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -9416,26 +9458,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -9447,7 +9489,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -9482,7 +9524,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -9504,14 +9546,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -9556,7 +9598,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -9568,19 +9610,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -9604,8 +9653,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -9615,24 +9664,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -9656,7 +9705,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -9805,7 +9854,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -9813,7 +9862,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -9862,9 +9911,9 @@ paths: summary: Delete given project operationId: delete_project responses: - '204': + "204": description: project has been successfully deleted - '/projects/{project_id}:open': + "/projects/{project_id}:open": parameters: - name: project_id in: path @@ -9885,7 +9934,7 @@ paths: type: string example: 5ac57685-c40f-448f-8711-70be1936fd63 responses: - '200': + "200": description: project successfuly opened content: application/json: @@ -9955,24 +10004,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -10011,26 +10060,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -10042,7 +10091,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -10077,7 +10126,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -10099,14 +10148,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -10151,7 +10200,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -10163,19 +10212,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -10199,8 +10255,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -10210,24 +10266,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -10251,7 +10307,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -10400,7 +10456,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -10408,7 +10464,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -10451,7 +10507,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/projects/{project_id}/state': + "/projects/{project_id}/state": parameters: - name: project_id in: path @@ -10464,7 +10520,7 @@ paths: summary: returns the state of a project operationId: state_project responses: - '200': + "200": description: returns the project current state content: application/json: @@ -10532,7 +10588,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -10540,7 +10596,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -10583,7 +10639,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/projects/{project_id}:xport': + "/projects/{project_id}:xport": parameters: - name: project_id in: path @@ -10596,7 +10652,7 @@ paths: summary: creates an archive of the project and downloads it operationId: export_project responses: - '200': + "200": description: creates an archive from a project file content: application/zip: @@ -10635,7 +10691,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -10643,7 +10699,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -10848,7 +10904,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -10856,7 +10912,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -10899,7 +10955,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/projects/{project_id}:close': + "/projects/{project_id}:close": parameters: - name: project_id in: path @@ -10920,7 +10976,7 @@ paths: type: string example: 5ac57685-c40f-448f-8711-70be1936fd63 responses: - '204': + "204": description: project succesfuly closed default: description: Default http error response body @@ -10954,7 +11010,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -10962,7 +11018,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -11005,7 +11061,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/projects/{project_id}/nodes': + "/projects/{project_id}/nodes": parameters: - name: project_id in: path @@ -11042,7 +11098,7 @@ paths: service_key: simcore/services/dynamic/3d-viewer service_version: 1.4.0 responses: - '201': + "201": description: created content: application/json: @@ -11095,7 +11151,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -11103,7 +11159,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -11146,7 +11202,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/projects/{project_id}/nodes/{node_id}': + "/projects/{project_id}/nodes/{node_id}": parameters: - name: project_id in: path @@ -11164,7 +11220,7 @@ paths: description: Gets node status operationId: get_node responses: - '200': + "200": description: OK service exists and runs. Returns node details. content: application/json: @@ -11225,7 +11281,7 @@ paths: description: different base path where current service is mounted otherwise defaults to root type: string example: /x/E1O2E-LAH - default: '' + default: "" service_state: description: | the service state * 'pending' - The service is waiting for resources to start * 'pulling' - The service is being pulled from the registry * 'starting' - The service is starting * 'running' - The service is running * 'complete' - The service completed * 'failed' - The service failed to start @@ -11276,7 +11332,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -11284,7 +11340,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -11333,7 +11389,7 @@ paths: description: Stops and removes a node from the project operationId: delete_node responses: - '204': + "204": description: node has been successfully deleted from project default: description: Default http error response body @@ -11367,7 +11423,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -11375,7 +11431,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -11418,7 +11474,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/nodes/{nodeInstanceUUID}/outputUi/{outputKey}': + "/nodes/{nodeInstanceUUID}/outputUi/{outputKey}": get: tags: - node @@ -11436,7 +11492,7 @@ paths: schema: type: string responses: - '200': + "200": description: Service Information content: application/json: @@ -11481,7 +11537,7 @@ paths: description: Error code type: integer example: 404 - '/nodes/{nodeInstanceUUID}/outputUi/{outputKey}/{apiCall}': + "/nodes/{nodeInstanceUUID}/outputUi/{outputKey}/{apiCall}": post: tags: - node @@ -11561,7 +11617,7 @@ paths: label: type: string thumbnail: - description: 'data url - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs' + description: "data url - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs" type: string - type: object - type: array @@ -11575,7 +11631,7 @@ paths: folder: type: boolean - type: object - '/nodes/{nodeInstanceUUID}/iframe': + "/nodes/{nodeInstanceUUID}/iframe": get: tags: - node @@ -11590,7 +11646,7 @@ paths: responses: default: description: any response appropriate in the iframe context - '/projects/{study_uuid}/tags/{tag_id}': + "/projects/{study_uuid}/tags/{tag_id}": parameters: - name: tag_id in: path @@ -11608,7 +11664,7 @@ paths: summary: Links an existing label with an existing study operationId: add_tag responses: - '200': + "200": description: The tag has been successfully linked to the study content: application/json: @@ -11678,24 +11734,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -11734,26 +11790,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -11765,7 +11821,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -11800,7 +11856,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -11822,14 +11878,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -11874,7 +11930,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -11886,19 +11942,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -11922,8 +11985,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -11933,24 +11996,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -11974,7 +12037,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -12123,7 +12186,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -12131,7 +12194,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -12180,7 +12243,7 @@ paths: summary: Removes an existing link between a label and a study operationId: remove_tag responses: - '200': + "200": description: The tag has been successfully removed from the study content: application/json: @@ -12250,24 +12313,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: '2018-07-01T11:13:43Z' + example: "2018-07-01T11:13:43Z" thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - key @@ -12306,26 +12369,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' + - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - 'null' + - "null" example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -12337,7 +12400,7 @@ paths: format: uuid output: type: string - pattern: '^[-_a-zA-Z0-9]+$' + pattern: "^[-_a-zA-Z0-9]+$" - type: object additionalProperties: false required: @@ -12372,7 +12435,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": type: string enum: - Invisible @@ -12394,14 +12457,14 @@ paths: default: {} type: object patternProperties: - '^[-_a-zA-Z0-9]+$': + "^[-_a-zA-Z0-9]+$": oneOf: - type: - integer - boolean - string - number - - 'null' + - "null" - type: object additionalProperties: false required: @@ -12446,7 +12509,7 @@ paths: - nodeUuid2 parent: type: - - 'null' + - "null" - string format: uuid description: Parent's (group-nodes') node ID s. @@ -12458,19 +12521,26 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" deprecated: true + connection_state: + title: Status + description: the node's connection state + default: CHANGED + enum: + - CHANGED + - CURRENT state: title: RunningState description: the node's running state @@ -12494,8 +12564,8 @@ paths: workbench: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -12505,24 +12575,24 @@ paths: additionalProperties: false required: - x - - 'y' + - "y" properties: x: type: integer description: The x position example: - - '12' - 'y': + - "12" + "y": type: integer description: The y position example: - - '15' + - "15" additionalProperties: true slideshow: type: object x-patternProperties: - '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': - type: object + ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" + : type: object additionalProperties: false required: - position @@ -12546,7 +12616,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: 'some:id:to:a:classifier' + example: "some:id:to:a:classifier" dev: type: object description: object used for development purposes only @@ -12695,7 +12765,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -12703,7 +12773,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -12752,8 +12822,8 @@ paths: tags: - activity responses: - '200': - description: 'Object containing queuing, CPU and Memory usage/limits information of services' + "200": + description: "Object containing queuing, CPU and Memory usage/limits information of services" content: application/json: schema: @@ -12817,7 +12887,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -12825,7 +12895,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -12875,7 +12945,7 @@ paths: summary: List all tags for the current user operationId: list_tags responses: - '200': + "200": description: List of tags content: application/json: @@ -12905,7 +12975,7 @@ paths: type: string color: type: string - pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" error: nullable: true default: null @@ -12941,7 +13011,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -12949,7 +13019,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -12998,7 +13068,7 @@ paths: summary: Creates a new tag operationId: create_tag responses: - '200': + "200": description: The created tag content: application/json: @@ -13021,7 +13091,7 @@ paths: type: string color: type: string - pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" error: nullable: true default: null @@ -13057,7 +13127,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13065,7 +13135,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13108,7 +13178,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/tags/{tag_id}': + "/tags/{tag_id}": parameters: - name: tag_id in: path @@ -13121,7 +13191,7 @@ paths: summary: Updates a tag operationId: update_tag responses: - '200': + "200": description: The updated tag content: application/json: @@ -13144,7 +13214,7 @@ paths: type: string color: type: string - pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' + pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" error: nullable: true default: null @@ -13180,7 +13250,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13188,7 +13258,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13237,7 +13307,7 @@ paths: summary: Deletes an existing tag operationId: delete_tag responses: - '204': + "204": description: The tag has been successfully deleted /publications/service-submission: post: @@ -13260,7 +13330,7 @@ paths: type: string format: binary responses: - '204': + "204": description: Submission has been registered default: description: Default http error response body @@ -13294,7 +13364,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13302,7 +13372,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13351,9 +13421,9 @@ paths: - catalog operationId: list_catalog_dags responses: - '200': + "200": description: List of catalog dags - '422': + "422": description: Validation Error default: description: Default http error response body @@ -13387,7 +13457,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13395,7 +13465,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13450,9 +13520,9 @@ paths: type: object additionalProperties: true responses: - '201': + "201": description: The dag was successfully created - '422': + "422": description: Validation Error default: description: Default http error response body @@ -13486,7 +13556,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13494,7 +13564,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13537,7 +13607,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/catalog/dags/{dag_id}': + "/catalog/dags/{dag_id}": parameters: - in: path name: dag_id @@ -13557,9 +13627,9 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: The dag was replaced in catalog - '422': + "422": description: Validation Error default: description: Default http error response body @@ -13593,7 +13663,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13601,7 +13671,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13650,9 +13720,9 @@ paths: summary: Deletes an existing dag operationId: delete_catalog_dag responses: - '204': + "204": description: Successfully deleted - '422': + "422": description: Validation Error /catalog/services: get: @@ -13661,7 +13731,7 @@ paths: summary: List Services operationId: list_catalog_services responses: - '200': + "200": description: Returns list of services from the catalog default: description: Default http error response body @@ -13695,7 +13765,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13703,7 +13773,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13746,7 +13816,7 @@ paths: message: Password is not secure field: pasword status: 400 - '/catalog/services/{service_key}/{service_version}': + "/catalog/services/{service_key}/{service_version}": parameters: - in: path name: service_key @@ -13768,7 +13838,7 @@ paths: summary: Get Service operationId: get_catalog_service responses: - '200': + "200": description: Returns service default: description: Default http error response body @@ -13802,7 +13872,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13810,7 +13880,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13865,7 +13935,7 @@ paths: type: object additionalProperties: true responses: - '200': + "200": description: Returns modified service default: description: Default http error response body @@ -13899,7 +13969,7 @@ paths: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -13907,7 +13977,7 @@ paths: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: @@ -13973,7 +14043,7 @@ paths: title: File Size in Bytes type: integer responses: - '200': + "200": content: application/json: schema: @@ -13984,7 +14054,7 @@ paths: title: File Type type: string redirection_url: - description: 'Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link' + description: "Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link" format: uri maxLength: 2083 minLength: 1 @@ -14012,7 +14082,7 @@ paths: get: operationId: list_supported_filetypes responses: - '200': + "200": content: application/json: schema: @@ -14024,7 +14094,7 @@ paths: title: File Type type: string redirection_url: - description: 'Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link' + description: "Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link" format: uri maxLength: 2083 minLength: 1 @@ -14083,7 +14153,7 @@ components: - INFO - ERROR message: - description: 'log message. If logger is USER, then it MUST be human readable' + description: "log message. If logger is USER, then it MUST be human readable" type: string logger: description: name of the logger receiving this message @@ -14091,7 +14161,7 @@ components: required: - message example: - message: 'Hi there, Mr user' + message: "Hi there, Mr user" level: INFO logger: user-logger errors: diff --git a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json index 713bae081d8..75fe94ec0d4 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json +++ b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json @@ -396,6 +396,15 @@ }, "deprecated": true }, + "connection_state": { + "title": "Status", + "description": "the node's connection state", + "default": "CHANGED", + "enum": [ + "CHANGED", + "CURRENT" + ] + }, "state": { "title": "RunningState", "description": "the node's running state", From bfbb5716cef30a2d3c51e4f9d7773342e99e6404 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 16:01:01 +0100 Subject: [PATCH 002/200] missing string --- api/specs/common/schemas/project-v0.0.1.json | 1 + 1 file changed, 1 insertion(+) diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index 75fe94ec0d4..98e3cf00c0e 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -400,6 +400,7 @@ "title": "Status", "description": "the node's connection state", "default": "CHANGED", + "type": "string", "enum": [ "CHANGED", "CURRENT" From 8eb35400ce7c2b6b842f9d0af0080f3301e632a7 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 16:02:28 +0100 Subject: [PATCH 003/200] renamed method --- .../simcore_service_director_v2/api/routes/computations.py | 4 ++-- .../src/simcore_service_director_v2/utils/dags.py | 2 +- services/director-v2/tests/unit/test_dags.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index de4579166ad..ce29827bb44 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -36,7 +36,7 @@ is_pipeline_stopped, ) from ...utils.dags import ( - create_complete_dag_graph, + create_complete_dag, create_minimal_computational_graph_based_on_selection, ) from ...utils.exceptions import PipelineNotFoundError, ProjectNotFoundError @@ -119,7 +119,7 @@ async def create_computation( ) # create the complete DAG graph - complete_dag = create_complete_dag_graph(project.workbench) + complete_dag = create_complete_dag(project.workbench) # find the minimal viable graph to be run computational_dag = await create_minimal_computational_graph_based_on_selection( full_dag_graph=complete_dag, diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index 978eb135e56..b22634eaf7a 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -59,7 +59,7 @@ async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: @log_decorator(logger=logger) -def create_complete_dag_graph(workbench: Workbench) -> nx.DiGraph: +def create_complete_dag(workbench: Workbench) -> nx.DiGraph: """creates a complete graph out of the project workbench""" dag_graph = nx.DiGraph() for node_id, node in workbench.items(): diff --git a/services/director-v2/tests/unit/test_dags.py b/services/director-v2/tests/unit/test_dags.py index d07cc9f7c39..349e3aaec87 100644 --- a/services/director-v2/tests/unit/test_dags.py +++ b/services/director-v2/tests/unit/test_dags.py @@ -11,7 +11,7 @@ import pytest from models_library.projects import Workbench from simcore_service_director_v2.utils.dags import ( - create_complete_dag_graph, + create_complete_dag, create_minimal_computational_graph_based_on_selection, ) @@ -20,7 +20,7 @@ def test_create_complete_dag_graph( fake_workbench: Workbench, fake_workbench_complete_adjacency: Dict[str, List[str]], ): - dag_graph = create_complete_dag_graph(fake_workbench) + dag_graph = create_complete_dag(fake_workbench) assert nx.is_directed_acyclic_graph(dag_graph) assert nx.to_dict_of_lists(dag_graph) == fake_workbench_complete_adjacency @@ -149,7 +149,7 @@ async def test_create_minimal_graph( force_exp_dag: Dict[str, List[str]], not_forced_exp_dag: Dict[str, List[str]], ): - full_dag_graph: nx.DiGraph = create_complete_dag_graph(fake_workbench) + full_dag_graph: nx.DiGraph = create_complete_dag(fake_workbench) # everything is outdated in that case reduced_dag: nx.DiGraph = ( From 8e4ceb0da44b663753e62d55b14d426281d730a5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:52:04 +0100 Subject: [PATCH 004/200] compute runnable states and io states --- .../simcore_service_director_v2/utils/dags.py | 91 +++++++++++++++---- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index b22634eaf7a..0c59b76b66f 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -1,4 +1,5 @@ import logging +from enum import Enum from typing import Any, Dict, List, Set import networkx as nx @@ -80,49 +81,105 @@ def create_complete_dag(workbench: Workbench) -> nx.DiGraph: return dag_graph +class NodeIOState(Enum): + OK = "OK" + OUTDATED = "OUTDATED" + + +async def compute_node_io_state( + nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID +) -> NodeIOState: + node = nodes_data_view[str(node_id)] + # if the node has no output it is outdated for sure + if not node["outputs"]: + return NodeIOState.OUTDATED + for output_port in node["outputs"]: + if output_port is None: + return NodeIOState.OUTDATED + # maybe our inputs changed? let's compute the node hash and compare with the saved one + async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: + return nodes_data_view[str(node_id)] + + computed_hash = await compute_node_hash(node_id, get_node_io_payload_cb) + if computed_hash != node["run_hash"]: + return NodeIOState.OUTDATED + return NodeIOState.OK + + +class NodeRunnableState(Enum): + WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" + READY = "READY" + + +async def compute_node_runnable_state(nodes_data_view, node_id) -> NodeRunnableState: + node = nodes_data_view[str(node_id)] + # check if the previous node is outdated or waits for dependencies... in which case this one has to wait + for input_port in node.get("inputs", {}).values(): + if isinstance(input_port, PortLink): + if node_needs_computation(nodes_data_view, input_port.node_uuid): + return NodeRunnableState.WAITING_FOR_DEPENDENCIES + # all good. ready + return NodeRunnableState.READY + + +async def compute_node_states( + nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID +): + node = nodes_data_view[str(node_id)] + node["io_state"] = await compute_node_io_state(nodes_data_view, node_id) + node["runnable_state"] = await compute_node_runnable_state(nodes_data_view, node_id) + + +def node_needs_computation( + nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID +) -> bool: + node = nodes_data_view[str(node_id)] + return (node.get("io_state", NodeIOState.OK) == NodeIOState.OUTDATED) or ( + node.get("runnable_state", NodeRunnableState.READY) + == NodeRunnableState.WAITING_FOR_DEPENDENCIES + ) + + @log_decorator(logger=logger) async def create_minimal_computational_graph_based_on_selection( - full_dag_graph: nx.DiGraph, selected_nodes: List[NodeID], force_restart: bool + complete_dag: nx.DiGraph, selected_nodes: List[NodeID], force_restart: bool ) -> nx.DiGraph: - nodes_data_view: nx.classes.reportviews.NodeDataView = full_dag_graph.nodes.data() + nodes_data_view: nx.classes.reportviews.NodeDataView = complete_dag.nodes.data() try: - - # first pass, find the nodes that are dirty (outdated) - for node in nx.topological_sort(full_dag_graph): - if _is_node_computational( - nodes_data_view[node]["key"] - ) and await _is_node_outdated(nodes_data_view, node): - _mark_node_as_dirty(nodes_data_view, node) + # first pass, traversing in topological order to correctly get the dependencies, set the nodes states + for node in nx.topological_sort(complete_dag): + if _is_node_computational(nodes_data_view[node]["key"]): + await compute_node_states(nodes_data_view, node) except nx.NetworkXUnfeasible: # not acyclic, return an empty graph return nx.DiGraph() # second pass, detect all the nodes that need to be run - minimal_selection_nodes: Set[NodeID] = set() + minimal_nodes_selection: Set[NodeID] = set() if not selected_nodes: - # fully automatic detection, we want anything that is outdated or depending on outdated nodes - minimal_selection_nodes.update( + # fully automatic detection, we want anything that is waiting for dependencies or outdated + minimal_nodes_selection.update( { n for n, _ in nodes_data_view if _is_node_computational(nodes_data_view[n]["key"]) - and (force_restart or _is_node_dirty(nodes_data_view, n)) + and (force_restart or node_needs_computation(nodes_data_view, n)) } ) else: # we want all the outdated nodes that are in the tree leading to the selected nodes for node in selected_nodes: - minimal_selection_nodes.update( + minimal_nodes_selection.update( set( n - for n in nx.bfs_tree(full_dag_graph, str(node), reverse=True) + for n in nx.bfs_tree(complete_dag, str(node), reverse=True) if _is_node_computational(nodes_data_view[n]["key"]) - and (force_restart or _is_node_dirty(nodes_data_view, n)) + and (force_restart or node_needs_computation(nodes_data_view, n)) ) ) - return full_dag_graph.subgraph(minimal_selection_nodes) + return complete_dag.subgraph(minimal_nodes_selection) @log_decorator(logger=logger) From 5a13d37706cd6f5cabcbbbd2328de16ad951f25b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 22:15:33 +0100 Subject: [PATCH 005/200] refactoring the tests --- .../tests/integration/test_computation_api.py | 388 ++++++++++-------- 1 file changed, 208 insertions(+), 180 deletions(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index e7275035754..f100e95c101 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -25,6 +25,7 @@ from simcore_service_director_v2.models.schemas.comp_tasks import ComputationTaskOut from sqlalchemy import literal_column from starlette import status +from starlette.responses import Response from starlette.testclient import TestClient from tenacity import retry, retry_if_exception_type, stop_after_delay, wait_random from yarl import URL @@ -41,6 +42,74 @@ COMPUTATION_URL: str = "v2/computations" +# HELPERS --------------------------------------- + + +def _assert_pipeline_status( + client: TestClient, + url: AnyHttpUrl, + user_id: PositiveInt, + project_uuid: UUID, + wait_for_states: List[RunningState] = None, +) -> ComputationTaskOut: + if not wait_for_states: + wait_for_states = [ + RunningState.SUCCESS, + RunningState.FAILED, + RunningState.ABORTED, + ] + + MAX_TIMEOUT_S = 60 + + @retry( + stop=stop_after_delay(MAX_TIMEOUT_S), + wait=wait_random(0, 2), + retry=retry_if_exception_type(AssertionError), + reraise=True, + ) + def check_pipeline_state() -> ComputationTaskOut: + response = client.get(url, params={"user_id": user_id}) + assert ( + response.status_code == status.HTTP_202_ACCEPTED + ), f"response code is {response.status_code}, error: {response.text}" + task_out = ComputationTaskOut.parse_obj(response.json()) + assert task_out.id == project_uuid + assert task_out.url == f"{client.base_url}/v2/computations/{project_uuid}" + print("Pipeline is in ", task_out.state) + assert task_out.state in wait_for_states + return task_out + + task_out = check_pipeline_state() + + return task_out + + +def _create_pipeline( + client: TestClient, + *, + project: ProjectAtDB, + user_id: PositiveInt, + start_pipeline: bool, + expected_response_status_code: status, + **kwargs, +) -> Response: + response = client.post( + COMPUTATION_URL, + json={ + "user_id": user_id, + "project_id": str(project.uuid), + "start_pipeline": start_pipeline, + **kwargs, + }, + ) + assert ( + response.status_code == expected_response_status_code + ), f"response code is {response.status_code}, error: {response.text}" + return response + + +# FIXTURES --------------------------------------- + @pytest.fixture(autouse=True) def minimal_configuration( @@ -159,6 +228,9 @@ def updator(project_uuid: str): yield updator +# TESTS --------------------------------------- + + @pytest.mark.parametrize( "body,exp_response", [ @@ -194,56 +266,13 @@ def test_start_empty_computation( ): # send an empty project to process empty_project = project() - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(empty_project.uuid), - "start_pipeline": True, - }, - ) - assert ( - response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - ), f"response code is {response.status_code}, error: {response.text}" - - -def _assert_pipeline_status( - client: TestClient, - url: AnyHttpUrl, - user_id: PositiveInt, - project_uuid: UUID, - wait_for_states: List[RunningState] = None, -) -> ComputationTaskOut: - if not wait_for_states: - wait_for_states = [ - RunningState.SUCCESS, - RunningState.FAILED, - RunningState.ABORTED, - ] - - MAX_TIMEOUT_S = 60 - - @retry( - stop=stop_after_delay(MAX_TIMEOUT_S), - wait=wait_random(0, 2), - retry=retry_if_exception_type(AssertionError), - reraise=True, + _create_pipeline( + client, + project=empty_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, ) - def check_pipeline_state() -> ComputationTaskOut: - response = client.get(url, params={"user_id": user_id}) - assert ( - response.status_code == status.HTTP_202_ACCEPTED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == project_uuid - assert task_out.url == f"{client.base_url}/v2/computations/{project_uuid}" - print("Pipeline is in ", task_out.state) - assert task_out.state in wait_for_states - return task_out - - task_out = check_pipeline_state() - - return task_out @pytest.mark.parametrize( @@ -268,23 +297,18 @@ def test_run_partial_computation( ): # send a valid project with sleepers sleepers_project = project(workbench=fake_workbench_without_outputs) - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - "start_pipeline": True, - "subgraph": [ - str(node_id) - for index, node_id in enumerate(sleepers_project.workbench) - if index in subgraph_elements - ], - }, + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_201_CREATED, + subgraph=[ + str(node_id) + for index, node_id in enumerate(sleepers_project.workbench) + if index in subgraph_elements + ], ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -319,42 +343,34 @@ def test_run_partial_computation( # run it a second time. the tasks are all up-to-date, nothing should be run # FIXME: currently the webserver is the one updating the projects table so we need to fake this by copying the run_hash update_project_workbench_with_comp_tasks(str(sleepers_project.uuid)) - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - "start_pipeline": True, - "subgraph": [ - str(node_id) - for index, node_id in enumerate(sleepers_project.workbench) - if index in subgraph_elements - ], - }, + + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + subgraph=[ + str(node_id) + for index, node_id in enumerate(sleepers_project.workbench) + if index in subgraph_elements + ], ) - assert ( - response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - ), f"response code is {response.status_code}, error: {response.text}" # force run it this time. - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - "start_pipeline": True, - "subgraph": [ - str(node_id) - for index, node_id in enumerate(sleepers_project.workbench) - if index in subgraph_elements - ], - "force_restart": True, - }, + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_201_CREATED, + subgraph=[ + str(node_id) + for index, node_id in enumerate(sleepers_project.workbench) + if index in subgraph_elements + ], + force_restart=True, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -372,21 +388,17 @@ def test_run_computation( user_id: PositiveInt, project: Callable, fake_workbench_without_outputs: Dict[str, Any], + update_project_workbench_with_comp_tasks: Callable, ): - # send a valid project with sleepers sleepers_project = project(workbench=fake_workbench_without_outputs) - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - "start_pipeline": True, - }, + # send a valid project with sleepers + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_201_CREATED, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -397,7 +409,7 @@ def test_run_computation( == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" ) - # now wait for the computation to finish + # wait for the computation to finish task_out = _assert_pipeline_status( client, task_out.url, user_id, sleepers_project.uuid ) @@ -408,6 +420,42 @@ def test_run_computation( task_out.state == RunningState.SUCCESS ), f"the pipeline complete with state {task_out.state}" + # FIXME: currently the webserver is the one updating the projects table so we need to fake this by copying the run_hash + update_project_workbench_with_comp_tasks(str(sleepers_project.uuid)) + # run again should return a 422 cause everything is uptodate + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + ) + + # now force run again + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_201_CREATED, + force_restart=True, + ) + task_out = ComputationTaskOut.parse_obj(response.json()) + assert task_out.id == sleepers_project.uuid + assert task_out.state == RunningState.PUBLISHED + assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" + assert ( + task_out.stop_url + == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" + ) + + # wait for the computation to finish + task_out = _assert_pipeline_status( + client, task_out.url, user_id, sleepers_project.uuid + ) + assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" + assert task_out.stop_url == None + def test_abort_computation( client: TestClient, @@ -415,20 +463,15 @@ def test_abort_computation( project: Callable, fake_workbench_without_outputs: Dict[str, Any], ): - # send a valid project with sleepers sleepers_project = project(workbench=fake_workbench_without_outputs) - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - "start_pipeline": True, - }, + # send a valid project with sleepers + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_201_CREATED, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -485,16 +528,15 @@ def test_update_and_delete_computation( project: Callable, fake_workbench_without_outputs: Dict[str, Any], ): - # send a valid project with sleepers sleepers_project = project(workbench=fake_workbench_without_outputs) - response = client.post( - COMPUTATION_URL, - json={"user_id": user_id, "project_id": str(sleepers_project.uuid)}, + # send a valid project with sleepers + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=False, + expected_response_status_code=status.HTTP_201_CREATED, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -502,14 +544,13 @@ def test_update_and_delete_computation( assert not task_out.stop_url # update the pipeline - response = client.post( - COMPUTATION_URL, - json={"user_id": user_id, "project_id": str(sleepers_project.uuid)}, + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=False, + expected_response_status_code=status.HTTP_201_CREATED, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -517,14 +558,13 @@ def test_update_and_delete_computation( assert not task_out.stop_url # update the pipeline - response = client.post( - COMPUTATION_URL, - json={"user_id": user_id, "project_id": str(sleepers_project.uuid)}, + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=False, + expected_response_status_code=status.HTTP_201_CREATED, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" - task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid @@ -532,17 +572,13 @@ def test_update_and_delete_computation( assert not task_out.stop_url # start it now - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - "start_pipeline": True, - }, + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_201_CREATED, ) - assert ( - response.status_code == status.HTTP_201_CREATED - ), f"response code is {response.status_code}, error: {response.text}" task_out = ComputationTaskOut.parse_obj(response.json()) assert task_out.id == sleepers_project.uuid assert task_out.state == RunningState.PUBLISHED @@ -565,16 +601,13 @@ def test_update_and_delete_computation( ), f"pipeline is not in the expected starting state but in {task_out.state}" # now try to update the pipeline, is expected to be forbidden - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(sleepers_project.uuid), - }, + response = _create_pipeline( + client, + project=sleepers_project, + user_id=user_id, + start_pipeline=False, + expected_response_status_code=status.HTTP_403_FORBIDDEN, ) - assert ( - response.status_code == status.HTTP_403_FORBIDDEN - ), f"response code is {response.status_code}, error: {response.text}" # try to delete the pipeline, is expected to be forbidden if force parameter is false (default) response = client.delete(task_out.url, json={"user_id": user_id}) @@ -607,26 +640,21 @@ def test_pipeline_with_no_comp_services_still_create_correct_comp_tasks( ) # this pipeline is not runnable as there are no computational services - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(project_with_dynamic_node.uuid), - "start_pipeline": True, - }, + response = _create_pipeline( + client, + project=project_with_dynamic_node, + user_id=user_id, + start_pipeline=True, + expected_response_status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, ) - assert ( - response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY - ), f"response code is {response.status_code}, error: {response.text}" # still this pipeline shall be createable if we do not want to start it - response = client.post( - COMPUTATION_URL, - json={ - "user_id": user_id, - "project_id": str(project_with_dynamic_node.uuid), - "start_pipeline": False, - }, + response = _create_pipeline( + client, + project=project_with_dynamic_node, + user_id=user_id, + start_pipeline=False, + expected_response_status_code=status.HTTP_201_CREATED, ) assert ( response.status_code == status.HTTP_201_CREATED From def28b4de320acae46a9648370a57727af715235 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 22:27:09 +0100 Subject: [PATCH 006/200] properly check force_restart when running a whole computation --- .../tests/integration/test_computation_api.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index f100e95c101..1ac5a9fd257 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -6,6 +6,7 @@ # pylint:disable=too-many-arguments from copy import deepcopy +from pprint import pformat from random import randint from typing import Any, Callable, Dict, List from uuid import UUID, uuid4 @@ -389,6 +390,7 @@ def test_run_computation( project: Callable, fake_workbench_without_outputs: Dict[str, Any], update_project_workbench_with_comp_tasks: Callable, + fake_workbench_adjacency: Dict[str, Any], ): sleepers_project = project(workbench=fake_workbench_without_outputs) # send a valid project with sleepers @@ -401,6 +403,7 @@ def test_run_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) + # check the contents is correct assert task_out.id == sleepers_project.uuid assert task_out.state == RunningState.PUBLISHED assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" @@ -408,6 +411,14 @@ def test_run_computation( task_out.stop_url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" ) + for key, list_value in task_out.pipeline.items(): + assert ( + str(key) in fake_workbench_adjacency + ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" + for item in list_value: + assert ( + str(item) in fake_workbench_adjacency[str(key)] + ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" # wait for the computation to finish task_out = _assert_pipeline_status( @@ -441,6 +452,7 @@ def test_run_computation( force_restart=True, ) task_out = ComputationTaskOut.parse_obj(response.json()) + # check the contents is correct assert task_out.id == sleepers_project.uuid assert task_out.state == RunningState.PUBLISHED assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" @@ -448,6 +460,14 @@ def test_run_computation( task_out.stop_url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" ) + for key, list_value in task_out.pipeline.items(): + assert ( + str(key) in fake_workbench_adjacency + ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" + for item in list_value: + assert ( + str(item) in fake_workbench_adjacency[str(key)] + ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" # wait for the computation to finish task_out = _assert_pipeline_status( From 4a724ee7258b025e6ffd8e24620b6e1e53279219 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 22:28:18 +0100 Subject: [PATCH 007/200] more refactoring --- services/director-v2/tests/integration/test_computation_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 1ac5a9fd257..8ef91d55849 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -426,7 +426,6 @@ def test_run_computation( ) assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" assert task_out.stop_url == None - assert ( task_out.state == RunningState.SUCCESS ), f"the pipeline complete with state {task_out.state}" From 2d8bee2082b0d4f40cdf444e710973d43013f1ed Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 22:46:16 +0100 Subject: [PATCH 008/200] better refactoring --- .../tests/integration/test_computation_api.py | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 8ef91d55849..8d26a729238 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -384,6 +384,32 @@ def test_run_partial_computation( assert task_out.pipeline == expected_adj_list_2nd_run +def _assert_computation_task_out_obj( + client: TestClient, + task_out: ComputationTaskOut, + *, + project: ProjectAtDB, + exp_task_state: RunningState, + exp_adjacency: Dict[str, List[str]], +): + assert task_out.id == project.uuid + assert task_out.state == exp_task_state + assert task_out.url == f"{client.base_url}/v2/computations/{project.uuid}" + assert ( + task_out.stop_url == f"{client.base_url}/v2/computations/{project.uuid}:stop" + if exp_task_state in [RunningState.SUCCESS] + else None + ) + for key, list_value in task_out.pipeline.items(): + assert ( + str(key) in exp_adjacency + ), f"expected adjacency list {pformat(exp_adjacency)}, received list {pformat(task_out.pipeline)}" + for item in list_value: + assert ( + str(item) in exp_adjacency[str(key)] + ), f"expected adjacency list {pformat(exp_adjacency)}, received list {pformat(task_out.pipeline)}" + + def test_run_computation( client: TestClient, user_id: PositiveInt, @@ -404,31 +430,26 @@ def test_run_computation( task_out = ComputationTaskOut.parse_obj(response.json()) # check the contents is correct - assert task_out.id == sleepers_project.uuid - assert task_out.state == RunningState.PUBLISHED - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert ( - task_out.stop_url - == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.PUBLISHED, + exp_adjacency=fake_workbench_adjacency, ) - for key, list_value in task_out.pipeline.items(): - assert ( - str(key) in fake_workbench_adjacency - ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" - for item in list_value: - assert ( - str(item) in fake_workbench_adjacency[str(key)] - ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" # wait for the computation to finish task_out = _assert_pipeline_status( client, task_out.url, user_id, sleepers_project.uuid ) - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert task_out.stop_url == None - assert ( - task_out.state == RunningState.SUCCESS - ), f"the pipeline complete with state {task_out.state}" + + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.SUCCESS, + exp_adjacency=fake_workbench_adjacency, + ) # FIXME: currently the webserver is the one updating the projects table so we need to fake this by copying the run_hash update_project_workbench_with_comp_tasks(str(sleepers_project.uuid)) @@ -452,28 +473,25 @@ def test_run_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) # check the contents is correct - assert task_out.id == sleepers_project.uuid - assert task_out.state == RunningState.PUBLISHED - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert ( - task_out.stop_url - == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.PUBLISHED, + exp_adjacency=fake_workbench_adjacency, ) - for key, list_value in task_out.pipeline.items(): - assert ( - str(key) in fake_workbench_adjacency - ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" - for item in list_value: - assert ( - str(item) in fake_workbench_adjacency[str(key)] - ), f"expected adjacency list {pformat(fake_workbench_adjacency)}, received list {pformat(task_out.pipeline)}" # wait for the computation to finish task_out = _assert_pipeline_status( client, task_out.url, user_id, sleepers_project.uuid ) - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert task_out.stop_url == None + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.SUCCESS, + exp_adjacency=fake_workbench_adjacency, + ) def test_abort_computation( From 79d9969875b031428478b75bab06ab137552d834 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 25 Jan 2021 23:53:49 +0100 Subject: [PATCH 009/200] changed return object to pipeline details. getting there --- .../api/routes/computations.py | 16 +++++-- .../models/schemas/comp_tasks.py | 35 +++++++++++++- .../simcore_service_director_v2/utils/dags.py | 48 ++++++++++++++----- .../tests/integration/test_computation_api.py | 10 ++-- 4 files changed, 84 insertions(+), 25 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index ce29827bb44..4a99180486a 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -36,6 +36,7 @@ is_pipeline_stopped, ) from ...utils.dags import ( + compute_pipeline_details, create_complete_dag, create_minimal_computational_graph_based_on_selection, ) @@ -159,7 +160,9 @@ async def create_computation( state=RunningState.PUBLISHED if job.start_pipeline else RunningState.NOT_STARTED, - pipeline=nx.to_dict_of_lists(computational_dag), + pipeline_details=await compute_pipeline_details( + complete_dag, computational_dag + ), url=f"{request.url}/{job.project_id}", stop_url=f"{request.url}/{job.project_id}:stop" if job.start_pipeline @@ -193,8 +196,9 @@ async def get_computation( try: # check that project actually exists # TODO: get a copy of the project and process it here instead! - await project_repo.get_project(project_id) - + project: ProjectAtDB = await project_repo.get_project(project_id) + # create the complete DAG graph + complete_dag = create_complete_dag(project.workbench) # get the project pipeline pipeline_at_db: CompPipelineAtDB = await computation_pipelines.get_pipeline( project_id @@ -204,7 +208,9 @@ async def get_computation( comp_tasks: List[CompTaskAtDB] = await computation_tasks.get_comp_tasks( project_id ) - dag_graph: nx.DiGraph = nx.from_dict_of_lists(pipeline_at_db.dag_adjacency_list) + dag_graph: nx.DiGraph = nx.from_dict_of_lists( + pipeline_at_db.dag_adjacency_list, create_using=nx.DiGraph + ) # filter the tasks by the effective pipeline filtered_tasks = [ t for t in comp_tasks if str(t.node_id) in list(dag_graph.nodes()) @@ -223,7 +229,7 @@ async def get_computation( task_out = ComputationTaskOut( id=project_id, state=pipeline_state, - pipeline=pipeline_at_db.dag_adjacency_list, + pipeline_details=await compute_pipeline_details(complete_dag, dag_graph), url=f"{request.url.remove_query_params('user_id')}", stop_url=f"{request.url.remove_query_params('user_id')}:stop" if is_pipeline_running(pipeline_state) diff --git a/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py index 7fee44c4f17..c3abadcbf23 100644 --- a/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py +++ b/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py @@ -1,3 +1,4 @@ +from enum import Enum, unique from typing import Dict, List, Optional from uuid import UUID @@ -11,14 +12,44 @@ TaskID = UUID +@unique +class NodeIOState(str, Enum): + OK = "OK" + OUTDATED = "OUTDATED" + + +@unique +class NodeRunnableState(str, Enum): + WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" + READY = "READY" + + +class NodeState(BaseModel): + io_state: NodeIOState = Field( + ..., description="represents the state of the inputs outputs" + ) + runnable_state: NodeRunnableState = Field( + ..., description="represent the runnable state of the node" + ) + + +class PipelineDetails(BaseModel): + adjacency_list: Dict[NodeID, List[NodeID]] = Field( + ..., description="The adjacency list in terms of {NodeID: [successor NodeID]}" + ) + node_states: Dict[NodeID, NodeState] = Field( + ..., description="The states of each of the pipeline node" + ) + + class ComputationTask(BaseModel): id: TaskID = Field(..., description="the id of the computation task") state: RunningState = Field(..., description="the state of the computational task") result: Optional[str] = Field( None, description="the result of the computational task" ) - pipeline: Dict[NodeID, List[NodeID]] = Field( - ..., description="the corresponding pipeline in terms of node uuids" + pipeline_details: PipelineDetails = Field( + ..., description="the details of the generated pipeline" ) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index 0c59b76b66f..8d8c539983b 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -8,6 +8,12 @@ from models_library.projects_nodes_io import PortLink from models_library.utils.nodes import compute_node_hash +from ..models.schemas.comp_tasks import ( + NodeIOState, + NodeRunnableState, + NodeState, + PipelineDetails, +) from .computations import NodeClass, to_node_class from .logging_utils import log_decorator @@ -81,11 +87,6 @@ def create_complete_dag(workbench: Workbench) -> nx.DiGraph: return dag_graph -class NodeIOState(Enum): - OK = "OK" - OUTDATED = "OUTDATED" - - async def compute_node_io_state( nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID ) -> NodeIOState: @@ -106,11 +107,6 @@ async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: return NodeIOState.OK -class NodeRunnableState(Enum): - WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" - READY = "READY" - - async def compute_node_runnable_state(nodes_data_view, node_id) -> NodeRunnableState: node = nodes_data_view[str(node_id)] # check if the previous node is outdated or waits for dependencies... in which case this one has to wait @@ -140,6 +136,14 @@ def node_needs_computation( ) +@log_decorator(logger=logger) +async def _set_computational_nodes_states(complete_dag: nx.DiGraph) -> None: + nodes_data_view: nx.classes.reportviews.NodeDataView = complete_dag.nodes.data() + for node in nx.topological_sort(complete_dag): + if _is_node_computational(nodes_data_view[node]["key"]): + await compute_node_states(nodes_data_view, node) + + @log_decorator(logger=logger) async def create_minimal_computational_graph_based_on_selection( complete_dag: nx.DiGraph, selected_nodes: List[NodeID], force_restart: bool @@ -148,9 +152,7 @@ async def create_minimal_computational_graph_based_on_selection( try: # first pass, traversing in topological order to correctly get the dependencies, set the nodes states - for node in nx.topological_sort(complete_dag): - if _is_node_computational(nodes_data_view[node]["key"]): - await compute_node_states(nodes_data_view, node) + await _set_computational_nodes_states(complete_dag) except nx.NetworkXUnfeasible: # not acyclic, return an empty graph return nx.DiGraph() @@ -182,6 +184,26 @@ async def create_minimal_computational_graph_based_on_selection( return complete_dag.subgraph(minimal_nodes_selection) +@log_decorator(logger=logger) +async def compute_pipeline_details( + complete_dag: nx.DiGraph, pipeline_dag: nx.DiGraph +) -> PipelineDetails: + + # first pass, traversing in topological order to correctly get the dependencies, set the nodes states + await _set_computational_nodes_states(complete_dag) + return PipelineDetails( + adjacency_list=nx.to_dict_of_lists(pipeline_dag), + node_states={ + node_id: NodeState( + io_state=node_data.get("io_state"), + runnable_state=node_data.get("runnable_state"), + ) + for node_id, node_data in complete_dag.nodes.data() + if node_id in pipeline_dag.nodes + }, + ) + + @log_decorator(logger=logger) def topological_sort_grouping(dag_graph: nx.DiGraph) -> List: # copy the graph diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 8d26a729238..18e84b536c8 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -395,12 +395,12 @@ def _assert_computation_task_out_obj( assert task_out.id == project.uuid assert task_out.state == exp_task_state assert task_out.url == f"{client.base_url}/v2/computations/{project.uuid}" - assert ( - task_out.stop_url == f"{client.base_url}/v2/computations/{project.uuid}:stop" - if exp_task_state in [RunningState.SUCCESS] + assert task_out.stop_url == ( + f"{client.base_url}/v2/computations/{project.uuid}:stop" + if exp_task_state in [RunningState.PUBLISHED, RunningState.PENDING] else None ) - for key, list_value in task_out.pipeline.items(): + for key, list_value in task_out.pipeline_details.adjacency_list.items(): assert ( str(key) in exp_adjacency ), f"expected adjacency list {pformat(exp_adjacency)}, received list {pformat(task_out.pipeline)}" @@ -429,7 +429,7 @@ def test_run_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) - # check the contents is correct + # check the contents is correctb _assert_computation_task_out_obj( client, task_out, From cf9a132e4ca4f12a98c07ca7ba746f754ac36970 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 09:58:22 +0100 Subject: [PATCH 010/200] need to compute the complete dag from the tasks when getting the state --- .../api/routes/computations.py | 25 ++++---- .../modules/db/repositories/comp_tasks.py | 14 +++++ .../simcore_service_director_v2/utils/dags.py | 20 ++++++ .../tests/integration/test_computation_api.py | 62 ++++++++++++++----- ...e_workbench_computational_node_states.json | 18 ++++++ 5 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 services/director-v2/tests/mocks/fake_workbench_computational_node_states.json diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index 4a99180486a..b8d2f30ba43 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -38,6 +38,7 @@ from ...utils.dags import ( compute_pipeline_details, create_complete_dag, + create_complete_dag_from_tasks, create_minimal_computational_graph_based_on_selection, ) from ...utils.exceptions import PipelineNotFoundError, ProjectNotFoundError @@ -195,25 +196,25 @@ async def get_computation( log.debug("User %s getting computation status for project %s", user_id, project_id) try: # check that project actually exists - # TODO: get a copy of the project and process it here instead! - project: ProjectAtDB = await project_repo.get_project(project_id) - # create the complete DAG graph - complete_dag = create_complete_dag(project.workbench) + await project_repo.get_project(project_id) + + # NOTE: Here it is assumed the project exists in comp_tasks/comp_pipeline # get the project pipeline pipeline_at_db: CompPipelineAtDB = await computation_pipelines.get_pipeline( project_id ) - - # get the project task states - comp_tasks: List[CompTaskAtDB] = await computation_tasks.get_comp_tasks( - project_id - ) - dag_graph: nx.DiGraph = nx.from_dict_of_lists( + pipeline_dag: nx.DiGraph = nx.from_dict_of_lists( pipeline_at_db.dag_adjacency_list, create_using=nx.DiGraph ) + + # get the project task states + tasks: List[CompTaskAtDB] = await computation_tasks.get_all_tasks(project_id) + # create the complete DAG graph + complete_dag = create_complete_dag_from_tasks(tasks) + # filter the tasks by the effective pipeline filtered_tasks = [ - t for t in comp_tasks if str(t.node_id) in list(dag_graph.nodes()) + t for t in tasks if str(t.node_id) in list(pipeline_dag.nodes()) ] pipeline_state = get_pipeline_state_from_task_states( filtered_tasks, celery_client.settings.publication_timeout @@ -229,7 +230,7 @@ async def get_computation( task_out = ComputationTaskOut( id=project_id, state=pipeline_state, - pipeline_details=await compute_pipeline_details(complete_dag, dag_graph), + pipeline_details=await compute_pipeline_details(complete_dag, pipeline_dag), url=f"{request.url.remove_query_params('user_id')}", stop_url=f"{request.url.remove_query_params('user_id')}:stop" if is_pipeline_running(pipeline_state) diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py index 768fa26a479..3bf19c479aa 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py @@ -122,6 +122,20 @@ async def _generate_tasks_list_from_project( class CompTasksRepository(BaseRepository): + @log_decorator(logger=logger) + async def get_all_tasks( + self, + project_id: ProjectID, + ) -> List[CompTaskAtDB]: + tasks: List[CompTaskAtDB] = [] + async for row in self.connection.execute( + sa.select([comp_tasks]).where((comp_tasks.c.project_id == str(project_id))) + ): + task_db = CompTaskAtDB.from_orm(row) + tasks.append(task_db) + + return tasks + @log_decorator(logger=logger) async def get_comp_tasks( self, diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index 8d8c539983b..a68a8020001 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -7,6 +7,7 @@ from models_library.projects_nodes import NodeID from models_library.projects_nodes_io import PortLink from models_library.utils.nodes import compute_node_hash +from simcore_service_director_v2.models.domains.comp_tasks import CompTaskAtDB from ..models.schemas.comp_tasks import ( NodeIOState, @@ -87,6 +88,25 @@ def create_complete_dag(workbench: Workbench) -> nx.DiGraph: return dag_graph +@log_decorator(logger=logger) +def create_complete_dag_from_tasks(tasks: List[CompTaskAtDB]) -> nx.DiGraph: + dag_graph = nx.DiGraph() + for task in tasks: + dag_graph.add_node( + str(task.node_id), + name=task.job_id, + key=task.image.name, + version=task.image.tag, + inputs=task.inputs, + run_hash=task.run_hash, + outputs=task.outputs, + ) + for input_data in task.inputs.values(): + if isinstance(input_data, PortLink): + dag_graph.add_edge(str(input_data.node_uuid), str(task.node_id)) + return dag_graph + + async def compute_node_io_state( nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID ) -> NodeIOState: diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 18e84b536c8..7d15d934444 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -5,7 +5,9 @@ # pylint:disable=no-value-for-parameter # pylint:disable=too-many-arguments +import json from copy import deepcopy +from pathlib import Path from pprint import pformat from random import randint from typing import Any, Callable, Dict, List @@ -23,7 +25,12 @@ from simcore_postgres_database.models.projects import ProjectType, projects from simcore_postgres_database.models.users import UserRole, UserStatus, users from simcore_service_director_v2.models.domains.projects import ProjectAtDB -from simcore_service_director_v2.models.schemas.comp_tasks import ComputationTaskOut +from simcore_service_director_v2.models.schemas.comp_tasks import ( + ComputationTaskOut, + NodeIOState, + NodeRunnableState, + PipelineDetails, +) from sqlalchemy import literal_column from starlette import status from starlette.responses import Response @@ -390,7 +397,7 @@ def _assert_computation_task_out_obj( *, project: ProjectAtDB, exp_task_state: RunningState, - exp_adjacency: Dict[str, List[str]], + exp_pipeline_details: PipelineDetails, ): assert task_out.id == project.uuid assert task_out.state == exp_task_state @@ -400,14 +407,38 @@ def _assert_computation_task_out_obj( if exp_task_state in [RunningState.PUBLISHED, RunningState.PENDING] else None ) - for key, list_value in task_out.pipeline_details.adjacency_list.items(): - assert ( - str(key) in exp_adjacency - ), f"expected adjacency list {pformat(exp_adjacency)}, received list {pformat(task_out.pipeline)}" - for item in list_value: - assert ( - str(item) in exp_adjacency[str(key)] - ), f"expected adjacency list {pformat(exp_adjacency)}, received list {pformat(task_out.pipeline)}" + # check pipeline details contents + assert task_out.pipeline_details == exp_pipeline_details + + +@pytest.fixture(scope="session") +def fake_workbench_node_states_file(mocks_dir: Path) -> Path: + file_path = mocks_dir / "fake_workbench_computational_node_states.json" + assert file_path.exists() + return file_path + + +@pytest.fixture(scope="session") +def fake_workbench_computational_pipeline_details( + fake_workbench_computational_adjacency_file: Path, + fake_workbench_node_states_file: Path, +) -> PipelineDetails: + adjacency_list = json.loads(fake_workbench_computational_adjacency_file.read_text()) + node_states = json.loads(fake_workbench_node_states_file.read_text()) + return PipelineDetails.parse_obj( + {"adjacency_list": adjacency_list, "node_states": node_states} + ) + + +@pytest.fixture(scope="session") +def fake_workbench_computational_pipeline_details_completed( + fake_workbench_computational_pipeline_details: PipelineDetails, +) -> PipelineDetails: + completed_pipeline_details = deepcopy(fake_workbench_computational_pipeline_details) + for node_state in completed_pipeline_details.node_states.values(): + node_state.io_state = NodeIOState.OK + node_state.runnable_state = NodeRunnableState.READY + return completed_pipeline_details def test_run_computation( @@ -416,7 +447,8 @@ def test_run_computation( project: Callable, fake_workbench_without_outputs: Dict[str, Any], update_project_workbench_with_comp_tasks: Callable, - fake_workbench_adjacency: Dict[str, Any], + fake_workbench_computational_pipeline_details: PipelineDetails, + fake_workbench_computational_pipeline_details_completed: PipelineDetails, ): sleepers_project = project(workbench=fake_workbench_without_outputs) # send a valid project with sleepers @@ -435,7 +467,7 @@ def test_run_computation( task_out, project=sleepers_project, exp_task_state=RunningState.PUBLISHED, - exp_adjacency=fake_workbench_adjacency, + exp_pipeline_details=fake_workbench_computational_pipeline_details, ) # wait for the computation to finish @@ -448,7 +480,7 @@ def test_run_computation( task_out, project=sleepers_project, exp_task_state=RunningState.SUCCESS, - exp_adjacency=fake_workbench_adjacency, + exp_pipeline_details=fake_workbench_computational_pipeline_details_completed, ) # FIXME: currently the webserver is the one updating the projects table so we need to fake this by copying the run_hash @@ -478,7 +510,7 @@ def test_run_computation( task_out, project=sleepers_project, exp_task_state=RunningState.PUBLISHED, - exp_adjacency=fake_workbench_adjacency, + exp_pipeline_details=fake_workbench_computational_pipeline_details_completed, # NOTE: here the pipeline already ran so its states are different ) # wait for the computation to finish @@ -490,7 +522,7 @@ def test_run_computation( task_out, project=sleepers_project, exp_task_state=RunningState.SUCCESS, - exp_adjacency=fake_workbench_adjacency, + exp_pipeline_details=fake_workbench_computational_pipeline_details_completed, ) diff --git a/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json b/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json new file mode 100644 index 00000000000..ad238f7434e --- /dev/null +++ b/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json @@ -0,0 +1,18 @@ +{ + "3a710d8b-565c-5f46-870b-b45ebe195fc7": { + "io_state": "OUTDATED", + "runnable_state": "READY" + }, + "e1e2ea96-ce8f-5abc-8712-b8ed312a782c": { + "io_state": "OUTDATED", + "runnable_state": "READY" + }, + "415fefd1-d08b-53c1-adb0-16bed3a687ef": { + "io_state": "OUTDATED", + "runnable_state": "WAITING_FOR_DEPENDENCIES" + }, + "6ede1209-b459-5735-91fc-761aa584808d": { + "io_state": "OUTDATED", + "runnable_state": "WAITING_FOR_DEPENDENCIES" + } +} From 2b9c54bf5a8c947e1b8e4a63c14b4672b9bc2a90 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:41:39 +0100 Subject: [PATCH 011/200] testing partial pipeline works --- .../tests/integration/test_computation_api.py | 239 +++++++++++------- 1 file changed, 154 insertions(+), 85 deletions(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 7d15d934444..098f623e597 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -6,9 +6,9 @@ # pylint:disable=too-many-arguments import json +from collections import namedtuple from copy import deepcopy from pathlib import Path -from pprint import pformat from random import randint from typing import Any, Callable, Dict, List from uuid import UUID, uuid4 @@ -29,6 +29,7 @@ ComputationTaskOut, NodeIOState, NodeRunnableState, + NodeState, PipelineDetails, ) from sqlalchemy import literal_column @@ -116,6 +117,26 @@ def _create_pipeline( return response +def _assert_computation_task_out_obj( + client: TestClient, + task_out: ComputationTaskOut, + *, + project: ProjectAtDB, + exp_task_state: RunningState, + exp_pipeline_details: PipelineDetails, +): + assert task_out.id == project.uuid + assert task_out.state == exp_task_state + assert task_out.url == f"{client.base_url}/v2/computations/{project.uuid}" + assert task_out.stop_url == ( + f"{client.base_url}/v2/computations/{project.uuid}:stop" + if exp_task_state in [RunningState.PUBLISHED, RunningState.PENDING] + else None + ) + # check pipeline details contents + assert task_out.pipeline_details == exp_pipeline_details + + # FIXTURES --------------------------------------- @@ -236,6 +257,36 @@ def updator(project_uuid: str): yield updator +@pytest.fixture(scope="session") +def fake_workbench_node_states_file(mocks_dir: Path) -> Path: + file_path = mocks_dir / "fake_workbench_computational_node_states.json" + assert file_path.exists() + return file_path + + +@pytest.fixture(scope="session") +def fake_workbench_computational_pipeline_details( + fake_workbench_computational_adjacency_file: Path, + fake_workbench_node_states_file: Path, +) -> PipelineDetails: + adjacency_list = json.loads(fake_workbench_computational_adjacency_file.read_text()) + node_states = json.loads(fake_workbench_node_states_file.read_text()) + return PipelineDetails.parse_obj( + {"adjacency_list": adjacency_list, "node_states": node_states} + ) + + +@pytest.fixture(scope="session") +def fake_workbench_computational_pipeline_details_completed( + fake_workbench_computational_pipeline_details: PipelineDetails, +) -> PipelineDetails: + completed_pipeline_details = deepcopy(fake_workbench_computational_pipeline_details) + for node_state in completed_pipeline_details.node_states.values(): + node_state.io_state = NodeIOState.OK + node_state.runnable_state = NodeRunnableState.READY + return completed_pipeline_details + + # TESTS --------------------------------------- @@ -283,13 +334,51 @@ def test_start_empty_computation( ) +PartialComputationParams = namedtuple( + "PartialComputationParams", + "subgraph_elements, exp_pipeline_adj_list, exp_node_states", +) + + @pytest.mark.parametrize( - "subgraph_elements,exp_pipeline_dag_adj_list_1st_run", + "subgraph_elements,exp_pipeline_adj_list, exp_node_states", [ - pytest.param([0, 1], {1: []}, id="element 0,1"), pytest.param( - [1, 2, 4], - {1: [2], 2: [4], 3: [4], 4: []}, + *PartialComputationParams( + subgraph_elements=[0, 1], + exp_pipeline_adj_list={1: []}, + exp_node_states={ + 1: NodeState( + io_state=NodeIOState.OUTDATED, + runnable_state=NodeRunnableState.READY, + ) + }, + ), + id="element 0,1", + ), + pytest.param( + *PartialComputationParams( + subgraph_elements=[1, 2, 4], + exp_pipeline_adj_list={1: [2], 2: [4], 3: [4], 4: []}, + exp_node_states={ + 1: NodeState( + io_state=NodeIOState.OUTDATED, + runnable_state=NodeRunnableState.READY, + ), + 2: NodeState( + io_state=NodeIOState.OUTDATED, + runnable_state=NodeRunnableState.WAITING_FOR_DEPENDENCIES, + ), + 3: NodeState( + io_state=NodeIOState.OUTDATED, + runnable_state=NodeRunnableState.READY, + ), + 4: NodeState( + io_state=NodeIOState.OUTDATED, + runnable_state=NodeRunnableState.WAITING_FOR_DEPENDENCIES, + ), + }, + ), id="element 1,2,4", ), ], @@ -301,10 +390,35 @@ def test_run_partial_computation( update_project_workbench_with_comp_tasks: Callable, fake_workbench_without_outputs: Dict[str, Any], subgraph_elements: List[int], - exp_pipeline_dag_adj_list_1st_run: Dict[str, List[str]], + exp_pipeline_adj_list: Dict[int, List[str]], + exp_node_states: Dict[int, NodeState], ): + sleepers_project: ProjectAtDB = project(workbench=fake_workbench_without_outputs) + + def _convert_to_pipeline_details( + project: ProjectAtDB, + exp_pipeline_adj_list: Dict[int, List[str]], + exp_node_states: Dict[int, NodeState], + ) -> PipelineDetails: + workbench_node_uuids = list(project.workbench.keys()) + converted_adj_list: Dict[NodeID, Dict[NodeID, List[NodeID]]] = {} + for node_key, next_nodes in exp_pipeline_adj_list.items(): + converted_adj_list[NodeID(workbench_node_uuids[node_key])] = [ + NodeID(workbench_node_uuids[n]) for n in next_nodes + ] + converted_node_states: Dict[NodeID, NodeState] = { + NodeID(workbench_node_uuids[n]): s for n, s in exp_node_states.items() + } + return PipelineDetails( + adjacency_list=converted_adj_list, node_states=converted_node_states + ) + + # convert the ids to the node uuids from the project + expected_pipeline_details = _convert_to_pipeline_details( + sleepers_project, exp_pipeline_adj_list, exp_node_states + ) + # send a valid project with sleepers - sleepers_project = project(workbench=fake_workbench_without_outputs) response = _create_pipeline( client, project=sleepers_project, @@ -318,35 +432,36 @@ def test_run_partial_computation( ], ) task_out = ComputationTaskOut.parse_obj(response.json()) - - assert task_out.id == sleepers_project.uuid - assert task_out.state == RunningState.PUBLISHED - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert ( - task_out.stop_url - == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" + # check the contents is correctb + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.PUBLISHED, + exp_pipeline_details=expected_pipeline_details, ) - # convert the ids to the node uuids from the project - workbench_node_uuids = list(sleepers_project.workbench.keys()) - expected_adj_list_1st_run: Dict[NodeID, List[NodeID]] = {} - expected_adj_list_2nd_run: Dict[NodeID, List[NodeID]] = {} - for conv in [expected_adj_list_1st_run, expected_adj_list_2nd_run]: - for node_key, next_nodes in exp_pipeline_dag_adj_list_1st_run.items(): - conv[NodeID(workbench_node_uuids[node_key])] = [ - NodeID(workbench_node_uuids[n]) for n in next_nodes - ] - assert task_out.pipeline == expected_adj_list_1st_run # now wait for the computation to finish task_out = _assert_pipeline_status( client, task_out.url, user_id, sleepers_project.uuid ) - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert task_out.stop_url == None - - assert ( - task_out.state == RunningState.SUCCESS - ), f"the pipeline complete with state {task_out.state}" + expected_pipeline_details = _convert_to_pipeline_details( + sleepers_project, + exp_pipeline_adj_list, + { + n: NodeState( + io_state=NodeIOState.OK, runnable_state=NodeRunnableState.READY + ) + for n in exp_node_states + }, + ) + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.SUCCESS, + exp_pipeline_details=expected_pipeline_details, + ) # run it a second time. the tasks are all up-to-date, nothing should be run # FIXME: currently the webserver is the one updating the projects table so we need to fake this by copying the run_hash @@ -381,66 +496,20 @@ def test_run_partial_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == sleepers_project.uuid - assert task_out.state == RunningState.PUBLISHED - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert ( - task_out.stop_url - == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" - ) - assert task_out.pipeline == expected_adj_list_2nd_run - - -def _assert_computation_task_out_obj( - client: TestClient, - task_out: ComputationTaskOut, - *, - project: ProjectAtDB, - exp_task_state: RunningState, - exp_pipeline_details: PipelineDetails, -): - assert task_out.id == project.uuid - assert task_out.state == exp_task_state - assert task_out.url == f"{client.base_url}/v2/computations/{project.uuid}" - assert task_out.stop_url == ( - f"{client.base_url}/v2/computations/{project.uuid}:stop" - if exp_task_state in [RunningState.PUBLISHED, RunningState.PENDING] - else None + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.PUBLISHED, + exp_pipeline_details=expected_pipeline_details, ) - # check pipeline details contents - assert task_out.pipeline_details == exp_pipeline_details - -@pytest.fixture(scope="session") -def fake_workbench_node_states_file(mocks_dir: Path) -> Path: - file_path = mocks_dir / "fake_workbench_computational_node_states.json" - assert file_path.exists() - return file_path - - -@pytest.fixture(scope="session") -def fake_workbench_computational_pipeline_details( - fake_workbench_computational_adjacency_file: Path, - fake_workbench_node_states_file: Path, -) -> PipelineDetails: - adjacency_list = json.loads(fake_workbench_computational_adjacency_file.read_text()) - node_states = json.loads(fake_workbench_node_states_file.read_text()) - return PipelineDetails.parse_obj( - {"adjacency_list": adjacency_list, "node_states": node_states} + # now wait for the computation to finish + task_out = _assert_pipeline_status( + client, task_out.url, user_id, sleepers_project.uuid ) -@pytest.fixture(scope="session") -def fake_workbench_computational_pipeline_details_completed( - fake_workbench_computational_pipeline_details: PipelineDetails, -) -> PipelineDetails: - completed_pipeline_details = deepcopy(fake_workbench_computational_pipeline_details) - for node_state in completed_pipeline_details.node_states.values(): - node_state.io_state = NodeIOState.OK - node_state.runnable_state = NodeRunnableState.READY - return completed_pipeline_details - - def test_run_computation( client: TestClient, user_id: PositiveInt, From 7c453847ad49db7e3ffa9a5f32ada1c7c42f36c8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:41:45 +0100 Subject: [PATCH 012/200] linter --- .../director-v2/src/simcore_service_director_v2/utils/dags.py | 1 - 1 file changed, 1 deletion(-) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index a68a8020001..fe2bd9a00d7 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -1,5 +1,4 @@ import logging -from enum import Enum from typing import Any, Dict, List, Set import networkx as nx From 187b9c7a1e66dad9dbcff82253600339ecc0addb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 11:53:14 +0100 Subject: [PATCH 013/200] fixed testing abort pipeline --- .../api/routes/computations.py | 19 +++++++++++++------ .../tests/integration/test_computation_api.py | 14 ++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index b8d2f30ba43..651b150f6fc 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -285,22 +285,29 @@ async def stop_computation_project( pipeline_at_db: CompPipelineAtDB = await computation_pipelines.get_pipeline( project_id ) - # check if current state allow to stop the computation - comp_tasks: List[CompTaskAtDB] = await computation_tasks.get_comp_tasks( - project_id + pipeline_dag: nx.DiGraph = nx.from_dict_of_lists( + pipeline_at_db.dag_adjacency_list, create_using=nx.DiGraph ) + # get the project task states + tasks: List[CompTaskAtDB] = await computation_tasks.get_all_tasks(project_id) + # create the complete DAG graph + complete_dag = create_complete_dag_from_tasks(tasks) + # filter the tasks by the effective pipeline + filtered_tasks = [ + t for t in tasks if str(t.node_id) in list(pipeline_dag.nodes()) + ] pipeline_state = get_pipeline_state_from_task_states( - comp_tasks, celery_client.settings.publication_timeout + filtered_tasks, celery_client.settings.publication_timeout ) if is_pipeline_running(pipeline_state): await _abort_pipeline_tasks( - project, comp_tasks, computation_tasks, celery_client + project, filtered_tasks, computation_tasks, celery_client ) return ComputationTaskOut( id=project_id, state=pipeline_state, - pipeline=pipeline_at_db.dag_adjacency_list, + pipeline_details=await compute_pipeline_details(complete_dag, pipeline_dag), url=f"{str(request.url).rstrip(':stop')}", ) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 098f623e597..0d5f9cfd1f0 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -600,6 +600,7 @@ def test_abort_computation( user_id: PositiveInt, project: Callable, fake_workbench_without_outputs: Dict[str, Any], + fake_workbench_computational_pipeline_details: PipelineDetails, ): sleepers_project = project(workbench=fake_workbench_without_outputs) # send a valid project with sleepers @@ -612,12 +613,13 @@ def test_abort_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == sleepers_project.uuid - assert task_out.state == RunningState.PUBLISHED - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert ( - task_out.stop_url - == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" + # check the contents is correctb + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.PUBLISHED, + exp_pipeline_details=fake_workbench_computational_pipeline_details, ) # wait until the pipeline is started From 17697cab2455ae3cc8569108bbc92266aafa1f80 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:19:51 +0100 Subject: [PATCH 014/200] fixes test for update and deletion of computations --- .../tests/integration/test_computation_api.py | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 0d5f9cfd1f0..7baf6318d1e 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -667,6 +667,7 @@ def test_update_and_delete_computation( user_id: PositiveInt, project: Callable, fake_workbench_without_outputs: Dict[str, Any], + fake_workbench_computational_pipeline_details: PipelineDetails, ): sleepers_project = project(workbench=fake_workbench_without_outputs) # send a valid project with sleepers @@ -679,9 +680,14 @@ def test_update_and_delete_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == sleepers_project.uuid - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert not task_out.stop_url + # check the contents is correctb + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.NOT_STARTED, + exp_pipeline_details=fake_workbench_computational_pipeline_details, + ) # update the pipeline response = _create_pipeline( @@ -693,9 +699,14 @@ def test_update_and_delete_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == sleepers_project.uuid - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert not task_out.stop_url + # check the contents is correctb + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.NOT_STARTED, + exp_pipeline_details=fake_workbench_computational_pipeline_details, + ) # update the pipeline response = _create_pipeline( @@ -707,9 +718,14 @@ def test_update_and_delete_computation( ) task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == sleepers_project.uuid - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert not task_out.stop_url + # check the contents is correctb + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.NOT_STARTED, + exp_pipeline_details=fake_workbench_computational_pipeline_details, + ) # start it now response = _create_pipeline( @@ -720,12 +736,13 @@ def test_update_and_delete_computation( expected_response_status_code=status.HTTP_201_CREATED, ) task_out = ComputationTaskOut.parse_obj(response.json()) - assert task_out.id == sleepers_project.uuid - assert task_out.state == RunningState.PUBLISHED - assert task_out.url == f"{client.base_url}/v2/computations/{sleepers_project.uuid}" - assert ( - task_out.stop_url - == f"{client.base_url}/v2/computations/{sleepers_project.uuid}:stop" + # check the contents is correctb + _assert_computation_task_out_obj( + client, + task_out, + project=sleepers_project, + exp_task_state=RunningState.PUBLISHED, + exp_pipeline_details=fake_workbench_computational_pipeline_details, ) # wait until the pipeline is started From e6b9eaee6e974c3369de721dbd11d882b7307984 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:20:57 +0100 Subject: [PATCH 015/200] updated schema --- services/director-v2/openapi.json | 97 +++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/services/director-v2/openapi.json b/services/director-v2/openapi.json index 1aac93e6b3f..deb6b30920b 100644 --- a/services/director-v2/openapi.json +++ b/services/director-v2/openapi.json @@ -558,7 +558,9 @@ "description": "Successful Response", "content": { "application/json": { - "schema": {} + "schema": { + "$ref": "#/components/schemas/ComputationTaskOut" + } } } }, @@ -758,7 +760,7 @@ "required": [ "id", "state", - "pipeline", + "pipeline_details", "url" ], "type": "object", @@ -782,17 +784,14 @@ "type": "string", "description": "the result of the computational task" }, - "pipeline": { - "title": "Pipeline", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" + "pipeline_details": { + "title": "Pipeline Details", + "allOf": [ + { + "$ref": "#/components/schemas/PipelineDetails" } - }, - "description": "the corresponding pipeline in terms of node uuids" + ], + "description": "the details of the generated pipeline" }, "url": { "title": "Url", @@ -875,6 +874,15 @@ } } }, + "NodeIOState": { + "title": "NodeIOState", + "enum": [ + "OK", + "OUTDATED" + ], + "type": "string", + "description": "An enumeration." + }, "NodeRequirement": { "title": "NodeRequirement", "enum": [ @@ -885,6 +893,71 @@ "type": "string", "description": "An enumeration." }, + "NodeRunnableState": { + "title": "NodeRunnableState", + "enum": [ + "WAITING_FOR_DEPENDENCIES", + "READY" + ], + "type": "string", + "description": "An enumeration." + }, + "NodeState": { + "title": "NodeState", + "required": [ + "io_state", + "runnable_state" + ], + "type": "object", + "properties": { + "io_state": { + "allOf": [ + { + "$ref": "#/components/schemas/NodeIOState" + } + ], + "description": "represents the state of the inputs outputs" + }, + "runnable_state": { + "allOf": [ + { + "$ref": "#/components/schemas/NodeRunnableState" + } + ], + "description": "represent the runnable state of the node" + } + } + }, + "PipelineDetails": { + "title": "PipelineDetails", + "required": [ + "adjacency_list", + "node_states" + ], + "type": "object", + "properties": { + "adjacency_list": { + "title": "Adjacency List", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "description": "The adjacency list in terms of {NodeID: [successor NodeID]}" + }, + "node_states": { + "title": "Node States", + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/NodeState" + }, + "description": "The states of each of the pipeline node" + } + } + }, "RetrieveDataIn": { "title": "RetrieveDataIn", "required": [ From 6f274d2791ffe49d81565a8ce873111e19bdd159 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 15:18:48 +0100 Subject: [PATCH 016/200] moved pydantic models to models-library --- .../src/models_library/projects_nodes.py | 22 ++++++++++++ .../src/models_library/projects_pipeline.py | 14 ++++++++ .../models/schemas/comp_tasks.py | 34 ++----------------- .../simcore_service_director_v2/utils/dags.py | 14 ++++---- .../tests/integration/test_computation_api.py | 10 ++---- 5 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 packages/models-library/src/models_library/projects_pipeline.py diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index 8c8512652e6..907cddff315 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -3,6 +3,7 @@ """ from copy import deepcopy +from enum import Enum, unique from typing import Dict, List, Optional, Union from pydantic import ( @@ -160,3 +161,24 @@ def schema_extra(schema, _model: "Node"): if prop_name in schema.get("properties", {}): was = deepcopy(schema["properties"][prop_name]) schema["properties"][prop_name] = {"anyOf": [{"type": "null"}, was]} + + +@unique +class NodeIOState(str, Enum): + OK = "OK" + OUTDATED = "OUTDATED" + + +@unique +class NodeRunnableState(str, Enum): + WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" + READY = "READY" + + +class NodeState(BaseModel): + io_state: NodeIOState = Field( + ..., description="represents the state of the inputs outputs" + ) + runnable_state: NodeRunnableState = Field( + ..., description="represent the runnable state of the node" + ) diff --git a/packages/models-library/src/models_library/projects_pipeline.py b/packages/models-library/src/models_library/projects_pipeline.py new file mode 100644 index 00000000000..999e260b2b2 --- /dev/null +++ b/packages/models-library/src/models_library/projects_pipeline.py @@ -0,0 +1,14 @@ +from typing import Dict, List + +from pydantic import BaseModel, Field + +from .projects_nodes import NodeID, NodeState + + +class PipelineDetails(BaseModel): + adjacency_list: Dict[NodeID, List[NodeID]] = Field( + ..., description="The adjacency list in terms of {NodeID: [successor NodeID]}" + ) + node_states: Dict[NodeID, NodeState] = Field( + ..., description="The states of each of the pipeline node" + ) diff --git a/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py index c3abadcbf23..9c99819ec8f 100644 --- a/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py +++ b/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py @@ -1,9 +1,9 @@ -from enum import Enum, unique -from typing import Dict, List, Optional +from typing import List, Optional from uuid import UUID from models_library.projects import ProjectID from models_library.projects_nodes import NodeID +from models_library.projects_pipeline import PipelineDetails from models_library.projects_state import RunningState from pydantic import AnyHttpUrl, BaseModel, Field @@ -12,36 +12,6 @@ TaskID = UUID -@unique -class NodeIOState(str, Enum): - OK = "OK" - OUTDATED = "OUTDATED" - - -@unique -class NodeRunnableState(str, Enum): - WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" - READY = "READY" - - -class NodeState(BaseModel): - io_state: NodeIOState = Field( - ..., description="represents the state of the inputs outputs" - ) - runnable_state: NodeRunnableState = Field( - ..., description="represent the runnable state of the node" - ) - - -class PipelineDetails(BaseModel): - adjacency_list: Dict[NodeID, List[NodeID]] = Field( - ..., description="The adjacency list in terms of {NodeID: [successor NodeID]}" - ) - node_states: Dict[NodeID, NodeState] = Field( - ..., description="The states of each of the pipeline node" - ) - - class ComputationTask(BaseModel): id: TaskID = Field(..., description="the id of the computation task") state: RunningState = Field(..., description="the state of the computational task") diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index fe2bd9a00d7..37df07ec110 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -3,17 +3,17 @@ import networkx as nx from models_library.projects import Workbench -from models_library.projects_nodes import NodeID -from models_library.projects_nodes_io import PortLink -from models_library.utils.nodes import compute_node_hash -from simcore_service_director_v2.models.domains.comp_tasks import CompTaskAtDB - -from ..models.schemas.comp_tasks import ( +from models_library.projects_nodes import ( + NodeID, NodeIOState, NodeRunnableState, NodeState, - PipelineDetails, ) +from models_library.projects_nodes_io import PortLink +from models_library.projects_pipeline import PipelineDetails +from models_library.utils.nodes import compute_node_hash +from simcore_service_director_v2.models.domains.comp_tasks import CompTaskAtDB + from .computations import NodeClass, to_node_class from .logging_utils import log_decorator diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 7baf6318d1e..6dd30d0369c 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -15,7 +15,9 @@ import pytest import sqlalchemy as sa +from models_library.projects_nodes import NodeIOState, NodeRunnableState, NodeState from models_library.projects_nodes_io import NodeID +from models_library.projects_pipeline import PipelineDetails from models_library.projects_state import RunningState from models_library.settings.rabbit import RabbitConfig from models_library.settings.redis import RedisConfig @@ -25,13 +27,7 @@ from simcore_postgres_database.models.projects import ProjectType, projects from simcore_postgres_database.models.users import UserRole, UserStatus, users from simcore_service_director_v2.models.domains.projects import ProjectAtDB -from simcore_service_director_v2.models.schemas.comp_tasks import ( - ComputationTaskOut, - NodeIOState, - NodeRunnableState, - NodeState, - PipelineDetails, -) +from simcore_service_director_v2.models.schemas.comp_tasks import ComputationTaskOut from sqlalchemy import literal_column from starlette import status from starlette.responses import Response From 353dc94da2e13156ade003e77f18e5a24b0a9bbc Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 15:28:38 +0100 Subject: [PATCH 017/200] moved ComputationTask model to models-library --- .../src/models_library/projects_pipeline.py | 18 +++++++++++++++++- .../models/schemas/comp_tasks.py | 17 +---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/models-library/src/models_library/projects_pipeline.py b/packages/models-library/src/models_library/projects_pipeline.py index 999e260b2b2..a058c988eed 100644 --- a/packages/models-library/src/models_library/projects_pipeline.py +++ b/packages/models-library/src/models_library/projects_pipeline.py @@ -1,8 +1,10 @@ -from typing import Dict, List +from typing import Dict, List, Optional +from uuid import UUID from pydantic import BaseModel, Field from .projects_nodes import NodeID, NodeState +from .projects_state import RunningState class PipelineDetails(BaseModel): @@ -12,3 +14,17 @@ class PipelineDetails(BaseModel): node_states: Dict[NodeID, NodeState] = Field( ..., description="The states of each of the pipeline node" ) + + +TaskID = UUID + + +class ComputationTask(BaseModel): + id: TaskID = Field(..., description="the id of the computation task") + state: RunningState = Field(..., description="the state of the computational task") + result: Optional[str] = Field( + None, description="the result of the computational task" + ) + pipeline_details: PipelineDetails = Field( + ..., description="the details of the generated pipeline" + ) diff --git a/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py index 9c99819ec8f..c0b55119a38 100644 --- a/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py +++ b/services/director-v2/src/simcore_service_director_v2/models/schemas/comp_tasks.py @@ -1,27 +1,12 @@ from typing import List, Optional -from uuid import UUID from models_library.projects import ProjectID from models_library.projects_nodes import NodeID -from models_library.projects_pipeline import PipelineDetails -from models_library.projects_state import RunningState +from models_library.projects_pipeline import ComputationTask from pydantic import AnyHttpUrl, BaseModel, Field from ..schemas.constants import UserID -TaskID = UUID - - -class ComputationTask(BaseModel): - id: TaskID = Field(..., description="the id of the computation task") - state: RunningState = Field(..., description="the state of the computational task") - result: Optional[str] = Field( - None, description="the result of the computational task" - ) - pipeline_details: PipelineDetails = Field( - ..., description="the details of the generated pipeline" - ) - class ComputationTaskOut(ComputationTask): url: AnyHttpUrl = Field( From 3e91f9f86daf86e47293a4adb6704a5e27dfa0fb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 15:43:54 +0100 Subject: [PATCH 018/200] use pydantic models refactor --- .../simcore_service_webserver/director_v2.py | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director_v2.py b/services/web/server/src/simcore_service_webserver/director_v2.py index 6a26ebe604b..c7bf0698f21 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2.py +++ b/services/web/server/src/simcore_service_webserver/director_v2.py @@ -3,13 +3,15 @@ from uuid import UUID from aiohttp import ClientTimeout, web -from models_library.projects_state import RunningState from pydantic.types import PositiveInt +from yarl import URL + +from models_library.projects_pipeline import ComputationTask +from models_library.projects_state import RunningState from servicelib.application_setup import ModuleCategory, app_module_setup from servicelib.logging_utils import log_decorator from servicelib.rest_responses import wrap_as_envelope from servicelib.rest_routing import iter_path_operations, map_handlers_with_operations -from yarl import URL from .director_v2_settings import ( CONFIG_SECTION_NAME, @@ -39,6 +41,7 @@ async def _request_director_v2( app: web.Application, method: str, url: URL, + expected_status: web.HTTPSuccessful = web.HTTPOk, headers: Optional[Dict[str, str]] = None, data: Optional[bytes] = None, **kwargs, @@ -48,7 +51,7 @@ async def _request_director_v2( async with session.request( method, url, headers=headers, json=data, **kwargs ) as resp: - if resp.status >= 400: + if resp.status != expected_status.status_code: # in some cases the director answers with plain text payload: Union[Dict, str] = ( await resp.json() @@ -69,15 +72,15 @@ async def _request_director_v2( @log_decorator(logger=log) async def create_or_update_pipeline( app: web.Application, user_id: PositiveInt, project_id: UUID -) -> Optional[Dict[str, Any]]: +) -> Dict[str, Any]: director2_settings: Directorv2Settings = get_settings(app) backend_url = URL(f"{director2_settings.endpoint}/computations") body = {"user_id": user_id, "project_id": str(project_id)} # request to director-v2 try: - computation_task_out, _ = await _request_director_v2( - app, "POST", backend_url, data=body + computation_task_out = await _request_director_v2( + app, "POST", backend_url, expected_status=web.HTTPCreated, data=body ) return computation_task_out @@ -97,7 +100,11 @@ async def get_pipeline_state( # request to director-v2 try: - computation_task_out, _ = await _request_director_v2(app, "GET", backend_url) + computation_task_out_dict = await _request_director_v2( + app, "GET", backend_url, expected_status=web.HTTPAccepted + ) + task_out = ComputationTask.construct(**computation_task_out_dict) + return task_out.state except _DirectorServiceError: log.warning( "getting pipeline state for project %s failed. state is then %s", @@ -106,8 +113,6 @@ async def get_pipeline_state( ) return RunningState.UNKNOWN - return RunningState(computation_task_out["state"]) - @log_decorator(logger=log) async def delete_pipeline( @@ -119,7 +124,9 @@ async def delete_pipeline( body = {"user_id": user_id, "force": True} # request to director-v2 - await _request_director_v2(app, "DELETE", backend_url, data=body) + await _request_director_v2( + app, "DELETE", backend_url, expected_status=web.HTTPNoContent, data=body + ) @login_required @@ -150,12 +157,14 @@ async def start_pipeline(request: web.Request) -> web.Response: # request to director-v2 try: - computation_task_out, resp_status = await _request_director_v2( - request.app, "POST", backend_url, data=body + computation_task_out = await _request_director_v2( + request.app, "POST", backend_url, expected_status=web.HTTPCreated, data=body ) data = {"pipeline_id": computation_task_out["id"]} - return web.json_response(data=wrap_as_envelope(data=data), status=resp_status) + return web.json_response( + data=wrap_as_envelope(data=data), status=web.HTTPCreated.status_code + ) except _DirectorServiceError as exc: return web.json_response( data=wrap_as_envelope(error=exc.reason), status=exc.status @@ -178,15 +187,14 @@ async def stop_pipeline(request: web.Request) -> web.Response: # request to director-v2 try: - _, resp_status = await _request_director_v2( - request.app, "POST", backend_url, data=body + await _request_director_v2( + request.app, + "POST", + backend_url, + expected_status=web.HTTPAccepted, + data=body, ) data = {} - # director responds with a 202 - if resp_status != web.HTTPAccepted.status_code: - raise _DirectorServiceError( - resp_status, "Unexpected response from director-v2" - ) return web.json_response( data=wrap_as_envelope(data=data), status=web.HTTPNoContent.status_code ) From 80eb140cfe009c1c158023ab27577cc374988308 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 15:57:45 +0100 Subject: [PATCH 019/200] typo --- .../web/server/src/simcore_service_webserver/director_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/director_v2.py b/services/web/server/src/simcore_service_webserver/director_v2.py index c7bf0698f21..ad3abfb32d1 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2.py +++ b/services/web/server/src/simcore_service_webserver/director_v2.py @@ -61,7 +61,7 @@ async def _request_director_v2( raise _DirectorServiceError(resp.status, payload) payload: Dict = await resp.json() - return (payload, resp.status) + return payload except TimeoutError as err: raise web.HTTPServiceUnavailable( From 4868c1d1915b7f03385240b0d7a2d4d494aab490 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 16:11:25 +0100 Subject: [PATCH 020/200] modified project json schema --- .../schemas/project-v0.0.1-converted.yaml | 21 +- api/specs/common/schemas/project-v0.0.1.json | 22 +- .../api/v0/schemas/project-v0.0.1.json | 23 +- .../api/v0/openapi.yaml | 21 +- .../api/v0/schemas/project-v0.0.1.json | 23 +- .../api/v0/openapi.yaml | 210 +++++++++++++----- .../api/v0/schemas/project-v0.0.1.json | 23 +- 7 files changed, 247 insertions(+), 96 deletions(-) diff --git a/api/specs/common/schemas/project-v0.0.1-converted.yaml b/api/specs/common/schemas/project-v0.0.1-converted.yaml index e9264f0a57c..82fdd26b3a7 100644 --- a/api/specs/common/schemas/project-v0.0.1-converted.yaml +++ b/api/specs/common/schemas/project-v0.0.1-converted.yaml @@ -285,13 +285,22 @@ properties: example: - '15' deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index 98e3cf00c0e..e67203108d2 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -396,14 +396,24 @@ }, "deprecated": true }, - "connection_state": { - "title": "Status", - "description": "the node's connection state", - "default": "CHANGED", + "io_state": { + "title": "IO State", + "description": "the node's Inputs/Outputs state", + "default": "OUTDATED", "type": "string", "enum": [ - "CHANGED", - "CURRENT" + "OUTDATED", + "OK" + ] + }, + "runnable_state": { + "title": "Runnable State", + "description": "the node's runnable state", + "default": "READY", + "type": "string", + "enum": [ + "READY", + "WAITING_FOR_DEPENDENCIES" ] }, "state": { diff --git a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json index 75fe94ec0d4..e67203108d2 100644 --- a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json +++ b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json @@ -396,13 +396,24 @@ }, "deprecated": true }, - "connection_state": { - "title": "Status", - "description": "the node's connection state", - "default": "CHANGED", + "io_state": { + "title": "IO State", + "description": "the node's Inputs/Outputs state", + "default": "OUTDATED", + "type": "string", + "enum": [ + "OUTDATED", + "OK" + ] + }, + "runnable_state": { + "title": "Runnable State", + "description": "the node's runnable state", + "default": "READY", + "type": "string", "enum": [ - "CHANGED", - "CURRENT" + "READY", + "WAITING_FOR_DEPENDENCIES" ] }, "state": { diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index 274f905145b..298a92b9b3b 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -998,13 +998,22 @@ components: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state diff --git a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json index 75fe94ec0d4..e67203108d2 100644 --- a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json +++ b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json @@ -396,13 +396,24 @@ }, "deprecated": true }, - "connection_state": { - "title": "Status", - "description": "the node's connection state", - "default": "CHANGED", + "io_state": { + "title": "IO State", + "description": "the node's Inputs/Outputs state", + "default": "OUTDATED", + "type": "string", + "enum": [ + "OUTDATED", + "OK" + ] + }, + "runnable_state": { + "title": "Runnable State", + "description": "the node's runnable state", + "default": "READY", + "type": "string", "enum": [ - "CHANGED", - "CURRENT" + "READY", + "WAITING_FOR_DEPENDENCIES" ] }, "state": { 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 6136d019a4f..39a6766ff17 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 @@ -6374,13 +6374,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -6956,13 +6965,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string enum: - - CHANGED - - CURRENT + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string + enum: + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -7418,13 +7436,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string enum: - - CHANGED - - CURRENT + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string + enum: + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -7998,13 +8025,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -8584,13 +8620,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -9161,13 +9206,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -9623,13 +9677,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string enum: - - CHANGED - - CURRENT + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string + enum: + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -10225,13 +10288,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string enum: - - CHANGED - - CURRENT + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string + enum: + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -11955,13 +12027,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state @@ -12534,13 +12615,22 @@ paths: example: - "15" deprecated: true - connection_state: - title: Status - description: the node's connection state - default: CHANGED + io_state: + title: IO State + description: the node's Inputs/Outputs state + default: OUTDATED + type: string + enum: + - OUTDATED + - OK + runnable_state: + title: Runnable State + description: the node's runnable state + default: READY + type: string enum: - - CHANGED - - CURRENT + - READY + - WAITING_FOR_DEPENDENCIES state: title: RunningState description: the node's running state diff --git a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json index 75fe94ec0d4..e67203108d2 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json +++ b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json @@ -396,13 +396,24 @@ }, "deprecated": true }, - "connection_state": { - "title": "Status", - "description": "the node's connection state", - "default": "CHANGED", + "io_state": { + "title": "IO State", + "description": "the node's Inputs/Outputs state", + "default": "OUTDATED", + "type": "string", + "enum": [ + "OUTDATED", + "OK" + ] + }, + "runnable_state": { + "title": "Runnable State", + "description": "the node's runnable state", + "default": "READY", + "type": "string", "enum": [ - "CHANGED", - "CURRENT" + "READY", + "WAITING_FOR_DEPENDENCIES" ] }, "state": { From 3eeae91233eaeb7540c9ccbd67aa23c2ad2987fb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 17:10:40 +0100 Subject: [PATCH 021/200] bugfix: issue webserver wrongly retrieves templates running state, creating log pollution #2108 --- .../projects/projects_api.py | 28 ++++++++--- .../projects/projects_handlers.py | 49 ++++++++++++++----- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index b9c7e9ce500..1e8c31a9a5e 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -80,15 +80,19 @@ async def get_project_for_user( db = app[APP_PROJECT_DBAPI] project: Dict = None + is_template = False if include_templates: project = await db.get_template_project(project_uuid) + is_template = bool(project) if not project: project = await db.get_user_project(user_id, project_uuid) # adds state if it is not a template if include_state: - project["state"] = await get_project_state_for_user(user_id, project_uuid, app) + project["state"] = await get_project_state_for_user( + user_id, project_uuid, is_template, app + ) # TODO: how to handle when database has an invalid project schema??? # Notice that db model does not include a check on project schema. @@ -310,7 +314,7 @@ async def update_project_node_state( db = app[APP_PROJECT_DBAPI] updated_project = await db.update_user_project(project, user_id, project_id) updated_project["state"] = await get_project_state_for_user( - user_id, project_id, app + user_id=user_id, project_uuid=project_id, is_template=False, app=app ) return updated_project @@ -333,7 +337,7 @@ async def update_project_node_progress( db = app[APP_PROJECT_DBAPI] updated_project = await db.update_user_project(project, user_id, project_id) updated_project["state"] = await get_project_state_for_user( - user_id, project_id, app + user_id=user_id, project_uuid=project_id, is_template=False, app=app ) return updated_project @@ -379,7 +383,7 @@ async def update_project_node_outputs( db = app[APP_PROJECT_DBAPI] updated_project = await db.update_user_project(project, user_id, project_id) updated_project["state"] = await get_project_state_for_user( - user_id, project_id, app + user_id=user_id, project_uuid=project_id, is_template=False, app=app ) return updated_project, changed_keys @@ -499,13 +503,19 @@ async def _get_project_lock_state( async def _get_project_running_state( - user_id: PositiveInt, project_uuid: str, app: web.Application + user_id: PositiveInt, project_uuid: str, is_template: bool, app: web.Application ) -> ProjectRunningState: - pipeline_state: RunningState = await get_pipeline_state(app, user_id, project_uuid) + pipeline_state: RunningState = ( + RunningState.UNKNOWN + if is_template + else await get_pipeline_state(app, user_id, project_uuid) + ) return ProjectRunningState(value=pipeline_state) -async def get_project_state_for_user(user_id, project_uuid, app) -> Dict: +async def get_project_state_for_user( + user_id: int, project_uuid: str, is_template: bool, app +) -> Dict: """ Returns state of a project with respect to a given user E.g. @@ -517,7 +527,9 @@ async def get_project_state_for_user(user_id, project_uuid, app) -> Dict: might require a mock for this function to work properly """ lock_state = await _get_project_lock_state(user_id, project_uuid, app) - running_state = await _get_project_running_state(user_id, project_uuid, app) + running_state = await _get_project_running_state( + user_id, project_uuid, is_template, app + ) return ProjectState( locked=lock_state, state=running_state, diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py index 1de52de5b31..782af58b662 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py @@ -3,7 +3,7 @@ """ import json import logging -from typing import Dict, List, Optional, Set +from typing import Any, Dict, List, Optional, Set import aioredlock from aiohttp import web @@ -100,7 +100,10 @@ async def create_projects(request: web.Request): # Appends state project["state"] = await projects_api.get_project_state_for_user( - user_id, project["uuid"], request.app + user_id=user_id, + project_uuid=project["uuid"], + is_template=as_template is not None, + app=request.app, ) except ValidationError as exc: @@ -125,12 +128,30 @@ async def list_projects(request: web.Request): db = request.config_dict[APP_PROJECT_DBAPI] # TODO: improve dbapi to list project + async def add_prj_state(project: Dict[str, Any], is_template: bool) -> None: + project["state"] = await projects_api.get_project_state_for_user( + user_id=user_id, + project_uuid=project["uuid"], + is_template=is_template, + app=request.app, + ) + projects_list = [] if ptype in ("template", "all"): - projects_list += await db.load_template_projects(user_id=user_id) + template_projects = await db.load_template_projects(user_id=user_id) + await logged_gather( + *[add_prj_state(prj, is_template=True) for prj in template_projects], + reraise=True, + ) + projects_list += template_projects if ptype in ("user", "all"): # standard only (notice that templates will only) - projects_list += await db.load_user_projects(user_id=user_id) + user_projects = await db.load_user_projects(user_id=user_id) + await logged_gather( + *[add_prj_state(prj, is_template=False) for prj in user_projects], + reraise=True, + ) + projects_list += user_projects start = int(request.query.get("start", 0)) count = int(request.query.get("count", len(projects_list))) @@ -144,11 +165,8 @@ async def list_projects(request: web.Request): ) # validate response - async def validate_project(prj: Dict) -> Optional[Dict]: + async def validate_project(prj: Dict[str, Any]) -> Dict[str, Any]: try: - prj["state"] = await projects_api.get_project_state_for_user( - user_id, project_uuid=prj["uuid"], app=request.app - ) projects_api.validate_project(request.app, prj) if await project_uses_available_services(prj, user_available_services): return prj @@ -259,7 +277,10 @@ async def replace_project(request: web.Request): await director_v2.create_or_update_pipeline(request.app, user_id, project_uuid) # Appends state new_project["state"] = await projects_api.get_project_state_for_user( - user_id, project_uuid, request.app + user_id=user_id, + project_uuid=project_uuid, + is_template=False, + app=request.app, ) except ValidationError as exc: @@ -369,7 +390,10 @@ async def try_add_project() -> Optional[Set[int]]: # notify users that project is now locked project["state"] = await projects_api.get_project_state_for_user( - user_id, project_uuid, request.app + user_id=user_id, + project_uuid=project_uuid, + is_template=False, + app=request.app, ) await projects_api.notify_project_state_update(request.app, project) @@ -417,7 +441,10 @@ async def _close_project_task() -> None: await rt.remove("project_id") # ensure we notify the user whatever happens, the GC should take care of dangling services in case of issue project["state"] = await projects_api.get_project_state_for_user( - user_id, project_uuid, request.app + user_id=user_id, + project_uuid=project_uuid, + is_template=False, + app=request.app, ) await projects_api.notify_project_state_update(request.app, project) From 4ad6c9da282f66e42725902e6dfb9bda56a55ea1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 17:34:11 +0100 Subject: [PATCH 022/200] refactoring --- .../projects/projects_api.py | 29 ++++++----- .../projects/projects_handlers.py | 48 +++++++++---------- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 1e8c31a9a5e..d90d722aadc 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -90,9 +90,7 @@ async def get_project_for_user( # adds state if it is not a template if include_state: - project["state"] = await get_project_state_for_user( - user_id, project_uuid, is_template, app - ) + project = await add_project_states_for_user(user_id, project, is_template, app) # TODO: how to handle when database has an invalid project schema??? # Notice that db model does not include a check on project schema. @@ -313,8 +311,8 @@ async def update_project_node_state( project["workbench"][node_id]["progress"] = 100 db = app[APP_PROJECT_DBAPI] updated_project = await db.update_user_project(project, user_id, project_id) - updated_project["state"] = await get_project_state_for_user( - user_id=user_id, project_uuid=project_id, is_template=False, app=app + updated_project = await add_project_states_for_user( + user_id=user_id, project=updated_project, is_template=False, app=app ) return updated_project @@ -336,8 +334,8 @@ async def update_project_node_progress( project["workbench"][node_id]["progress"] = int(100.0 * float(progress) + 0.5) db = app[APP_PROJECT_DBAPI] updated_project = await db.update_user_project(project, user_id, project_id) - updated_project["state"] = await get_project_state_for_user( - user_id=user_id, project_uuid=project_id, is_template=False, app=app + updated_project = await add_project_states_for_user( + user_id=user_id, project=updated_project, is_template=False, app=app ) return updated_project @@ -382,8 +380,8 @@ async def update_project_node_outputs( db = app[APP_PROJECT_DBAPI] updated_project = await db.update_user_project(project, user_id, project_id) - updated_project["state"] = await get_project_state_for_user( - user_id=user_id, project_uuid=project_id, is_template=False, app=app + updated_project = await add_project_states_for_user( + user_id=user_id, project=updated_project, is_template=False, app=app ) return updated_project, changed_keys @@ -514,8 +512,8 @@ async def _get_project_running_state( async def get_project_state_for_user( - user_id: int, project_uuid: str, is_template: bool, app -) -> Dict: + user_id: int, project_uuid: str, is_template: bool, app: web.Application +) -> Dict[str, Any]: """ Returns state of a project with respect to a given user E.g. @@ -534,3 +532,12 @@ async def get_project_state_for_user( locked=lock_state, state=running_state, ).dict(by_alias=True, exclude_unset=True) + + +async def add_project_states_for_user( + user_id: int, project: Dict[str, Any], is_template: bool, app: web.Application +) -> Dict[str, Any]: + project["state"] = await get_project_state_for_user( + user_id, project["uuid"], is_template, app + ) + return project diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py index 782af58b662..f89fa685b7b 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py @@ -99,9 +99,9 @@ async def create_projects(request: web.Request): ) # Appends state - project["state"] = await projects_api.get_project_state_for_user( + project = await projects_api.add_project_states_for_user( user_id=user_id, - project_uuid=project["uuid"], + project=project, is_template=as_template is not None, app=request.app, ) @@ -128,29 +128,29 @@ async def list_projects(request: web.Request): db = request.config_dict[APP_PROJECT_DBAPI] # TODO: improve dbapi to list project - async def add_prj_state(project: Dict[str, Any], is_template: bool) -> None: - project["state"] = await projects_api.get_project_state_for_user( - user_id=user_id, - project_uuid=project["uuid"], - is_template=is_template, - app=request.app, + async def set_all_project_states(projects: List[Dict[str, Any]], is_template: bool): + await logged_gather( + *[ + projects_api.add_project_states_for_user( + user_id=user_id, + project=prj, + is_template=is_template, + app=request.app, + ) + for prj in projects + ], + reraise=True, ) projects_list = [] if ptype in ("template", "all"): template_projects = await db.load_template_projects(user_id=user_id) - await logged_gather( - *[add_prj_state(prj, is_template=True) for prj in template_projects], - reraise=True, - ) + await set_all_project_states(template_projects, False) projects_list += template_projects if ptype in ("user", "all"): # standard only (notice that templates will only) user_projects = await db.load_user_projects(user_id=user_id) - await logged_gather( - *[add_prj_state(prj, is_template=False) for prj in user_projects], - reraise=True, - ) + await set_all_project_states(user_projects, True) projects_list += user_projects start = int(request.query.get("start", 0)) @@ -276,9 +276,9 @@ async def replace_project(request: web.Request): ) await director_v2.create_or_update_pipeline(request.app, user_id, project_uuid) # Appends state - new_project["state"] = await projects_api.get_project_state_for_user( + new_project = await projects_api.add_project_states_for_user( user_id=user_id, - project_uuid=project_uuid, + project=new_project, is_template=False, app=request.app, ) @@ -389,9 +389,9 @@ async def try_add_project() -> Optional[Set[int]]: await projects_api.start_project_interactive_services(request, project, user_id) # notify users that project is now locked - project["state"] = await projects_api.get_project_state_for_user( + project = await projects_api.add_project_states_for_user( user_id=user_id, - project_uuid=project_uuid, + project=project, is_template=False, app=request.app, ) @@ -421,7 +421,7 @@ async def close_project(request: web.Request) -> web.Response: include_state=False, ) # if we are the only user left we can safely remove the services - async def _close_project_task() -> None: + async def _close_project_task(project: Dict[str, Any]) -> None: try: project_opened_by_others: bool = False with managed_resource(user_id, client_session_id, request.app) as rt: @@ -440,15 +440,15 @@ async def _close_project_task() -> None: # now we can remove the lock await rt.remove("project_id") # ensure we notify the user whatever happens, the GC should take care of dangling services in case of issue - project["state"] = await projects_api.get_project_state_for_user( + project = await projects_api.add_project_states_for_user( user_id=user_id, - project_uuid=project_uuid, + project=project, is_template=False, app=request.app, ) await projects_api.notify_project_state_update(request.app, project) - fire_and_forget_task(_close_project_task()) + fire_and_forget_task(_close_project_task(project)) raise web.HTTPNoContent(content_type="application/json") except ProjectNotFoundError as exc: From 2fb975db21a21ba0207e6b60df28d9fb5395afdf Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 17:55:56 +0100 Subject: [PATCH 023/200] refactoring more --- .../simcore_service_webserver/director_v2.py | 10 ++-- .../projects/projects_api.py | 49 +++++-------------- 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director_v2.py b/services/web/server/src/simcore_service_webserver/director_v2.py index ad3abfb32d1..8c608e8b053 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2.py +++ b/services/web/server/src/simcore_service_webserver/director_v2.py @@ -7,7 +7,7 @@ from yarl import URL from models_library.projects_pipeline import ComputationTask -from models_library.projects_state import RunningState +from pydantic.types import PositiveInt from servicelib.application_setup import ModuleCategory, app_module_setup from servicelib.logging_utils import log_decorator from servicelib.rest_responses import wrap_as_envelope @@ -91,7 +91,7 @@ async def create_or_update_pipeline( @log_decorator(logger=log) async def get_pipeline_state( app: web.Application, user_id: PositiveInt, project_id: UUID -) -> RunningState: +) -> Optional[ComputationTask]: director2_settings: Directorv2Settings = get_settings(app) backend_url = URL( @@ -104,14 +104,12 @@ async def get_pipeline_state( app, "GET", backend_url, expected_status=web.HTTPAccepted ) task_out = ComputationTask.construct(**computation_task_out_dict) - return task_out.state + return task_out except _DirectorServiceError: log.warning( - "getting pipeline state for project %s failed. state is then %s", + "getting pipeline for project %s failed.", project_id, - RunningState.UNKNOWN, ) - return RunningState.UNKNOWN @log_decorator(logger=log) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index d90d722aadc..ebb5208685a 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -500,44 +500,19 @@ async def _get_project_lock_state( return ProjectLocked(value=is_locked) -async def _get_project_running_state( - user_id: PositiveInt, project_uuid: str, is_template: bool, app: web.Application -) -> ProjectRunningState: - pipeline_state: RunningState = ( - RunningState.UNKNOWN - if is_template - else await get_pipeline_state(app, user_id, project_uuid) - ) - return ProjectRunningState(value=pipeline_state) - - -async def get_project_state_for_user( - user_id: int, project_uuid: str, is_template: bool, app: web.Application -) -> Dict[str, Any]: - """ - Returns state of a project with respect to a given user - E.g. - the state is locked for user1 because user2 is working on it and - there is a locked-while-using policy in place - - WARNING: assumes project_uuid exists!! If not, call first get_project_for_user - NOTE: This adds a dependency to the socket registry sub-module. Many tests - might require a mock for this function to work properly - """ - lock_state = await _get_project_lock_state(user_id, project_uuid, app) - running_state = await _get_project_running_state( - user_id, project_uuid, is_template, app - ) - return ProjectState( - locked=lock_state, - state=running_state, - ).dict(by_alias=True, exclude_unset=True) - - async def add_project_states_for_user( user_id: int, project: Dict[str, Any], is_template: bool, app: web.Application ) -> Dict[str, Any]: - project["state"] = await get_project_state_for_user( - user_id, project["uuid"], is_template, app - ) + + lock_state = ProjectLocked(value=False) + running_state = RunningState.UNKNOWN + if not is_template: + lock_state = await _get_project_lock_state(user_id, project["uuid"], app) + computation_task = await get_pipeline_state(app, user_id, project["uuid"]) + if computation_task: + running_state = computation_task.state + + project["state"] = ProjectState( + locked=lock_state, state=ProjectRunningState(value=running_state) + ).dict(by_alias=True, exclude_unset=True) return project From c8e824ff72c32167b6a3ea59e41da03b0f79b9f6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 17:57:32 +0100 Subject: [PATCH 024/200] renamed get_pipeline_state to get_computation_task --- .../server/src/simcore_service_webserver/director_v2.py | 2 +- .../simcore_service_webserver/projects/projects_api.py | 4 ++-- .../server/tests/unit/with_dbs/fast/test_director_v2.py | 8 +++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director_v2.py b/services/web/server/src/simcore_service_webserver/director_v2.py index 8c608e8b053..564b603fead 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2.py +++ b/services/web/server/src/simcore_service_webserver/director_v2.py @@ -89,7 +89,7 @@ async def create_or_update_pipeline( @log_decorator(logger=log) -async def get_pipeline_state( +async def get_computation_task( app: web.Application, user_id: PositiveInt, project_id: UUID ) -> Optional[ComputationTask]: director2_settings: Directorv2Settings = get_settings(app) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index ebb5208685a..b1c9ba62712 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -31,7 +31,7 @@ from ..director import director_api from ..director_v2 import ( delete_pipeline, - get_pipeline_state, + get_computation_task, request_retrieve_dyn_service, ) from ..resource_manager.websocket_manager import managed_resource @@ -508,7 +508,7 @@ async def add_project_states_for_user( running_state = RunningState.UNKNOWN if not is_template: lock_state = await _get_project_lock_state(user_id, project["uuid"], app) - computation_task = await get_pipeline_state(app, user_id, project["uuid"]) + computation_task = await get_computation_task(app, user_id, project["uuid"]) if computation_task: running_state = computation_task.state diff --git a/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py b/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py index 70b70735f8c..c1b2a2fe48a 100644 --- a/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py +++ b/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py @@ -129,16 +129,14 @@ async def test_create_pipeline(client, user_id: PositiveInt, project_id: UUID): assert task_out["state"] == RunningState.NOT_STARTED -async def test_get_pipeline_state( +async def test_get_computation_task( client, user_id: PositiveInt, project_id: UUID, ): - project_running_state = await director_v2.get_pipeline_state( - client.app, user_id, project_id - ) + task_out = await director_v2.get_computation_task(client.app, user_id, project_id) - assert project_running_state == RunningState.NOT_STARTED + assert RunningState(task_out["state"]) == RunningState.NOT_STARTED async def test_delete_pipeline(client, user_id: PositiveInt, project_id: UUID): From ebc5090400dc24e555dd41113b82e3bd54df15fb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 18:07:56 +0100 Subject: [PATCH 025/200] coming along --- .../projects/projects_api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index b1c9ba62712..50615a5465c 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -27,6 +27,7 @@ from servicelib.jsonschema_validation import validate_instance from servicelib.observer import observe from servicelib.utils import fire_and_forget_task, logged_gather +from simcore_sdk.models.pipeline_models import ComputationalTask from ..director import director_api from ..director_v2 import ( @@ -508,9 +509,20 @@ async def add_project_states_for_user( running_state = RunningState.UNKNOWN if not is_template: lock_state = await _get_project_lock_state(user_id, project["uuid"], app) - computation_task = await get_computation_task(app, user_id, project["uuid"]) + computation_task: ComputationalTask = await get_computation_task( + app, user_id, project["uuid"] + ) if computation_task: + # get the running state running_state = computation_task.state + # get the nodes individual states + for ( + node_id, + node_state, + ) in computation_task.pipeline_details["node_states"].items(): + prj_node = project["workbench"].get(str(node_id)) + prj_node["io_state"] = node_state.get("io_state") + prj_node["runnable_state"] = node_state.get("runnable_state") project["state"] = ProjectState( locked=lock_state, state=ProjectRunningState(value=running_state) From cbb90a4491455591b909f511a372dab955c3d9f0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 20:38:35 +0100 Subject: [PATCH 026/200] improve logging formatting --- .../src/simcore_service_webserver/projects/projects_db.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_db.py b/services/web/server/src/simcore_service_webserver/projects/projects_db.py index bc1604981ca..fabfd051722 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_db.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_db.py @@ -11,6 +11,7 @@ from collections import deque from datetime import datetime from enum import Enum +from pprint import pformat from typing import Dict, List, Mapping, Optional, Set, Union import psycopg2.errors @@ -20,11 +21,10 @@ from aiopg.sa.connection import SAConnection from aiopg.sa.result import ResultProxy, RowProxy from change_case import ChangeCase -from sqlalchemy import literal_column -from sqlalchemy.sql import and_, select - from servicelib.application_keys import APP_DB_ENGINE_KEY from simcore_postgres_database.webserver_models import ProjectType, projects +from sqlalchemy import literal_column +from sqlalchemy.sql import and_, select from ..db_models import GroupType, groups, study_tags, user_to_groups, users from ..utils import format_datetime, now_str @@ -324,7 +324,7 @@ async def __load_projects( except ProjectInvalidRightsError: continue prj = dict(row.items()) - log.debug("found project: %s", prj) + log.debug("found project: %s", pformat(prj)) db_projects.append(prj) # NOTE: DO NOT nest _get_tags_by_project in async loop above !!! From 9c3bf42e64596397bd20243ca511ebadc956e607 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 20:54:17 +0100 Subject: [PATCH 027/200] fixed syntax --- .../schemas/project-v0.0.1-converted.yaml | 4 +- api/specs/common/schemas/project-v0.0.1.json | 4 +- .../api/v0/schemas/project-v0.0.1.json | 4 +- .../api/v0/openapi.yaml | 4 +- .../api/v0/schemas/project-v0.0.1.json | 4 +- .../api/v0/openapi.yaml | 40 +++++++++---------- .../api/v0/schemas/project-v0.0.1.json | 4 +- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/api/specs/common/schemas/project-v0.0.1-converted.yaml b/api/specs/common/schemas/project-v0.0.1-converted.yaml index 82fdd26b3a7..d7954252f8b 100644 --- a/api/specs/common/schemas/project-v0.0.1-converted.yaml +++ b/api/specs/common/schemas/project-v0.0.1-converted.yaml @@ -285,7 +285,7 @@ properties: example: - '15' deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -293,7 +293,7 @@ properties: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index e67203108d2..07c3ce894fb 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -396,7 +396,7 @@ }, "deprecated": true }, - "io_state": { + "ioState": { "title": "IO State", "description": "the node's Inputs/Outputs state", "default": "OUTDATED", @@ -406,7 +406,7 @@ "OK" ] }, - "runnable_state": { + "runnableState": { "title": "Runnable State", "description": "the node's runnable state", "default": "READY", diff --git a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json index e67203108d2..07c3ce894fb 100644 --- a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json +++ b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json @@ -396,7 +396,7 @@ }, "deprecated": true }, - "io_state": { + "ioState": { "title": "IO State", "description": "the node's Inputs/Outputs state", "default": "OUTDATED", @@ -406,7 +406,7 @@ "OK" ] }, - "runnable_state": { + "runnableState": { "title": "Runnable State", "description": "the node's runnable state", "default": "READY", diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index 298a92b9b3b..f5328111449 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -998,7 +998,7 @@ components: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -1006,7 +1006,7 @@ components: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY diff --git a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json index e67203108d2..07c3ce894fb 100644 --- a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json +++ b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json @@ -396,7 +396,7 @@ }, "deprecated": true }, - "io_state": { + "ioState": { "title": "IO State", "description": "the node's Inputs/Outputs state", "default": "OUTDATED", @@ -406,7 +406,7 @@ "OK" ] }, - "runnable_state": { + "runnableState": { "title": "Runnable State", "description": "the node's runnable state", "default": "READY", 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 39a6766ff17..730cb9ac9bd 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 @@ -6374,7 +6374,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -6382,7 +6382,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -6965,7 +6965,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -6973,7 +6973,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -7436,7 +7436,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -7444,7 +7444,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -8025,7 +8025,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -8033,7 +8033,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -8620,7 +8620,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -8628,7 +8628,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -9206,7 +9206,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -9214,7 +9214,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -9677,7 +9677,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -9685,7 +9685,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -10288,7 +10288,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -10296,7 +10296,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -12027,7 +12027,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -12035,7 +12035,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY @@ -12615,7 +12615,7 @@ paths: example: - "15" deprecated: true - io_state: + ioState: title: IO State description: the node's Inputs/Outputs state default: OUTDATED @@ -12623,7 +12623,7 @@ paths: enum: - OUTDATED - OK - runnable_state: + runnableState: title: Runnable State description: the node's runnable state default: READY diff --git a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json index e67203108d2..07c3ce894fb 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json +++ b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json @@ -396,7 +396,7 @@ }, "deprecated": true }, - "io_state": { + "ioState": { "title": "IO State", "description": "the node's Inputs/Outputs state", "default": "OUTDATED", @@ -406,7 +406,7 @@ "OK" ] }, - "runnable_state": { + "runnableState": { "title": "Runnable State", "description": "the node's runnable state", "default": "READY", From 7294bb490ee5855e36d607df1ab16f619aa73662 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 22:07:59 +0100 Subject: [PATCH 028/200] fill model --- .../client/source/class/osparc/data/model/Node.js | 8 ++++++++ .../source/class/osparc/data/model/NodeStatus.js | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 219fab2f44e..9f52b6d6ffb 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -334,6 +334,14 @@ qx.Class.define("osparc.data.model.Node", { this.getStatus().setRunningStatus(nodeData.state); } + if (nodeData.ioState) { + this.getStatus().setIoState(nodeData.ioState); + } + + if (nodeData.runnableState) { + this.getStatus().setRunnableState(nodeData.runnableState); + } + if ("progress" in nodeData) { this.getStatus().setProgress(nodeData.progress); } diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index ae84567e2e6..ad9b7e11975 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -44,6 +44,18 @@ qx.Class.define("osparc.data.model.NodeStatus", { check: ["idle", "starting", "pulling", "pending", "connecting", "ready", "failed"], nullable: true, event: "changeInteractiveStatus" + }, + + ioState: { + check: ["OUTDATED", "OK"], + nullable: true, + event: "changeIoStateStatus" + }, + + runnableState: { + check: ["READY", "WAITING_FOR_DEPENDENCIES"], + nullable: true, + event: "changeRunnableStateStatus" } } }); From 940c878bd9c54bd801a95ef49cd5c37a99161d70 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 22:08:27 +0100 Subject: [PATCH 029/200] gather calls syntax for schema --- .../projects/projects_api.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 50615a5465c..b2c97fde690 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -508,10 +508,11 @@ async def add_project_states_for_user( lock_state = ProjectLocked(value=False) running_state = RunningState.UNKNOWN if not is_template: - lock_state = await _get_project_lock_state(user_id, project["uuid"], app) - computation_task: ComputationalTask = await get_computation_task( - app, user_id, project["uuid"] + lock_state, computation_task = await logged_gather( + _get_project_lock_state(user_id, project["uuid"], app), + get_computation_task(app, user_id, project["uuid"]), ) + if computation_task: # get the running state running_state = computation_task.state @@ -519,10 +520,10 @@ async def add_project_states_for_user( for ( node_id, node_state, - ) in computation_task.pipeline_details["node_states"].items(): + ) in computation_task.pipeline_details.node_states.items(): prj_node = project["workbench"].get(str(node_id)) - prj_node["io_state"] = node_state.get("io_state") - prj_node["runnable_state"] = node_state.get("runnable_state") + prj_node["ioState"] = node_state.io_state + prj_node["runnableState"] = node_state.runnable_state project["state"] = ProjectState( locked=lock_state, state=ProjectRunningState(value=running_state) From bb824063a0143783e6d68289f0ba8da5d510604e Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 22:23:42 +0100 Subject: [PATCH 030/200] linter --- .../src/simcore_service_webserver/projects/projects_api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index b2c97fde690..17309beb5cf 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -22,12 +22,10 @@ ProjectState, RunningState, ) -from pydantic.types import PositiveInt from servicelib.application_keys import APP_JSONSCHEMA_SPECS_KEY from servicelib.jsonschema_validation import validate_instance from servicelib.observer import observe from servicelib.utils import fire_and_forget_task, logged_gather -from simcore_sdk.models.pipeline_models import ComputationalTask from ..director import director_api from ..director_v2 import ( From f4597ed93a1a018213dfb02891d1948e6af69c4c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 22:48:09 +0100 Subject: [PATCH 031/200] do normal validation as nested types are not constructed with construct --- .../web/server/src/simcore_service_webserver/director_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/director_v2.py b/services/web/server/src/simcore_service_webserver/director_v2.py index 564b603fead..1db5a61fa6c 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2.py +++ b/services/web/server/src/simcore_service_webserver/director_v2.py @@ -103,7 +103,7 @@ async def get_computation_task( computation_task_out_dict = await _request_director_v2( app, "GET", backend_url, expected_status=web.HTTPAccepted ) - task_out = ComputationTask.construct(**computation_task_out_dict) + task_out = ComputationTask.parse_obj(computation_task_out_dict) return task_out except _DirectorServiceError: log.warning( From f37d240b97ce5e9e63f19655454698457b6ec3e6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 22:54:43 +0100 Subject: [PATCH 032/200] correctly retrieve computationTasks --- .../services_api_mocks_for_aiohttp_clients.py | 80 +++++++++++++++++-- .../unit/with_dbs/fast/test_director_v2.py | 6 +- 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py index b93208ad4b6..8cf51693a01 100644 --- a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py +++ b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py @@ -1,4 +1,5 @@ import re +from pathlib import Path from typing import Dict, List import pytest @@ -9,11 +10,38 @@ # The adjacency list is defined as a dictionary with the key to the node and its list of successors FULL_PROJECT_PIPELINE_ADJACENCY: Dict[str, List[str]] = { - "node id 1": ["node id 2", "node id 3", "node id 4"], - "node id 2": ["node_id 5"], - "node id 3": ["node_id 5"], - "node id 4": ["node_id 5"], - "node id 5": [], + "62bca361-8594-48c8-875e-b8577e868aec": [ + "e0d7a1a5-0700-42c7-b033-97f72ac4a5cd", + "5284bb5b-b068-4d0e-9075-3d5d8eec5060", + "750454a8-b450-43ce-a012-40b669f7d28c", + ], + "e0d7a1a5-0700-42c7-b033-97f72ac4a5cd": ["e83a359a-1efe-41d3-83aa-a285afbfaf12"], + "5284bb5b-b068-4d0e-9075-3d5d8eec5060": ["e83a359a-1efe-41d3-83aa-a285afbfaf12"], + "750454a8-b450-43ce-a012-40b669f7d28c": ["e83a359a-1efe-41d3-83aa-a285afbfaf12"], + "e83a359a-1efe-41d3-83aa-a285afbfaf12": [], +} + +FULL_PROJECT_NODE_STATES: Dict[str, Dict[str, str]] = { + "62bca361-8594-48c8-875e-b8577e868aec": { + "io_state": "OUTDATED", + "runnable_state": "READY", + }, + "e0d7a1a5-0700-42c7-b033-97f72ac4a5cd": { + "io_state": "OUTDATED", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + }, + "5284bb5b-b068-4d0e-9075-3d5d8eec5060": { + "io_state": "OK", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + }, + "750454a8-b450-43ce-a012-40b669f7d28c": { + "io_state": "OUTDATED", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + }, + "e83a359a-1efe-41d3-83aa-a285afbfaf12": { + "io_state": "OK", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + }, } @@ -29,17 +57,53 @@ def creation_cb(url, **kwargs) -> CallbackResult: else RunningState.NOT_STARTED ) pipeline: Dict[str, List[str]] = FULL_PROJECT_PIPELINE_ADJACENCY + node_states = FULL_PROJECT_NODE_STATES if body.get("subgraph"): # create some fake adjacency list + pipeline = {} + node_states = {} for node_id in body.get("subgraph"): - pipeline[node_id] = ["some node 5334", "some node 2324"] + pipeline[node_id] = [ + "62237c33-8d6c-4709-aa92-c3cf693dd6d2", + "0bdf824f-57cb-4e38-949e-fd12c184f000", + ] + node_states[node_id] = {"io_state": "OUTDATED", "runnable_state": "READY"} + node_states["62237c33-8d6c-4709-aa92-c3cf693dd6d2"] = { + "io_state": "OUTDATED", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + } + node_states["0bdf824f-57cb-4e38-949e-fd12c184f000"] = { + "io_state": "OUTDATED", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + } return CallbackResult( status=201, payload={ "id": kwargs["json"]["project_id"], "state": state, - "pipeline": pipeline, + "pipeline_details": { + "adjacency_list": pipeline, + "node_states": node_states, + }, + }, + ) + + +def get_computation_cb(url, **kwargs) -> CallbackResult: + state = RunningState.NOT_STARTED + pipeline: Dict[str, List[str]] = FULL_PROJECT_PIPELINE_ADJACENCY + node_states = FULL_PROJECT_NODE_STATES + + return CallbackResult( + status=202, + payload={ + "id": Path(url.path).name, + "state": state, + "pipeline_details": { + "adjacency_list": pipeline, + "node_states": node_states, + }, }, ) @@ -78,7 +142,7 @@ async def director_v2_service_mock() -> aioresponses: mock.get( get_computation_pattern, status=202, - payload={"state": str(RunningState.NOT_STARTED.value)}, + callback=get_computation_cb, repeat=True, ) mock.delete(delete_computation_pattern, status=204, repeat=True) diff --git a/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py b/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py index c1b2a2fe48a..9f9c9a43d36 100644 --- a/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py +++ b/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py @@ -125,8 +125,8 @@ async def test_create_pipeline(client, user_id: PositiveInt, project_id: UUID): task_out = await director_v2.create_or_update_pipeline( client.app, user_id, project_id ) - assert "state" in task_out - assert task_out["state"] == RunningState.NOT_STARTED + + assert task_out.state == RunningState.NOT_STARTED async def test_get_computation_task( @@ -136,7 +136,7 @@ async def test_get_computation_task( ): task_out = await director_v2.get_computation_task(client.app, user_id, project_id) - assert RunningState(task_out["state"]) == RunningState.NOT_STARTED + assert task_out.state == RunningState.NOT_STARTED async def test_delete_pipeline(client, user_id: PositiveInt, project_id: UUID): From 8e9ce02f3f5f043665764150f238c4892e3edc9e Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 26 Jan 2021 23:00:53 +0100 Subject: [PATCH 033/200] this one is still dict --- .../web/server/tests/unit/with_dbs/fast/test_director_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py b/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py index 9f9c9a43d36..4e726066854 100644 --- a/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py +++ b/services/web/server/tests/unit/with_dbs/fast/test_director_v2.py @@ -126,7 +126,7 @@ async def test_create_pipeline(client, user_id: PositiveInt, project_id: UUID): client.app, user_id, project_id ) - assert task_out.state == RunningState.NOT_STARTED + assert task_out["state"] == RunningState.NOT_STARTED async def test_get_computation_task( From 5c86c4856f5a3690db3114f2d221c9d2e26c6f75 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 08:47:50 +0100 Subject: [PATCH 034/200] fix fixture --- .../with_dbs/fast/test_access_to_studies.py | 11 ++-------- .../fast/test_studies_dispatcher_routes.py | 20 ++++++------------- .../tests/unit/with_dbs/slow/test_projects.py | 6 +++--- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/fast/test_access_to_studies.py b/services/web/server/tests/unit/with_dbs/fast/test_access_to_studies.py index 36c63854e7b..3339676448b 100644 --- a/services/web/server/tests/unit/with_dbs/fast/test_access_to_studies.py +++ b/services/web/server/tests/unit/with_dbs/fast/test_access_to_studies.py @@ -214,16 +214,9 @@ def mocks_on_projects_api(mocker) -> Dict: """ All projects in this module are UNLOCKED """ - state = ProjectState( - locked=ProjectLocked( - value=False, - owner=Owner(user_id=2, first_name="Speedy", last_name="Gonzalez"), - ), - state=ProjectRunningState(value=RunningState.NOT_STARTED), - ).dict(by_alias=True, exclude_unset=True) mocker.patch( - "simcore_service_webserver.projects.projects_api.get_project_state_for_user", - return_value=future_with_result(state), + "simcore_service_webserver.projects.projects_api._get_project_lock_state", + return_value=future_with_result(ProjectLocked(value=False)), ) diff --git a/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py b/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py index 1b2d7d03a99..113054a64b6 100644 --- a/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py +++ b/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py @@ -11,11 +11,6 @@ import pytest from aiohttp import ClientResponse, ClientSession, web -from pytest_simcore.helpers.utils_assert import assert_status -from pytest_simcore.helpers.utils_login import UserRole -from pytest_simcore.helpers.utils_mock import future_with_result -from yarl import URL - from models_library.projects_state import ( Owner, ProjectLocked, @@ -23,8 +18,12 @@ ProjectState, RunningState, ) +from pytest_simcore.helpers.utils_assert import assert_status +from pytest_simcore.helpers.utils_login import UserRole +from pytest_simcore.helpers.utils_mock import future_with_result from simcore_service_webserver import catalog from simcore_service_webserver.log import setup_logging +from yarl import URL @pytest.fixture @@ -179,16 +178,9 @@ def mocks_on_projects_api(mocker): """ All projects in this module are UNLOCKED """ - state = ProjectState( - locked=ProjectLocked( - value=False, - owner=Owner(user_id=2, first_name="Speedy", last_name="Gonzalez"), - ), - state=ProjectRunningState(value=RunningState.NOT_STARTED), - ).dict(by_alias=True, exclude_unset=True) mocker.patch( - "simcore_service_webserver.projects.projects_api.get_project_state_for_user", - return_value=future_with_result(state), + "simcore_service_webserver.projects.projects_api._get_project_lock_state", + return_value=future_with_result(ProjectLocked(value=False)), ) diff --git a/services/web/server/tests/unit/with_dbs/slow/test_projects.py b/services/web/server/tests/unit/with_dbs/slow/test_projects.py index aba23262bd4..734f8fa19d9 100644 --- a/services/web/server/tests/unit/with_dbs/slow/test_projects.py +++ b/services/web/server/tests/unit/with_dbs/slow/test_projects.py @@ -138,9 +138,9 @@ def mocks_on_projects_api(mocker, logged_user) -> Dict: ), ), state=ProjectRunningState(value=RunningState.NOT_STARTED), - ).dict(by_alias=True, exclude_unset=True) + ) mocker.patch( - "simcore_service_webserver.projects.projects_api.get_project_state_for_user", + "simcore_service_webserver.projects.projects_api._get_project_lock_state", return_value=future_with_result(state), ) @@ -328,7 +328,7 @@ async def _new_project( "classifiers": [], "ui": {}, "dev": {}, - "quality": {} + "quality": {}, } if project: project_data.update(project) From d70228b5d03cac93c35b598799d4e3a135da1af0 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 09:07:28 +0100 Subject: [PATCH 035/200] add base example for model --- .../src/models_library/projects_pipeline.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/models-library/src/models_library/projects_pipeline.py b/packages/models-library/src/models_library/projects_pipeline.py index a058c988eed..b7d6e3a41dc 100644 --- a/packages/models-library/src/models_library/projects_pipeline.py +++ b/packages/models-library/src/models_library/projects_pipeline.py @@ -28,3 +28,29 @@ class ComputationTask(BaseModel): pipeline_details: PipelineDetails = Field( ..., description="the details of the generated pipeline" ) + + class Config: + schema_extra = { + "example": { + "id": "42838344-03de-4ce2-8d93-589a5dcdfd05", + "state": "PUBLISHED", + "pipeline_details": { + "adjacency_list": { + "2fb4808a-e403-4a46-b52c-892560d27862": [], + "19a40c7b-0a40-458a-92df-c77a5df7c886": [ + "2fb4808a-e403-4a46-b52c-892560d27862" + ], + }, + "node_states": { + "2fb4808a-e403-4a46-b52c-892560d27862": { + "io_state": "OUTDATED", + "runnable_state": "READY", + }, + "19a40c7b-0a40-458a-92df-c77a5df7c886": { + "io_state": "OK", + "runnable_state": "WAITING_FOR_DEPENDENCIES", + }, + }, + }, + } + } From aa0e96466224e85b258b44e99e7bab2643442026 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 09:32:16 +0100 Subject: [PATCH 036/200] added tests for empty thumbnail --- .../src/models_library/projects.py | 6 +-- .../models-library/tests/test_projects.py | 41 +++++++++++++++++++ .../tests/test_projects_pipeline.py | 13 ++++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 packages/models-library/tests/test_projects.py create mode 100644 packages/models-library/tests/test_projects_pipeline.py diff --git a/packages/models-library/src/models_library/projects.py b/packages/models-library/src/models_library/projects.py index 296716657f8..26b671d89a8 100644 --- a/packages/models-library/src/models_library/projects.py +++ b/packages/models-library/src/models_library/projects.py @@ -2,7 +2,7 @@ Models a study's project document """ from copy import deepcopy -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional from uuid import UUID from pydantic import BaseModel, EmailStr, Extra, Field, HttpUrl, validator @@ -42,7 +42,7 @@ class Project(BaseModel): ) # NOTE: str is necessary because HttpUrl will not accept and empty string and the # frontend sometimes sends this empty string, which is removed by the validator - thumbnail: Union[str, HttpUrl] = Field( + thumbnail: Optional[HttpUrl] = Field( ..., description="url of the project thumbnail", examples=["https://placeimg.com/171/96/tech/grayscale/?0.jpg"], @@ -94,8 +94,8 @@ class Project(BaseModel): # Dev only dev: Optional[Dict] = Field(description="object used for development purposes only") + @validator("thumbnail", pre=True) @classmethod - @validator("thumbnail") def null_thumbnail(cls, v): if isinstance(v, str) and v == "": return None diff --git a/packages/models-library/tests/test_projects.py b/packages/models-library/tests/test_projects.py new file mode 100644 index 00000000000..dd38509baab --- /dev/null +++ b/packages/models-library/tests/test_projects.py @@ -0,0 +1,41 @@ +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +from copy import deepcopy +from typing import Any, Dict +from uuid import uuid4 + +import pytest +from models_library.projects import Project + + +@pytest.fixture() +def minimal_project() -> Dict[str, Any]: + return { + "uuid": str(uuid4()), + "name": "The project name", + "description": "The project's description", + "prjOwner": "theowner@ownership.com", + "accessRights": {}, + "thumbnail": None, + "creationDate": "2019-05-24T10:36:57.813Z", + "lastChangeDate": "2019-05-24T10:36:57.813Z", + "workbench": {}, + } + + +def test_project_minimal_model(minimal_project: Dict[str, Any]): + project = Project.parse_obj(minimal_project) + assert project + + assert project.thumbnail == None + + +def test_project_with_thumbnail_as_empty_string(minimal_project: Dict[str, Any]): + thumbnail_empty_string = deepcopy(minimal_project) + thumbnail_empty_string.update({"thumbnail": ""}) + project = Project.parse_obj(thumbnail_empty_string) + + assert project + assert project.thumbnail == None diff --git a/packages/models-library/tests/test_projects_pipeline.py b/packages/models-library/tests/test_projects_pipeline.py new file mode 100644 index 00000000000..2e239877bd0 --- /dev/null +++ b/packages/models-library/tests/test_projects_pipeline.py @@ -0,0 +1,13 @@ +# pylint:disable=unused-variable +# pylint:disable=unused-argument +# pylint:disable=redefined-outer-name + +from models_library.projects_pipeline import ComputationTask + + +def test_computation_task_model(): + example = ComputationTask.Config.schema_extra["example"] + print(example) + + model_instance = ComputationTask.parse_obj(example) + assert model_instance From dbdd3fcfb1e3ce9edb8554c2c07126c324613eab Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:02:38 +0100 Subject: [PATCH 037/200] do not assign state if the node is missing from the project --- .../src/simcore_service_webserver/projects/projects_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 17309beb5cf..6b1e35c178e 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -520,6 +520,8 @@ async def add_project_states_for_user( node_state, ) in computation_task.pipeline_details.node_states.items(): prj_node = project["workbench"].get(str(node_id)) + if prj_node is None: + continue prj_node["ioState"] = node_state.io_state prj_node["runnableState"] = node_state.runnable_state From ab8aa3f4f3d58e6c4fb5b8c4364ef036ed120ce3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:18:27 +0100 Subject: [PATCH 038/200] invalid call to function to set states --- .../simcore_service_webserver/projects/projects_handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py index f89fa685b7b..69b83934c8a 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py @@ -145,12 +145,12 @@ async def set_all_project_states(projects: List[Dict[str, Any]], is_template: bo projects_list = [] if ptype in ("template", "all"): template_projects = await db.load_template_projects(user_id=user_id) - await set_all_project_states(template_projects, False) + await set_all_project_states(template_projects, is_template=True) projects_list += template_projects if ptype in ("user", "all"): # standard only (notice that templates will only) user_projects = await db.load_user_projects(user_id=user_id) - await set_all_project_states(user_projects, True) + await set_all_project_states(user_projects, is_template=False) projects_list += user_projects start = int(request.query.get("start", 0)) From 2021877533281c4b328eb644b638475da58d7075 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:49:32 +0100 Subject: [PATCH 039/200] fixes unit test accessing director-v2 --- .../fast/test_studies_dispatcher_routes.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py b/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py index 113054a64b6..784f1b179d8 100644 --- a/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py +++ b/services/web/server/tests/unit/with_dbs/fast/test_studies_dispatcher_routes.py @@ -11,13 +11,8 @@ import pytest from aiohttp import ClientResponse, ClientSession, web -from models_library.projects_state import ( - Owner, - ProjectLocked, - ProjectRunningState, - ProjectState, - RunningState, -) +from aioresponses import aioresponses +from models_library.projects_state import ProjectLocked from pytest_simcore.helpers.utils_assert import assert_status from pytest_simcore.helpers.utils_login import UserRole from pytest_simcore.helpers.utils_mock import future_with_result @@ -80,6 +75,13 @@ def app_cfg(default_app_cfg, aiohttp_unused_port, qx_client_outdir, redis_servic return cfg +@pytest.fixture(autouse=True) +async def director_v2_automock( + director_v2_service_mock: aioresponses, +) -> aioresponses: + yield director_v2_service_mock + + # REST-API ----------------------------------------------------------------------------------------------- # Samples taken from trials on http://127.0.0.1:9081/dev/doc#/viewer/get_viewer_for_file # From fe6b9f1df9cd6c3d2f17c5678bded8de127aea0d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 11:14:09 +0100 Subject: [PATCH 040/200] removed thumbnail as str --- packages/models-library/src/models_library/projects.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/models-library/src/models_library/projects.py b/packages/models-library/src/models_library/projects.py index 26b671d89a8..1b0398a8dd1 100644 --- a/packages/models-library/src/models_library/projects.py +++ b/packages/models-library/src/models_library/projects.py @@ -40,8 +40,6 @@ class Project(BaseModel): description="longer one-line description about the project", examples=["Dabbling in temporal transitions ..."], ) - # NOTE: str is necessary because HttpUrl will not accept and empty string and the - # frontend sometimes sends this empty string, which is removed by the validator thumbnail: Optional[HttpUrl] = Field( ..., description="url of the project thumbnail", @@ -94,7 +92,7 @@ class Project(BaseModel): # Dev only dev: Optional[Dict] = Field(description="object used for development purposes only") - @validator("thumbnail", pre=True) + @validator("thumbnail", pre=True, always=True) @classmethod def null_thumbnail(cls, v): if isinstance(v, str) and v == "": From 318c8516281e93e4bb629910ed7d5b45b158ab13 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 11:54:22 +0100 Subject: [PATCH 041/200] API schema is not expecting the same as DB schema, thumbnail is a string --- .../src/simcore_service_webserver/projects/projects_db.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_db.py b/services/web/server/src/simcore_service_webserver/projects/projects_db.py index fabfd051722..6cb354b9345 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_db.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_db.py @@ -117,6 +117,8 @@ def _convert_to_schema_names( elif key == "prj_owner": # this entry has to be converted to the owner e-mail address converted_value = user_email + elif key == "thumbnail" and value is None: + converted_value = "" converted_args[ChangeCase.snake_to_camel(key)] = converted_value converted_args.update(**kwargs) return converted_args From 5d4ea641444ce064ec32fea3f422b1d40da44c97 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 15:15:53 +0100 Subject: [PATCH 042/200] added iostate and runnable state in Project model --- .../src/models_library/projects_nodes.py | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index 907cddff315..10311b5657e 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -56,6 +56,27 @@ Outputs = Dict[OutputID, OutputTypes] +@unique +class NodeIOState(str, Enum): + OK = "OK" + OUTDATED = "OUTDATED" + + +@unique +class NodeRunnableState(str, Enum): + WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" + READY = "READY" + + +class NodeState(BaseModel): + io_state: NodeIOState = Field( + ..., description="represents the state of the inputs outputs" + ) + runnable_state: NodeRunnableState = Field( + ..., description="represent the runnable state of the node" + ) + + class Node(BaseModel): key: str = Field( ..., @@ -125,15 +146,27 @@ class Node(BaseModel): example=["nodeUUid1", "nodeUuid2"], ) + # NOTE: use projects_ui.py + position: Optional[Position] = Field(None, deprecated=True) + + io_state: Optional[NodeIOState] = Field( + NodeIOState.OUTDATED, + description="The node's inpyts/outputs state", + alias="ioState", + ) + + runnable_state: Optional[NodeRunnableState] = Field( + NodeRunnableState.READY, + description="The node's runnable state", + alias="runnableState", + ) + state: Optional[RunningState] = Field( RunningState.NOT_STARTED, description="the node's running state", example=["RUNNING", "FAILED"], ) - # NOTE: use projects_ui.py - position: Optional[Position] = Field(None, deprecated=True) - @validator("thumbnail", pre=True) @classmethod def convert_empty_str_to_none(cls, v): From 7a812e0fdc89458666440caea30c1d8ff15173a3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 15:17:34 +0100 Subject: [PATCH 043/200] updated OAS --- services/director-v2/openapi.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/services/director-v2/openapi.json b/services/director-v2/openapi.json index deb6b30920b..1bd81f2cba9 100644 --- a/services/director-v2/openapi.json +++ b/services/director-v2/openapi.json @@ -809,6 +809,28 @@ "description": "the link where to stop the task", "format": "uri" } + }, + "example": { + "id": "42838344-03de-4ce2-8d93-589a5dcdfd05", + "state": "PUBLISHED", + "pipeline_details": { + "adjacency_list": { + "2fb4808a-e403-4a46-b52c-892560d27862": [], + "19a40c7b-0a40-458a-92df-c77a5df7c886": [ + "2fb4808a-e403-4a46-b52c-892560d27862" + ] + }, + "node_states": { + "2fb4808a-e403-4a46-b52c-892560d27862": { + "io_state": "OUTDATED", + "runnable_state": "READY" + }, + "19a40c7b-0a40-458a-92df-c77a5df7c886": { + "io_state": "OK", + "runnable_state": "WAITING_FOR_DEPENDENCIES" + } + } + } } }, "ComputationTaskStop": { From e0e1b954d79199e73e92aeb827942f6584c49f7f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 27 Jan 2021 17:20:20 +0100 Subject: [PATCH 044/200] refactoring --- .../src/simcore_service_webserver/projects/projects_db.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_db.py b/services/web/server/src/simcore_service_webserver/projects/projects_db.py index 6cb354b9345..e6ac41872eb 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_db.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_db.py @@ -43,6 +43,7 @@ "id", "published", ] +SCHEMA_NON_NULL_KEYS = ["thumbnail"] class ProjectAccessRights(Enum): @@ -117,8 +118,10 @@ def _convert_to_schema_names( elif key == "prj_owner": # this entry has to be converted to the owner e-mail address converted_value = user_email - elif key == "thumbnail" and value is None: + + if key in SCHEMA_NON_NULL_KEYS and value is None: converted_value = "" + converted_args[ChangeCase.snake_to_camel(key)] = converted_value converted_args.update(**kwargs) return converted_args From 64f9b8920963e8a21839196055b148f6983f0490 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 29 Jan 2021 18:54:54 +0100 Subject: [PATCH 045/200] try fixing downgrading the projects table --- .../20ec678d7dad_nullable_project_columns.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py index ab4a2ff0e02..5904319580d 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py @@ -10,29 +10,35 @@ # revision identifiers, used by Alembic. -revision = '20ec678d7dad' -down_revision = '99db5efc4548' +revision = "20ec678d7dad" +down_revision = "99db5efc4548" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('projects', 'description', - existing_type=sa.VARCHAR(), - nullable=True) - op.alter_column('projects', 'thumbnail', - existing_type=sa.VARCHAR(), - nullable=True) + op.alter_column( + "projects", "description", existing_type=sa.VARCHAR(), nullable=True + ) + op.alter_column("projects", "thumbnail", existing_type=sa.VARCHAR(), nullable=True) # ### end Alembic commands ### +from simcore_postgres_database.models.projects import projects + + def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('projects', 'thumbnail', - existing_type=sa.VARCHAR(), - nullable=False) - op.alter_column('projects', 'description', - existing_type=sa.VARCHAR(), - nullable=False) + op.execute( + sa.update(projects).where(projects.c.thumbnail is None).values(thumbnail="") + ) + op.alter_column("projects", "thumbnail", existing_type=sa.VARCHAR(), nullable=False) + + op.execute( + sa.update(projects).where(projects.c.description is None).values(description="") + ) + op.alter_column( + "projects", "description", existing_type=sa.VARCHAR(), nullable=False + ) # ### end Alembic commands ### From c9379bfcbd944d6112bf5ec38fd44afff10457e4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 29 Jan 2021 18:55:31 +0100 Subject: [PATCH 046/200] sorting --- .../versions/20ec678d7dad_nullable_project_columns.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py index 5904319580d..edda03c59f4 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py @@ -5,9 +5,9 @@ Create Date: 2019-07-04 08:44:35.901118+00:00 """ -from alembic import op import sqlalchemy as sa - +from alembic import op +from simcore_postgres_database.models.projects import projects # revision identifiers, used by Alembic. revision = "20ec678d7dad" @@ -25,9 +25,6 @@ def upgrade(): # ### end Alembic commands ### -from simcore_postgres_database.models.projects import projects - - def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.execute( From e944e3a16fb7310d9af885822b47642fce317ea1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 29 Jan 2021 19:24:33 +0100 Subject: [PATCH 047/200] use direct SQL --- .../versions/20ec678d7dad_nullable_project_columns.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py index edda03c59f4..110487f2ab2 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py @@ -7,7 +7,7 @@ """ import sqlalchemy as sa from alembic import op -from simcore_postgres_database.models.projects import projects + # revision identifiers, used by Alembic. revision = "20ec678d7dad" @@ -28,12 +28,14 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### op.execute( - sa.update(projects).where(projects.c.thumbnail is None).values(thumbnail="") + sa.update(sa.DDL("UPDATE projects SET thumbnail = '' WHERE thumbnail = NULL")) ) op.alter_column("projects", "thumbnail", existing_type=sa.VARCHAR(), nullable=False) op.execute( - sa.update(projects).where(projects.c.description is None).values(description="") + sa.update( + sa.DDL("UPDATE projects SET description = '' WHERE description = NULL") + ) ) op.alter_column( "projects", "description", existing_type=sa.VARCHAR(), nullable=False From 874bc0ef0105705c04c62b22b2cecc7632ec98a3 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 1 Feb 2021 15:51:43 +0100 Subject: [PATCH 048/200] re-created specs --- .../api/v0/openapi.yaml | 1354 ++++++++--------- 1 file changed, 677 insertions(+), 677 deletions(-) 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 730cb9ac9bd..f607b411292 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 @@ -8,17 +8,17 @@ info: email: support@simcore.io license: name: MIT - url: "https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE" + url: 'https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE' servers: - description: API server url: /v0 - description: Development server - url: "http://{host}:{port}/{basePath}" + url: 'http://{host}:{port}/{basePath}' variables: host: default: localhost port: - default: "8001" + default: '8001' basePath: enum: - v0 @@ -56,7 +56,7 @@ paths: summary: run check operationId: check_running responses: - "200": + '200': description: Service information content: application/json: @@ -116,7 +116,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -124,7 +124,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -174,7 +174,7 @@ paths: summary: health check operationId: check_health responses: - "200": + '200': description: Service information content: application/json: @@ -234,7 +234,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -242,7 +242,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -285,7 +285,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/check/{action}": + '/check/{action}': post: tags: - maintenance @@ -330,7 +330,7 @@ paths: key1: value1 key2: value2 responses: - "200": + '200': description: Echoes response based on action content: application/json: @@ -403,7 +403,7 @@ paths: tags: - configuration responses: - "200": + '200': description: configuration details content: application/json: @@ -454,7 +454,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -462,7 +462,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -536,7 +536,7 @@ paths: invitation: 33c451d4-17b7-4e65-9880-694559b8ffc2 required: true responses: - "200": + '200': description: User has been succesfully registered. content: application/json: @@ -558,7 +558,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -566,7 +566,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: @@ -604,7 +604,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -612,7 +612,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -675,7 +675,7 @@ paths: email: foo@mymail.com password: my secret responses: - "200": + '200': description: Succesfully logged in content: application/json: @@ -697,7 +697,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -705,7 +705,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: @@ -743,7 +743,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -751,7 +751,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -809,7 +809,7 @@ paths: type: string example: 5ac57685-c40f-448f-8711-70be1936fd63 responses: - "200": + '200': description: Succesfully logged out content: application/json: @@ -831,7 +831,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -839,7 +839,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: @@ -877,7 +877,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -885,7 +885,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -947,7 +947,7 @@ paths: example: email: foo@mymail.com responses: - "200": + '200': description: confirmation email sent to user content: application/json: @@ -969,7 +969,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -977,13 +977,13 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: nullable: true default: null - "503": + '503': description: failed to send confirmation email content: application/json: @@ -1015,7 +1015,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1023,7 +1023,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1066,7 +1066,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/auth/reset-password/{code}": + '/auth/reset-password/{code}': post: tags: - authentication @@ -1095,7 +1095,7 @@ paths: password: my secret confirm: my secret responses: - "200": + '200': description: password was successfully changed content: application/json: @@ -1117,7 +1117,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1125,13 +1125,13 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: nullable: true default: null - "401": + '401': description: unauthorized reset due to invalid token code content: application/json: @@ -1163,7 +1163,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1171,7 +1171,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1246,7 +1246,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1254,7 +1254,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1316,7 +1316,7 @@ paths: example: email: foo@mymail.com responses: - "200": + '200': description: confirmation sent to new email to complete operation content: application/json: @@ -1338,7 +1338,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1346,13 +1346,13 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: nullable: true default: null - "401": + '401': description: unauthorized user. Login required content: application/json: @@ -1384,7 +1384,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1392,7 +1392,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1435,7 +1435,7 @@ paths: message: Password is not secure field: pasword status: 400 - "503": + '503': description: unable to send confirmation email content: application/json: @@ -1467,7 +1467,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1475,7 +1475,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1550,7 +1550,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1558,7 +1558,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1628,7 +1628,7 @@ paths: new: my new secret confirm: my new secret responses: - "200": + '200': description: password was successfully changed content: application/json: @@ -1650,7 +1650,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1658,13 +1658,13 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger error: nullable: true default: null - "401": + '401': description: unauthorized user. Login required content: application/json: @@ -1696,7 +1696,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1704,7 +1704,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1747,7 +1747,7 @@ paths: message: Password is not secure field: pasword status: 400 - "409": + '409': description: mismatch between new and confirmation passwords content: application/json: @@ -1779,7 +1779,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1787,7 +1787,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1830,7 +1830,7 @@ paths: message: Password is not secure field: pasword status: 400 - "422": + '422': description: current password is invalid content: application/json: @@ -1862,7 +1862,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1870,7 +1870,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1945,7 +1945,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -1953,7 +1953,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -1996,7 +1996,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/auth/confirmation/{code}": + '/auth/confirmation/{code}': get: summary: email link sent to user to confirm an action tags: @@ -2018,7 +2018,7 @@ paths: - authentication operationId: list_api_keys responses: - "200": + '200': description: returns the display names of API keys content: application/json: @@ -2026,9 +2026,9 @@ paths: type: array items: type: string - "401": + '401': description: requires login to list keys - "403": + '403': description: not enough permissions to list keys post: summary: creates API keys to access public API @@ -2045,7 +2045,7 @@ paths: display_name: type: string responses: - "200": + '200': description: Authorization granted returning API key content: application/json: @@ -2058,11 +2058,11 @@ paths: type: string api_secret: type: string - "400": + '400': description: key name requested is invalid - "401": + '401': description: requires login to create a key - "403": + '403': description: not enough permissions to create a key delete: summary: deletes API key by name @@ -2079,11 +2079,11 @@ paths: display_name: type: string responses: - "204": + '204': description: api key successfully deleted - "401": + '401': description: requires login to delete a key - "403": + '403': description: not enough permissions to delete a key /me: get: @@ -2091,7 +2091,7 @@ paths: tags: - user responses: - "200": + '200': description: current user profile content: application/json: @@ -2167,18 +2167,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' organizations: type: array items: @@ -2227,18 +2227,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' all: type: object properties: @@ -2285,18 +2285,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' gravatar_id: type: string example: @@ -2338,7 +2338,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -2346,7 +2346,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -2411,7 +2411,7 @@ paths: first_name: Pedro last_name: Crespo responses: - "204": + '204': description: updated profile default: description: Default http error response body @@ -2445,7 +2445,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -2453,7 +2453,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -2503,7 +2503,7 @@ paths: tags: - user responses: - "200": + '200': description: list of tokens content: application/json: @@ -2569,7 +2569,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -2577,7 +2577,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -2657,7 +2657,7 @@ paths: nullable: true default: null responses: - "201": + '201': description: token created content: application/json: @@ -2721,7 +2721,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -2729,7 +2729,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -2772,7 +2772,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/me/tokens/{service}": + '/me/tokens/{service}': parameters: - name: service in: path @@ -2785,7 +2785,7 @@ paths: tags: - user responses: - "200": + '200': description: got detailed token content: application/json: @@ -2823,7 +2823,7 @@ paths: tags: - user responses: - "204": + '204': description: token has been successfully updated delete: summary: Delete token @@ -2831,7 +2831,7 @@ paths: tags: - user responses: - "204": + '204': description: token has been successfully deleted /groups: get: @@ -2840,7 +2840,7 @@ paths: tags: - group responses: - "200": + '200': description: list of the groups I belonged to content: application/json: @@ -2898,18 +2898,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' organizations: type: array items: @@ -2958,18 +2958,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' all: type: object properties: @@ -3016,18 +3016,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' error: nullable: true default: null @@ -3063,7 +3063,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -3071,7 +3071,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -3170,20 +3170,20 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' responses: - "201": + '201': description: group created content: application/json: @@ -3238,18 +3238,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' error: nullable: true default: null @@ -3285,7 +3285,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -3293,7 +3293,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -3336,7 +3336,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/groups/{gid}": + '/groups/{gid}': parameters: - name: gid in: path @@ -3349,7 +3349,7 @@ paths: summary: Gets one group details operationId: get_group responses: - "200": + '200': description: got group content: application/json: @@ -3404,18 +3404,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' error: nullable: true default: null @@ -3451,7 +3451,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -3459,7 +3459,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -3558,20 +3558,20 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' responses: - "200": + '200': description: the modified group content: application/json: @@ -3626,18 +3626,18 @@ paths: - description - accessRights example: - - gid: "27" + - gid: '27' label: A user description: A very special user - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "1" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '1' label: ITIS Foundation description: The Foundation for Research on Information Technologies in Society - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" - - gid: "0" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' + - gid: '0' label: All description: Open to all users - thumbnail: "https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png" + thumbnail: 'https://user-images.githubusercontent.com/32800795/61083844-ff48fb00-a42c-11e9-8e63-fa2d709c8baf.png' error: nullable: true default: null @@ -3673,7 +3673,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -3681,7 +3681,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -3730,7 +3730,7 @@ paths: summary: Deletes one group operationId: delete_group responses: - "204": + '204': description: group has been successfully deleted default: description: Default http error response body @@ -3764,7 +3764,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -3772,7 +3772,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -3815,7 +3815,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/groups/{gid}/users": + '/groups/{gid}/users': parameters: - name: gid in: path @@ -3828,7 +3828,7 @@ paths: summary: Gets list of users in group operationId: get_group_users responses: - "200": + '200': description: got list of users and their respective rights content: application/json: @@ -3868,8 +3868,8 @@ paths: last_name: Smith login: mr.smith@matrix.com gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 - id: "1" - gid: "3" + id: '1' + gid: '3' - description: defines acesss rights for the user type: object properties: @@ -3928,7 +3928,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -3936,7 +3936,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4007,7 +4007,7 @@ paths: format: email description: the user email responses: - "204": + '204': description: user successfully added default: description: Default http error response body @@ -4041,7 +4041,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4049,7 +4049,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4092,7 +4092,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/groups/{gid}/users/{uid}": + '/groups/{gid}/users/{uid}': parameters: - name: gid in: path @@ -4110,7 +4110,7 @@ paths: summary: Gets specific user in group operationId: get_group_user responses: - "200": + '200': description: got user content: application/json: @@ -4148,8 +4148,8 @@ paths: last_name: Smith login: mr.smith@matrix.com gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 - id: "1" - gid: "3" + id: '1' + gid: '3' - description: defines acesss rights for the user type: object properties: @@ -4208,7 +4208,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4216,7 +4216,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4299,7 +4299,7 @@ paths: required: - accessRights responses: - "200": + '200': description: modified user content: application/json: @@ -4337,8 +4337,8 @@ paths: last_name: Smith login: mr.smith@matrix.com gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 - id: "1" - gid: "3" + id: '1' + gid: '3' - description: defines acesss rights for the user type: object properties: @@ -4397,7 +4397,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4405,7 +4405,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4454,7 +4454,7 @@ paths: summary: Delete specific user in group operationId: delete_group_user responses: - "204": + '204': description: successfully removed user default: description: Default http error response body @@ -4488,7 +4488,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4496,7 +4496,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4539,7 +4539,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/groups/{gid}/classifiers": + '/groups/{gid}/classifiers': get: parameters: - name: gid @@ -4561,7 +4561,7 @@ paths: summary: Gets classifiers bundle for this group operationId: get_group_classifiers responses: - "200": + '200': description: got a bundle with all information about classifiers default: description: Default http error response body @@ -4595,7 +4595,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4603,7 +4603,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4646,7 +4646,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/groups/sparc/classifiers/scicrunch-resources/{rrid}": + '/groups/sparc/classifiers/scicrunch-resources/{rrid}': parameters: - name: rrid in: path @@ -4656,14 +4656,14 @@ paths: get: tags: - group - summary: "Returns information on a valid RRID (https://www.force11.org/group/resource-identification-initiative)" + summary: 'Returns information on a valid RRID (https://www.force11.org/group/resource-identification-initiative)' operationId: get_scicrunch_resource responses: - "200": + '200': description: Got information of a valid RRID - "400": + '400': description: Invalid RRID - "503": + '503': description: scircrunch.org service is not reachable default: description: Default http error response body @@ -4697,7 +4697,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4705,7 +4705,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4754,11 +4754,11 @@ paths: summary: Adds new RRID to classifiers operationId: add_scicrunch_resource responses: - "200": + '200': description: Got information of a valid RRID - "400": + '400': description: Invalid RRID - "503": + '503': description: scircrunch.org service is not reachable default: description: Default http error response body @@ -4792,7 +4792,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4800,7 +4800,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4843,7 +4843,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/groups/sparc/classifiers/scicrunch-resources:search": + '/groups/sparc/classifiers/scicrunch-resources:search': get: parameters: - name: guess_name @@ -4856,9 +4856,9 @@ paths: summary: Returns a list of related resource provided a search name operationId: search_scicrunch_resources responses: - "200": + '200': description: Got information of a valid RRID - "503": + '503': description: scircrunch.org service is not reachable default: description: Default http error response body @@ -4892,7 +4892,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -4900,7 +4900,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -4950,7 +4950,7 @@ paths: - storage operationId: get_storage_locations responses: - "200": + '200': description: List of availabe storage locations content: application/json: @@ -4998,7 +4998,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -5006,7 +5006,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -5049,7 +5049,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/storage/locations/{location_id}/files/metadata": + '/storage/locations/{location_id}/files/metadata': get: summary: Get list of file meta data tags: @@ -5062,7 +5062,7 @@ paths: schema: type: string responses: - "200": + '200': description: list of file meta-datas content: application/json: @@ -5111,24 +5111,24 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: "0" + location_id: '0' location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: "105" + project_id: '105' project_name: futurology - node_id: "10000" + node_id: '10000' node_name: alpha file_name: example.txt - user_id: "12" + user_id: '12' user_name: dennis - file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" + file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: "2019-06-19T12:29:03.308611Z" - last_modified: "2019-06-19T12:29:03.78852Z" + created_at: '2019-06-19T12:29:03.308611Z' + last_modified: '2019-06-19T12:29:03.78852Z' file_size: 73 - parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" + parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' default: description: Default http error response body content: @@ -5161,7 +5161,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -5169,7 +5169,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -5212,7 +5212,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/storage/locations/{location_id}/files/{fileId}": + '/storage/locations/{location_id}/files/{fileId}': get: summary: Returns download link for requested file tags: @@ -5230,7 +5230,7 @@ paths: schema: type: string responses: - "200": + '200': description: Returns presigned link content: application/json: @@ -5268,7 +5268,7 @@ paths: schema: type: string responses: - "200": + '200': description: Returns presigned link content: application/json: @@ -5296,9 +5296,9 @@ paths: schema: type: string responses: - "204": - description: "" - "/storage/locations/{location_id}/files/{fileId}/metadata": + '204': + description: '' + '/storage/locations/{location_id}/files/{fileId}/metadata': get: summary: Get File Metadata tags: @@ -5316,7 +5316,7 @@ paths: schema: type: string responses: - "200": + '200': description: Returns file metadata content: application/json: @@ -5363,24 +5363,24 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: "0" + location_id: '0' location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: "105" + project_id: '105' project_name: futurology - node_id: "10000" + node_id: '10000' node_name: alpha file_name: example.txt - user_id: "12" + user_id: '12' user_name: dennis - file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" + file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: "2019-06-19T12:29:03.308611Z" - last_modified: "2019-06-19T12:29:03.78852Z" + created_at: '2019-06-19T12:29:03.308611Z' + last_modified: '2019-06-19T12:29:03.78852Z' file_size: 73 - parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" + parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' patch: summary: Update File Metadata tags: @@ -5443,26 +5443,26 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: "0" + location_id: '0' location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: "105" + project_id: '105' project_name: futurology - node_id: "10000" + node_id: '10000' node_name: alpha file_name: example.txt - user_id: "12" + user_id: '12' user_name: dennis - file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" + file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: "2019-06-19T12:29:03.308611Z" - last_modified: "2019-06-19T12:29:03.78852Z" + created_at: '2019-06-19T12:29:03.308611Z' + last_modified: '2019-06-19T12:29:03.78852Z' file_size: 73 - parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" + parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' responses: - "200": + '200': description: Returns file metadata content: application/json: @@ -5509,25 +5509,25 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: "0" + location_id: '0' location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: "105" + project_id: '105' project_name: futurology - node_id: "10000" + node_id: '10000' node_name: alpha file_name: example.txt - user_id: "12" + user_id: '12' user_name: dennis - file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" + file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: "2019-06-19T12:29:03.308611Z" - last_modified: "2019-06-19T12:29:03.78852Z" + created_at: '2019-06-19T12:29:03.308611Z' + last_modified: '2019-06-19T12:29:03.78852Z' file_size: 73 - parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" - "/storage/locations/{location_id}/datasets/{dataset_id}/metadata": + parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' + '/storage/locations/{location_id}/datasets/{dataset_id}/metadata': get: summary: Get Files Metadata tags: @@ -5545,7 +5545,7 @@ paths: schema: type: string responses: - "200": + '200': description: list of file meta-datas content: application/json: @@ -5594,24 +5594,24 @@ paths: type: string example: file_uuid: simcore-testing/105/1000/3 - location_id: "0" + location_id: '0' location_name: simcore.s3 bucket_name: simcore-testing object_name: 105/10000/3 - project_id: "105" + project_id: '105' project_name: futurology - node_id: "10000" + node_id: '10000' node_name: alpha file_name: example.txt - user_id: "12" + user_id: '12' user_name: dennis - file_id: "N:package:e263da07-2d89-45a6-8b0f-61061b913873" + file_id: 'N:package:e263da07-2d89-45a6-8b0f-61061b913873' raw_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv display_file_path: Curation/derivatives/subjects/sourcedata/docs/samples/sam_1/sam_1.csv - created_at: "2019-06-19T12:29:03.308611Z" - last_modified: "2019-06-19T12:29:03.78852Z" + created_at: '2019-06-19T12:29:03.308611Z' + last_modified: '2019-06-19T12:29:03.78852Z' file_size: 73 - parent_id: "N:collection:e263da07-2d89-45a6-8b0f-61061b913873" + parent_id: 'N:collection:e263da07-2d89-45a6-8b0f-61061b913873' default: description: Default http error response body content: @@ -5644,7 +5644,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -5652,7 +5652,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -5695,7 +5695,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/storage/locations/{location_id}/datasets": + '/storage/locations/{location_id}/datasets': get: summary: Get datasets metadata tags: @@ -5708,7 +5708,7 @@ paths: schema: type: string responses: - "200": + '200': description: list of dataset meta-datas content: application/json: @@ -5722,7 +5722,7 @@ paths: display_name: type: string example: - dataset_uuid: "N:id-aaaa" + dataset_uuid: 'N:id-aaaa' display_name: simcore-testing default: description: Default http error response body @@ -5756,7 +5756,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -5764,7 +5764,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -5807,7 +5807,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/computation/pipeline/{project_id}:start": + '/computation/pipeline/{project_id}:start': post: description: Starts a pipeline of a given project tags: @@ -5840,7 +5840,7 @@ paths: type: string format: uuid responses: - "201": + '201': description: Successfully started the pipeline content: application/json: @@ -5892,7 +5892,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -5900,7 +5900,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -5943,7 +5943,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/computation/pipeline/{project_id}:stop": + '/computation/pipeline/{project_id}:stop': post: description: Stops a pipeline of a given project tags: @@ -5958,7 +5958,7 @@ paths: type: string example: 123e4567-e89b-12d3-a456-426655440000 responses: - "204": + '204': description: Succesffully stopped the pipeline default: description: Default http error response body @@ -5992,7 +5992,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -6000,7 +6000,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -6081,7 +6081,7 @@ paths: minItems: 1 description: maximum number of items to return responses: - "200": + '200': description: list of projects content: application/json: @@ -6153,24 +6153,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -6209,26 +6209,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -6240,7 +6240,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -6275,7 +6275,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -6297,14 +6297,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -6349,7 +6349,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -6361,18 +6361,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -6413,8 +6413,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -6424,24 +6424,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -6465,7 +6465,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -6614,7 +6614,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -6622,7 +6622,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -6675,12 +6675,12 @@ paths: in: query schema: type: string - description: "Option to create a project from existing template: from_template={template_uuid}" + description: 'Option to create a project from existing template: from_template={template_uuid}' - name: as_template in: query schema: type: string - description: "Option to create a template from existing project: as_template={study_uuid}" + description: 'Option to create a template from existing project: as_template={study_uuid}' requestBody: content: application/json: @@ -6744,24 +6744,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -6800,26 +6800,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -6831,7 +6831,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -6866,7 +6866,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -6888,14 +6888,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -6940,7 +6940,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -6952,18 +6952,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -7004,8 +7004,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -7015,24 +7015,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -7056,7 +7056,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -7145,7 +7145,7 @@ paths: title: Quality description: Object containing Quality Assessment related data responses: - "201": + '201': description: project created content: application/json: @@ -7215,24 +7215,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -7271,26 +7271,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -7302,7 +7302,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -7337,7 +7337,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -7359,14 +7359,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -7411,7 +7411,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -7423,18 +7423,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -7475,8 +7475,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -7486,24 +7486,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -7527,7 +7527,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -7676,7 +7676,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -7684,7 +7684,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -7734,7 +7734,7 @@ paths: summary: Gets active project operationId: get_active_project responses: - "200": + '200': description: returns active project content: application/json: @@ -7804,24 +7804,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -7860,26 +7860,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -7891,7 +7891,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -7926,7 +7926,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -7948,14 +7948,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -8000,7 +8000,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -8012,18 +8012,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -8064,8 +8064,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -8075,24 +8075,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -8116,7 +8116,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -8265,7 +8265,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -8273,7 +8273,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -8316,7 +8316,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/projects/{project_id}": + '/projects/{project_id}': parameters: - name: project_id in: path @@ -8329,7 +8329,7 @@ paths: summary: Gets given project operationId: get_project responses: - "200": + '200': description: got detailed project content: application/json: @@ -8399,24 +8399,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -8455,26 +8455,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -8486,7 +8486,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -8521,7 +8521,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -8543,14 +8543,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -8595,7 +8595,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -8607,18 +8607,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -8659,8 +8659,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -8670,24 +8670,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -8711,7 +8711,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -8860,7 +8860,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -8868,7 +8868,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -8985,24 +8985,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -9041,26 +9041,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -9072,7 +9072,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -9107,7 +9107,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -9129,14 +9129,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -9181,7 +9181,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -9193,18 +9193,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -9245,8 +9245,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -9256,24 +9256,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -9297,7 +9297,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -9386,7 +9386,7 @@ paths: title: Quality description: Object containing Quality Assessment related data responses: - "200": + '200': description: got detailed project content: application/json: @@ -9456,24 +9456,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -9512,26 +9512,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -9543,7 +9543,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -9578,7 +9578,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -9600,14 +9600,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -9652,7 +9652,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -9664,18 +9664,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -9716,8 +9716,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -9727,24 +9727,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -9768,7 +9768,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -9917,7 +9917,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -9925,7 +9925,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -9974,9 +9974,9 @@ paths: summary: Delete given project operationId: delete_project responses: - "204": + '204': description: project has been successfully deleted - "/projects/{project_id}:open": + '/projects/{project_id}:open': parameters: - name: project_id in: path @@ -9997,7 +9997,7 @@ paths: type: string example: 5ac57685-c40f-448f-8711-70be1936fd63 responses: - "200": + '200': description: project successfuly opened content: application/json: @@ -10067,24 +10067,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -10123,26 +10123,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -10154,7 +10154,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -10189,7 +10189,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -10211,14 +10211,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -10263,7 +10263,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -10275,18 +10275,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -10327,8 +10327,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -10338,24 +10338,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -10379,7 +10379,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -10528,7 +10528,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -10536,7 +10536,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -10579,7 +10579,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/projects/{project_id}/state": + '/projects/{project_id}/state': parameters: - name: project_id in: path @@ -10592,7 +10592,7 @@ paths: summary: returns the state of a project operationId: state_project responses: - "200": + '200': description: returns the project current state content: application/json: @@ -10660,7 +10660,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -10668,7 +10668,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -10711,7 +10711,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/projects/{project_id}:xport": + '/projects/{project_id}:xport': parameters: - name: project_id in: path @@ -10724,7 +10724,7 @@ paths: summary: creates an archive of the project and downloads it operationId: export_project responses: - "200": + '200': description: creates an archive from a project file content: application/zip: @@ -10763,7 +10763,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -10771,7 +10771,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -10976,7 +10976,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -10984,7 +10984,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -11027,7 +11027,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/projects/{project_id}:close": + '/projects/{project_id}:close': parameters: - name: project_id in: path @@ -11048,7 +11048,7 @@ paths: type: string example: 5ac57685-c40f-448f-8711-70be1936fd63 responses: - "204": + '204': description: project succesfuly closed default: description: Default http error response body @@ -11082,7 +11082,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -11090,7 +11090,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -11133,7 +11133,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/projects/{project_id}/nodes": + '/projects/{project_id}/nodes': parameters: - name: project_id in: path @@ -11170,7 +11170,7 @@ paths: service_key: simcore/services/dynamic/3d-viewer service_version: 1.4.0 responses: - "201": + '201': description: created content: application/json: @@ -11223,7 +11223,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -11231,7 +11231,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -11274,7 +11274,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/projects/{project_id}/nodes/{node_id}": + '/projects/{project_id}/nodes/{node_id}': parameters: - name: project_id in: path @@ -11292,7 +11292,7 @@ paths: description: Gets node status operationId: get_node responses: - "200": + '200': description: OK service exists and runs. Returns node details. content: application/json: @@ -11353,7 +11353,7 @@ paths: description: different base path where current service is mounted otherwise defaults to root type: string example: /x/E1O2E-LAH - default: "" + default: '' service_state: description: | the service state * 'pending' - The service is waiting for resources to start * 'pulling' - The service is being pulled from the registry * 'starting' - The service is starting * 'running' - The service is running * 'complete' - The service completed * 'failed' - The service failed to start @@ -11404,7 +11404,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -11412,7 +11412,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -11461,7 +11461,7 @@ paths: description: Stops and removes a node from the project operationId: delete_node responses: - "204": + '204': description: node has been successfully deleted from project default: description: Default http error response body @@ -11495,7 +11495,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -11503,7 +11503,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -11546,7 +11546,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/nodes/{nodeInstanceUUID}/outputUi/{outputKey}": + '/nodes/{nodeInstanceUUID}/outputUi/{outputKey}': get: tags: - node @@ -11564,7 +11564,7 @@ paths: schema: type: string responses: - "200": + '200': description: Service Information content: application/json: @@ -11609,7 +11609,7 @@ paths: description: Error code type: integer example: 404 - "/nodes/{nodeInstanceUUID}/outputUi/{outputKey}/{apiCall}": + '/nodes/{nodeInstanceUUID}/outputUi/{outputKey}/{apiCall}': post: tags: - node @@ -11689,7 +11689,7 @@ paths: label: type: string thumbnail: - description: "data url - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs" + description: 'data url - https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs' type: string - type: object - type: array @@ -11703,7 +11703,7 @@ paths: folder: type: boolean - type: object - "/nodes/{nodeInstanceUUID}/iframe": + '/nodes/{nodeInstanceUUID}/iframe': get: tags: - node @@ -11718,7 +11718,7 @@ paths: responses: default: description: any response appropriate in the iframe context - "/projects/{study_uuid}/tags/{tag_id}": + '/projects/{study_uuid}/tags/{tag_id}': parameters: - name: tag_id in: path @@ -11736,7 +11736,7 @@ paths: summary: Links an existing label with an existing study operationId: add_tag responses: - "200": + '200': description: The tag has been successfully linked to the study content: application/json: @@ -11806,24 +11806,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -11862,26 +11862,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -11893,7 +11893,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -11928,7 +11928,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -11950,14 +11950,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -12002,7 +12002,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -12014,18 +12014,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -12066,8 +12066,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -12077,24 +12077,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -12118,7 +12118,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -12267,7 +12267,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -12275,7 +12275,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -12324,7 +12324,7 @@ paths: summary: Removes an existing link between a label and a study operationId: remove_tag responses: - "200": + '200': description: The tag has been successfully removed from the study content: application/json: @@ -12394,24 +12394,24 @@ paths: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -12450,26 +12450,26 @@ paths: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -12481,7 +12481,7 @@ paths: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -12516,7 +12516,7 @@ paths: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -12538,14 +12538,14 @@ paths: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -12590,7 +12590,7 @@ paths: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -12602,18 +12602,18 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -12654,8 +12654,8 @@ paths: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -12665,24 +12665,24 @@ paths: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -12706,7 +12706,7 @@ paths: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -12855,7 +12855,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -12863,7 +12863,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -12912,8 +12912,8 @@ paths: tags: - activity responses: - "200": - description: "Object containing queuing, CPU and Memory usage/limits information of services" + '200': + description: 'Object containing queuing, CPU and Memory usage/limits information of services' content: application/json: schema: @@ -12977,7 +12977,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -12985,7 +12985,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13035,7 +13035,7 @@ paths: summary: List all tags for the current user operationId: list_tags responses: - "200": + '200': description: List of tags content: application/json: @@ -13065,7 +13065,7 @@ paths: type: string color: type: string - pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' error: nullable: true default: null @@ -13101,7 +13101,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13109,7 +13109,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13158,7 +13158,7 @@ paths: summary: Creates a new tag operationId: create_tag responses: - "200": + '200': description: The created tag content: application/json: @@ -13181,7 +13181,7 @@ paths: type: string color: type: string - pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' error: nullable: true default: null @@ -13217,7 +13217,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13225,7 +13225,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13268,7 +13268,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/tags/{tag_id}": + '/tags/{tag_id}': parameters: - name: tag_id in: path @@ -13281,7 +13281,7 @@ paths: summary: Updates a tag operationId: update_tag responses: - "200": + '200': description: The updated tag content: application/json: @@ -13304,7 +13304,7 @@ paths: type: string color: type: string - pattern: "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' error: nullable: true default: null @@ -13340,7 +13340,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13348,7 +13348,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13397,7 +13397,7 @@ paths: summary: Deletes an existing tag operationId: delete_tag responses: - "204": + '204': description: The tag has been successfully deleted /publications/service-submission: post: @@ -13420,7 +13420,7 @@ paths: type: string format: binary responses: - "204": + '204': description: Submission has been registered default: description: Default http error response body @@ -13454,7 +13454,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13462,7 +13462,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13511,9 +13511,9 @@ paths: - catalog operationId: list_catalog_dags responses: - "200": + '200': description: List of catalog dags - "422": + '422': description: Validation Error default: description: Default http error response body @@ -13547,7 +13547,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13555,7 +13555,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13610,9 +13610,9 @@ paths: type: object additionalProperties: true responses: - "201": + '201': description: The dag was successfully created - "422": + '422': description: Validation Error default: description: Default http error response body @@ -13646,7 +13646,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13654,7 +13654,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13697,7 +13697,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/catalog/dags/{dag_id}": + '/catalog/dags/{dag_id}': parameters: - in: path name: dag_id @@ -13717,9 +13717,9 @@ paths: type: object additionalProperties: true responses: - "200": + '200': description: The dag was replaced in catalog - "422": + '422': description: Validation Error default: description: Default http error response body @@ -13753,7 +13753,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13761,7 +13761,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13810,9 +13810,9 @@ paths: summary: Deletes an existing dag operationId: delete_catalog_dag responses: - "204": + '204': description: Successfully deleted - "422": + '422': description: Validation Error /catalog/services: get: @@ -13821,7 +13821,7 @@ paths: summary: List Services operationId: list_catalog_services responses: - "200": + '200': description: Returns list of services from the catalog default: description: Default http error response body @@ -13855,7 +13855,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13863,7 +13863,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -13906,7 +13906,7 @@ paths: message: Password is not secure field: pasword status: 400 - "/catalog/services/{service_key}/{service_version}": + '/catalog/services/{service_key}/{service_version}': parameters: - in: path name: service_key @@ -13928,7 +13928,7 @@ paths: summary: Get Service operationId: get_catalog_service responses: - "200": + '200': description: Returns service default: description: Default http error response body @@ -13962,7 +13962,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -13970,7 +13970,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -14025,7 +14025,7 @@ paths: type: object additionalProperties: true responses: - "200": + '200': description: Returns modified service default: description: Default http error response body @@ -14059,7 +14059,7 @@ paths: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -14067,7 +14067,7 @@ paths: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: @@ -14133,7 +14133,7 @@ paths: title: File Size in Bytes type: integer responses: - "200": + '200': content: application/json: schema: @@ -14144,7 +14144,7 @@ paths: title: File Type type: string redirection_url: - description: "Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link" + description: 'Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link' format: uri maxLength: 2083 minLength: 1 @@ -14172,7 +14172,7 @@ paths: get: operationId: list_supported_filetypes responses: - "200": + '200': content: application/json: schema: @@ -14184,7 +14184,7 @@ paths: title: File Type type: string redirection_url: - description: "Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link" + description: 'Base url to redirect to this viewer. Needs appending file_size, [file_name] and download_link' format: uri maxLength: 2083 minLength: 1 @@ -14243,7 +14243,7 @@ components: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -14251,7 +14251,7 @@ components: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger errors: From 614fdb6d5af03282061634146995dc6742a62fc8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 3 Feb 2021 10:21:32 +0100 Subject: [PATCH 049/200] updated specs after rebase --- .../api/v0/openapi.yaml | 206 +++++++++--------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index f5328111449..1ccc7759895 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -8,17 +8,17 @@ info: email: support@simcore.io license: name: MIT - url: "https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE" + url: 'https://github.com/ITISFoundation/osparc-simcore/blob/master/LICENSE' servers: - description: API server url: /v0 - description: Development server - url: "http://{host}:{port}/{basePath}" + url: 'http://{host}:{port}/{basePath}' variables: host: default: localhost port: - default: "8080" + default: '8080' basePath: enum: - v0 @@ -30,15 +30,15 @@ paths: description: Some general information on the API and state of the service behind operationId: health_check responses: - "200": + '200': description: Service information content: application/json: schema: - $ref: "#/components/schemas/HealthCheckEnveloped" + $ref: '#/components/schemas/HealthCheckEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/check/{action}": + $ref: '#/components/responses/DefaultErrorResponse' + '/check/{action}': post: summary: Test checkpoint to ask server to fail or echo back the transmitted data parameters: @@ -59,16 +59,16 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Fake" + $ref: '#/components/schemas/Fake' responses: - "200": + '200': description: Echoes response based on action content: application/json: schema: - $ref: "#/components/schemas/FakeEnveloped" + $ref: '#/components/schemas/FakeEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" + $ref: '#/components/responses/DefaultErrorResponse' /locations: get: summary: Lists available storage locations @@ -80,15 +80,15 @@ paths: schema: type: string responses: - "200": + '200': description: List of available storage locations content: application/json: schema: - $ref: "#/components/schemas/FileLocationArrayEnveloped" + $ref: '#/components/schemas/FileLocationArrayEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/locations/{location_id}/datasets": + $ref: '#/components/responses/DefaultErrorResponse' + '/locations/{location_id}/datasets': get: summary: Lists all dataset's metadata operationId: get_datasets_metadata @@ -104,15 +104,15 @@ paths: schema: type: string responses: - "200": + '200': description: list of dataset meta-datas content: application/json: schema: - $ref: "#/components/schemas/DatasetMetaDataArrayEnveloped" + $ref: '#/components/schemas/DatasetMetaDataArrayEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/locations/{location_id}/files/metadata": + $ref: '#/components/responses/DefaultErrorResponse' + '/locations/{location_id}/files/metadata': get: summary: Lists all file's metadata operationId: get_files_metadata @@ -133,15 +133,15 @@ paths: schema: type: string responses: - "200": + '200': description: list of file meta-datas content: application/json: schema: - $ref: "#/components/schemas/FileMetaDataArrayEnveloped" + $ref: '#/components/schemas/FileMetaDataArrayEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/locations/{location_id}/datasets/{dataset_id}/metadata": + $ref: '#/components/responses/DefaultErrorResponse' + '/locations/{location_id}/datasets/{dataset_id}/metadata': get: summary: Get dataset metadata operationId: get_files_metadata_dataset @@ -162,15 +162,15 @@ paths: schema: type: string responses: - "200": + '200': description: list of file meta-datas content: application/json: schema: - $ref: "#/components/schemas/FileMetaDataArrayEnveloped" + $ref: '#/components/schemas/FileMetaDataArrayEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/locations/{location_id}/files/{fileId}/metadata": + $ref: '#/components/responses/DefaultErrorResponse' + '/locations/{location_id}/files/{fileId}/metadata': get: summary: Get file metadata operationId: get_file_metadata @@ -191,14 +191,14 @@ paths: schema: type: string responses: - "200": + '200': description: Returns file metadata content: application/json: schema: - $ref: "#/components/schemas/FileMetaDataEnveloped" + $ref: '#/components/schemas/FileMetaDataEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" + $ref: '#/components/responses/DefaultErrorResponse' patch: summary: Update file metadata operationId: update_file_meta_data @@ -217,17 +217,17 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/FileMetaData" + $ref: '#/components/schemas/FileMetaData' responses: - "200": + '200': description: Returns file metadata content: application/json: schema: - $ref: "#/components/schemas/FileMetaDataEnveloped" + $ref: '#/components/schemas/FileMetaDataEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/locations/{location_id}/files/{fileId}": + $ref: '#/components/responses/DefaultErrorResponse' + '/locations/{location_id}/files/{fileId}': get: summary: Gets download link for file at location operationId: download_file @@ -248,14 +248,14 @@ paths: schema: type: string responses: - "200": + '200': description: Returns presigned link content: application/json: schema: - $ref: "#/components/schemas/PresignedLinkEnveloped" + $ref: '#/components/schemas/PresignedLinkEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" + $ref: '#/components/responses/DefaultErrorResponse' put: summary: Returns upload link or performs copy operation to datcore operationId: upload_file @@ -286,14 +286,14 @@ paths: schema: type: string responses: - "200": + '200': description: Returns presigned link content: application/json: schema: - $ref: "#/components/schemas/PresignedLinkEnveloped" + $ref: '#/components/schemas/PresignedLinkEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" + $ref: '#/components/responses/DefaultErrorResponse' delete: summary: Deletes file operationId: delete_file @@ -314,11 +314,11 @@ paths: schema: type: string responses: - "204": - description: "everything is OK, but there is no content to return" + '204': + description: 'everything is OK, but there is no content to return' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/simcore-s3/files/metadata:search": + $ref: '#/components/responses/DefaultErrorResponse' + '/simcore-s3/files/metadata:search': post: summary: Returns metadata for all files matching a pattern operationId: search_files_starting_with @@ -333,16 +333,16 @@ paths: in: query schema: type: string - default: "" + default: '' responses: - "200": + '200': description: list of matching files found content: application/json: schema: - $ref: "#/components/schemas/FileMetaDataArrayEnveloped" + $ref: '#/components/schemas/FileMetaDataArrayEnveloped' default: - $ref: "#/components/responses/DefaultErrorResponse" + $ref: '#/components/responses/DefaultErrorResponse' /simcore-s3/folders: post: summary: Deep copies of all data from source to destination project in s3 @@ -360,24 +360,24 @@ paths: type: object properties: source: - $ref: "#/components/schemas/Project" + $ref: '#/components/schemas/Project' destination: - $ref: "#/components/schemas/Project" + $ref: '#/components/schemas/Project' nodes_map: type: object description: maps source and destination node uuids additionalProperties: type: string responses: - "201": + '201': description: Data from destination project copied and returns project content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: '#/components/schemas/Project' default: - $ref: "#/components/responses/DefaultErrorResponse" - "/simcore-s3/folders/{folder_id}": + $ref: '#/components/responses/DefaultErrorResponse' + '/simcore-s3/folders/{folder_id}': delete: summary: Deletes all objects within a node_id or within a project_id if node_id is omitted operationId: delete_folders_of_project @@ -398,7 +398,7 @@ paths: schema: type: string responses: - "204": + '204': description: folder has been successfully deleted components: schemas: @@ -409,7 +409,7 @@ components: - error properties: data: - $ref: "#/components/schemas/HealthCheck" + $ref: '#/components/schemas/HealthCheck' error: nullable: true default: null @@ -439,7 +439,7 @@ components: nullable: true default: null error: - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' Error: type: object properties: @@ -447,12 +447,12 @@ components: description: log messages type: array items: - $ref: "#/components/schemas/LogMessage" + $ref: '#/components/schemas/LogMessage' errors: description: errors metadata type: array items: - $ref: "#/components/schemas/ErrorItem" + $ref: '#/components/schemas/ErrorItem' status: description: HTTP error code type: integer @@ -497,7 +497,7 @@ components: - error properties: data: - $ref: "#/components/schemas/LogMessage" + $ref: '#/components/schemas/LogMessage' error: nullable: true default: null @@ -514,7 +514,7 @@ components: - INFO - ERROR message: - description: "log message. If logger is USER, then it MUST be human readable" + description: 'log message. If logger is USER, then it MUST be human readable' type: string logger: description: name of the logger receiving this message @@ -522,7 +522,7 @@ components: required: - message example: - message: "Hi there, Mr user" + message: 'Hi there, Mr user' level: INFO logger: user-logger FakeEnveloped: @@ -532,7 +532,7 @@ components: - error properties: data: - $ref: "#/components/schemas/Fake" + $ref: '#/components/schemas/Fake' error: nullable: true default: null @@ -563,14 +563,14 @@ components: - error properties: data: - $ref: "#/components/schemas/FileLocationArray" + $ref: '#/components/schemas/FileLocationArray' error: nullable: true default: null FileLocationArray: type: array items: - $ref: "#/components/schemas/FileLocation" + $ref: '#/components/schemas/FileLocation' FileLocationEnveloped: type: object required: @@ -578,7 +578,7 @@ components: - error properties: data: - $ref: "#/components/schemas/FileLocation" + $ref: '#/components/schemas/FileLocation' error: nullable: true default: null @@ -599,7 +599,7 @@ components: - error properties: data: - $ref: "#/components/schemas/DatasetMetaDataArray" + $ref: '#/components/schemas/DatasetMetaDataArray' error: nullable: true default: null @@ -610,7 +610,7 @@ components: - error properties: data: - $ref: "#/components/schemas/DatasetMetaData" + $ref: '#/components/schemas/DatasetMetaData' error: nullable: true default: null @@ -622,12 +622,12 @@ components: display_name: type: string example: - dataset_uuid: "N:id-aaaa" + dataset_uuid: 'N:id-aaaa' display_name: simcore-testing DatasetMetaDataArray: type: array items: - $ref: "#/components/schemas/DatasetMetaData" + $ref: '#/components/schemas/DatasetMetaData' FileMetaDataEnveloped: type: object required: @@ -635,7 +635,7 @@ components: - error properties: data: - $ref: "#/components/schemas/FileMetaData" + $ref: '#/components/schemas/FileMetaData' error: nullable: true default: null @@ -689,14 +689,14 @@ components: - error properties: data: - $ref: "#/components/schemas/FileMetaDataArray" + $ref: '#/components/schemas/FileMetaDataArray' error: nullable: true default: null FileMetaDataArray: type: array items: - $ref: "#/components/schemas/FileMetaData" + $ref: '#/components/schemas/FileMetaData' PresignedLinkEnveloped: type: object required: @@ -704,7 +704,7 @@ components: - error properties: data: - $ref: "#/components/schemas/PresignedLink" + $ref: '#/components/schemas/PresignedLink' error: nullable: true default: null @@ -777,24 +777,24 @@ components: type: string description: project creation date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' lastChangeDate: type: string description: last save date pattern: '\d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z' - example: "2018-07-01T11:13:43Z" + example: '2018-07-01T11:13:43Z' thumbnail: type: string minLength: 0 maxLength: 2083 format: uri description: url of the latest screenshot of the project - example: "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + example: 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - key @@ -833,26 +833,26 @@ components: type: string description: url of the latest screenshot of the node example: - - "https://placeimg.com/171/96/tech/grayscale/?0.jpg" + - 'https://placeimg.com/171/96/tech/grayscale/?0.jpg' runHash: description: the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated type: - string - - "null" + - 'null' example: - a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2 inputs: type: object description: values of input properties patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -864,7 +864,7 @@ components: format: uuid output: type: string - pattern: "^[-_a-zA-Z0-9]+$" + pattern: '^[-_a-zA-Z0-9]+$' - type: object additionalProperties: false required: @@ -899,7 +899,7 @@ components: description: map with key - access level pairs type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': type: string enum: - Invisible @@ -921,14 +921,14 @@ components: default: {} type: object patternProperties: - "^[-_a-zA-Z0-9]+$": + '^[-_a-zA-Z0-9]+$': oneOf: - type: - integer - boolean - string - number - - "null" + - 'null' - type: object additionalProperties: false required: @@ -973,7 +973,7 @@ components: - nodeUuid2 parent: type: - - "null" + - 'null' - string format: uuid description: Parent's (group-nodes') node ID s. @@ -985,18 +985,18 @@ components: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' deprecated: true ioState: title: IO State @@ -1037,8 +1037,8 @@ components: workbench: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -1048,24 +1048,24 @@ components: additionalProperties: false required: - x - - "y" + - 'y' properties: x: type: integer description: The x position example: - - "12" - "y": + - '12' + 'y': type: integer description: The y position example: - - "15" + - '15' additionalProperties: true slideshow: type: object x-patternProperties: - ? "^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$" - : type: object + '^[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?4[0-9a-fA-F]{3}-?[89abAB][0-9a-fA-F]{3}-?[0-9a-fA-F]{12}$': + type: object additionalProperties: false required: - position @@ -1089,7 +1089,7 @@ components: description: Contains the reference to the project classifiers items: type: string - example: "some:id:to:a:classifier" + example: 'some:id:to:a:classifier' dev: type: object description: object used for development purposes only @@ -1183,4 +1183,4 @@ components: content: application/json: schema: - $ref: "#/components/schemas/ErrorEnveloped" + $ref: '#/components/schemas/ErrorEnveloped' From 1cdbce1f02c0b6b1eefe4d298ed39d963ae7bde7 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 3 Feb 2021 11:35:14 +0100 Subject: [PATCH 050/200] bad merge --- .../simcore_service_director_v2/api/routes/computations.py | 2 +- services/director-v2/tests/unit/test_dags.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index 651b150f6fc..48f9974937c 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -124,7 +124,7 @@ async def create_computation( complete_dag = create_complete_dag(project.workbench) # find the minimal viable graph to be run computational_dag = await create_minimal_computational_graph_based_on_selection( - full_dag_graph=complete_dag, + complete_dag=complete_dag, selected_nodes=job.subgraph or [], force_restart=job.force_restart, ) diff --git a/services/director-v2/tests/unit/test_dags.py b/services/director-v2/tests/unit/test_dags.py index 349e3aaec87..ad964097b81 100644 --- a/services/director-v2/tests/unit/test_dags.py +++ b/services/director-v2/tests/unit/test_dags.py @@ -149,12 +149,12 @@ async def test_create_minimal_graph( force_exp_dag: Dict[str, List[str]], not_forced_exp_dag: Dict[str, List[str]], ): - full_dag_graph: nx.DiGraph = create_complete_dag(fake_workbench) + complete_dag: nx.DiGraph = create_complete_dag(fake_workbench) # everything is outdated in that case reduced_dag: nx.DiGraph = ( await create_minimal_computational_graph_based_on_selection( - full_dag_graph, subgraph, force_restart=True + complete_dag, subgraph, force_restart=True ) ) assert nx.to_dict_of_lists(reduced_dag) == force_exp_dag @@ -162,7 +162,7 @@ async def test_create_minimal_graph( # only the outdated stuff shall be found here reduced_dag_with_auto_detect: nx.DiGraph = ( await create_minimal_computational_graph_based_on_selection( - full_dag_graph, subgraph, force_restart=False + complete_dag, subgraph, force_restart=False ) ) assert nx.to_dict_of_lists(reduced_dag_with_auto_detect) == not_forced_exp_dag From 099e69589cffdb6c370bac4f519eaa2640262a7c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 3 Feb 2021 11:42:51 +0100 Subject: [PATCH 051/200] fixed invalid SQL call --- .../versions/20ec678d7dad_nullable_project_columns.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py index 110487f2ab2..102eda6222a 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py @@ -8,7 +8,6 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. revision = "20ec678d7dad" down_revision = "99db5efc4548" @@ -27,16 +26,10 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.execute( - sa.update(sa.DDL("UPDATE projects SET thumbnail = '' WHERE thumbnail = NULL")) - ) + op.execute(sa.DDL("UPDATE projects SET thumbnail = '' WHERE thumbnail = NULL")) op.alter_column("projects", "thumbnail", existing_type=sa.VARCHAR(), nullable=False) - op.execute( - sa.update( - sa.DDL("UPDATE projects SET description = '' WHERE description = NULL") - ) - ) + op.execute(sa.DDL("UPDATE projects SET description = '' WHERE description = NULL")) op.alter_column( "projects", "description", existing_type=sa.VARCHAR(), nullable=False ) From 15a6444d078451b4b6bf4eaa70c32a9ced3516fb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 3 Feb 2021 14:12:09 +0100 Subject: [PATCH 052/200] linter --- .../web/server/src/simcore_service_webserver/director_v2.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/director_v2.py b/services/web/server/src/simcore_service_webserver/director_v2.py index 1db5a61fa6c..bf3dfc19817 100644 --- a/services/web/server/src/simcore_service_webserver/director_v2.py +++ b/services/web/server/src/simcore_service_webserver/director_v2.py @@ -3,15 +3,13 @@ from uuid import UUID from aiohttp import ClientTimeout, web -from pydantic.types import PositiveInt -from yarl import URL - from models_library.projects_pipeline import ComputationTask from pydantic.types import PositiveInt from servicelib.application_setup import ModuleCategory, app_module_setup from servicelib.logging_utils import log_decorator from servicelib.rest_responses import wrap_as_envelope from servicelib.rest_routing import iter_path_operations, map_handlers_with_operations +from yarl import URL from .director_v2_settings import ( CONFIG_SECTION_NAME, From 73917ecdb366fa55668d0c1bcfe8205977851d50 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 08:31:49 +0100 Subject: [PATCH 053/200] remove unused code --- .../simcore_service_director_v2/utils/dags.py | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index 37df07ec110..4cb02f54d90 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -24,47 +24,6 @@ def _is_node_computational(node_key: str) -> bool: return to_node_class(node_key) == NodeClass.COMPUTATIONAL -def _mark_node_as_dirty( - nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID -): - nodes_data_view[str(node_id)]["dirty"] = True - - -def _is_node_dirty( - nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID -) -> bool: - return nodes_data_view[str(node_id)].get("dirty", False) - - -async def _is_node_outdated( - nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID -) -> bool: - """this function will return whether a node is outdated: - - if it has no outputs - - if one of the output ports in the outputs is missing - - if, after *resolving the inputs if linked to other nodes* these nodes are dirty (outdated themselves) - - if the last run_hash does not fit with the current one - """ - node = nodes_data_view[str(node_id)] - # if the node has no output it is outdated for sure - if not node["outputs"]: - return True - for output_port in node["outputs"]: - if output_port is None: - return True - # check if the previous node (if any) are dirty... in which case this one is too - for input_port in node["inputs"].values(): - if isinstance(input_port, PortLink): - if _is_node_dirty(nodes_data_view, input_port.node_uuid): - return True - # maybe our inputs changed? let's compute the node hash and compare with the saved one - async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: - return nodes_data_view[str(node_id)] - - computed_hash = await compute_node_hash(node_id, get_node_io_payload_cb) - return computed_hash != node["run_hash"] - - @log_decorator(logger=logger) def create_complete_dag(workbench: Workbench) -> nx.DiGraph: """creates a complete graph out of the project workbench""" From bd847d2d9110e82ed8cd3c75387ceb85b974ef98 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 08:34:28 +0100 Subject: [PATCH 054/200] fix node hashes after import --- .../exporter/formatters/formatter_v1.py | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index f2616454a4a..bd697f48230 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -5,11 +5,14 @@ from collections import deque from itertools import chain from pathlib import Path -from typing import Deque, Dict +from typing import Any, Deque, Dict import aiofiles from aiohttp import ClientSession, ClientTimeout, web from models_library.projects import AccessRights, Project +from models_library.projects_nodes_io import NodeID +from models_library.projects_state import RunningState +from models_library.utils.nodes import compute_node_hash from simcore_service_webserver.director_v2 import create_or_update_pipeline from simcore_service_webserver.projects.projects_api import ( delete_project, @@ -214,6 +217,26 @@ async def add_new_project(app: web.Application, project: Project, user_id: int): await create_or_update_pipeline(app, user_id, project.uuid) +async def _fix_node_run_hashes_based_on_states(project: Project) -> None: + async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: + node_io_payload = {"inputs": None, "outputs": None} + node = project.workbench.get(str(node_id)) + if node: + node_io_payload = node.dict( + include={"inputs", "outputs"}, + by_alias=True, + exclude_unset=True, + ) + + return node_io_payload + + for node_id, node in project.workbench.items(): + if node.state == RunningState.SUCCESS: + # this node run hash shall be re-computed + new_node_run_hash = await compute_node_hash(node_id, get_node_io_payload_cb) + node.run_hash = new_node_run_hash + + async def import_files_and_validate_project( app: web.Application, user_id: int, root_folder: Path ) -> str: @@ -288,6 +311,7 @@ async def import_files_and_validate_project( project_uuid = str(project.uuid) try: + await _fix_node_run_hashes_based_on_states(project) await add_new_project(app, project, user_id) except Exception as e: log.warning( @@ -298,7 +322,7 @@ async def import_files_and_validate_project( ) try: await delete_project(app=app, project_uuid=project_uuid, user_id=user_id) - except ProjectsException as e: + except ProjectsException: # no need to raise an error here log.exception( "Could not find project %s while trying to revert actions", project_uuid From 70409c8d3d10bfdbb59dcb553b49f367b9b4e044 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 08:39:14 +0100 Subject: [PATCH 055/200] use orjson instead of json --- packages/models-library/src/models_library/utils/nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/models-library/src/models_library/utils/nodes.py b/packages/models-library/src/models_library/utils/nodes.py index 3a130651966..5441d3efa65 100644 --- a/packages/models-library/src/models_library/utils/nodes.py +++ b/packages/models-library/src/models_library/utils/nodes.py @@ -1,10 +1,10 @@ import hashlib -import json import logging from copy import deepcopy from pprint import pformat from typing import Any, Callable, Coroutine, Dict +import orjson from models_library.projects_nodes import NodeID from models_library.projects_nodes_io import PortLink from pydantic import BaseModel @@ -47,9 +47,9 @@ async def compute_node_hash( if payload is not None: resolved_payload[port_type][port_key] = payload - # now create the hash + # now create the hash using orjson since there might be UUIDs in the Node logger.debug("io_payload generated is %s", pformat(resolved_payload)) - block_string = json.dumps(resolved_payload, sort_keys=True).encode("utf-8") + block_string = orjson.dumps(resolved_payload, option=orjson.OPT_SORT_KEYS) logger.debug("block string generated is %s", block_string) raw_hash = hashlib.sha256(block_string) logger.debug("generated hash %s", raw_hash) From 1d852010a14e20bceb40fd99616b013c91081c7a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 09:03:12 +0100 Subject: [PATCH 056/200] pin pip-tools version to 5.4.0 --- requirements/devenv.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements/devenv.txt b/requirements/devenv.txt index cda9642648b..995c5043b88 100644 --- a/requirements/devenv.txt +++ b/requirements/devenv.txt @@ -20,7 +20,9 @@ black isort # dependency manager -pip-tools +# last version completely changes the output formatting of requirements files +# [https://github.com/jazzband/pip-tools/pull/1237] there is currently no way to disable it +pip-tools==5.4.0 # version manager bump2version From a4a9a738018514fe04570a3d056b1163d0205db6 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 09:07:57 +0100 Subject: [PATCH 057/200] added orjson to models-library --- packages/models-library/requirements/_base.in | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/models-library/requirements/_base.in b/packages/models-library/requirements/_base.in index c0592c46c0d..46ec9192494 100644 --- a/packages/models-library/requirements/_base.in +++ b/packages/models-library/requirements/_base.in @@ -3,4 +3,5 @@ # -c ../../../requirements/constraints.txt +orjson pydantic[email] From e567d89c04b554ae8c48de338b2f76f07003ff83 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 09:18:54 +0100 Subject: [PATCH 058/200] orjson does not set the space between the fields --- packages/models-library/tests/test_utils_nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/models-library/tests/test_utils_nodes.py b/packages/models-library/tests/test_utils_nodes.py index 4b92301aefb..242d6b17a36 100644 --- a/packages/models-library/tests/test_utils_nodes.py +++ b/packages/models-library/tests/test_utils_nodes.py @@ -26,11 +26,11 @@ def node_id() -> NodeID: [ ( {"inputs": None, "outputs": None}, - "6c4d5b04b166697ef6eddc63d6c1ee4092dccf8af91a0f7efc55bc423984ea5a", + "23db227371a8c18f25fcb51fef16a74b6ba4136c7988b8da50f17fc5fcff8524", ), ( {"inputs": {}, "outputs": {}}, - "d98878dbcffbb908ee6d96d3ca87cc0c083f75683488c50ce9c945bef0588047", + "3e8b860b6c32dc75b859f3d59c56dfcc0410bacdc623eb3d0d90f36d8720efb0", ), ( { @@ -58,7 +58,7 @@ def node_id() -> NodeID: ), }, }, - "829cb6d6e62142b27d0a07f56f279bdf68fa9ddcb6e61fdc0a77f2d2e9a18752", + "8acedaf90992acdc0c7d9b6774448206042b424cb9569d06a30519225d1b9e22", ), ], ) From b93072158352c1d8dd8021b7d818bdf5ebf0b5a2 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 10:12:46 +0100 Subject: [PATCH 059/200] adjust workbench to orjson not having spaces --- packages/simcore-sdk/tests/mock/default_config.json | 2 +- services/director-v2/tests/mocks/fake_workbench.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/simcore-sdk/tests/mock/default_config.json b/packages/simcore-sdk/tests/mock/default_config.json index 1f52097f531..715326b5c99 100644 --- a/packages/simcore-sdk/tests/mock/default_config.json +++ b/packages/simcore-sdk/tests/mock/default_config.json @@ -56,5 +56,5 @@ "path": "/simcore/outputControllerOut.dat" } }, - "run_hash": "08e7efdeaffd57d453ab458bffdedb8bfbbca0cb2312772a555044162b3c2e59" + "run_hash": "ec9f455ecc0a2a39668ef1b7806ef1f914a482ee45645cb19e13ed136cc46ad3" } diff --git a/services/director-v2/tests/mocks/fake_workbench.json b/services/director-v2/tests/mocks/fake_workbench.json index 74c9f06a4f7..a9d20e260dc 100644 --- a/services/director-v2/tests/mocks/fake_workbench.json +++ b/services/director-v2/tests/mocks/fake_workbench.json @@ -40,7 +40,7 @@ "eTag": "3029u20fdlskjhddfssdlkj" } }, - "runHash": "d8c418ad1a720861949017920a6676682adce13a87b7712c857f2eef927a06dd" + "runHash": "c2032ce676602599f449a9dda7e78693dd23f30469309aebbfc34755db7f73e8" }, "415fefd1-d08b-53c1-adb0-16bed3a687ef": { "key": "simcore/services/comp/itis/sleeper", From 57f6017b8e9d802e777fef20a7a8800d2fd72e6c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 10:47:24 +0100 Subject: [PATCH 060/200] improving how test comparison is done --- .../server/tests/integration/test_exporter.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/services/web/server/tests/integration/test_exporter.py b/services/web/server/tests/integration/test_exporter.py index 5eb6d383434..a3c607caff9 100644 --- a/services/web/server/tests/integration/test_exporter.py +++ b/services/web/server/tests/integration/test_exporter.py @@ -69,6 +69,7 @@ "uuid", "creation_date", "last_change_date", + "runHash", REMAPPING_KEY, } @@ -283,10 +284,19 @@ def replace_uuids_with_sequences(original_project: Dict[str, Any]) -> Dict[str, def dict_without_keys(dict_data: Dict[str, Any], keys: Set[str]) -> Dict[str, Any]: - result = deepcopy(dict_data) - for key in keys: - result.pop(key, None) - return result + def _delete_keys_from_dict( + dictionary: Dict[str, Any], keys: Set[str] + ) -> Dict[str, Any]: + modified_dict = {} + for key, value in dictionary.items(): + if key not in keys: + if isinstance(value, dict): + modified_dict[key] = _delete_keys_from_dict(value, keys) + else: + modified_dict[key] = deepcopy(value) + return modified_dict + + return _delete_keys_from_dict(dict_data, keys) def assert_combined_entires_condition( From 48bf949639d0175d0d311db868eaf05d987a51d2 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 21:59:06 +0100 Subject: [PATCH 061/200] comment --- .../src/simcore_service_webserver/projects/projects_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py index 69b83934c8a..edab7936de9 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_handlers.py @@ -93,7 +93,7 @@ async def create_projects(request: web.Request): project = await db.add_project( project, user_id, force_as_template=as_template is not None ) - # This is a new project and every new graph needs to be reflected in the pipeline db + # This is a new project and every new graph needs to be reflected in the pipeline tables await director_v2.create_or_update_pipeline( request.app, user_id, project["uuid"] ) From cad74299fb90a651b1deb625bc305828d9684509 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 22:15:28 +0100 Subject: [PATCH 062/200] @GitHK: This eTag in the imported project is wrong. it is the old one. you need to replace it. --- .../exporter/formatters/formatter_v1.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index bd697f48230..4281046fbd4 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -158,12 +158,15 @@ async def generate_directory_contents( await ProjectFile.model_to_file(root_dir=root_folder, **project_data) +ETag = str + + async def upload_file_to_storage( app: web.Application, link_and_path: LinkAndPath2, user_id: int, session: ClientSession, -) -> None: +) -> ETag: try: upload_url = await get_file_upload_url( app=app, @@ -194,8 +197,11 @@ async def file_sender(file_name=None): raise ExporterException( f"Client replied with status={resp.status} and body '{upload_result}'" ) - - log.debug("Upload status=%s, result: '%s'", resp.status, upload_result) + e_tag = resp.headers.get("Etag", None) + log.debug( + "Upload status=%s, result: '%s', Etag %s", resp.status, upload_result, e_tag + ) + return e_tag async def add_new_project(app: web.Application, project: Project, user_id: int): @@ -291,6 +297,9 @@ async def import_files_and_validate_project( ) await asyncio.gather(*run_in_parallel) + # FIXME: @GitHK: the eTag changes since this is a new upload. Can you please fix this one? I modified the upload + # function such that it returns it now. + # finally create and add the project project = Project( uuid=shuffled_project_file.uuid, From 16fe23e60635ee0b563bf348abbdf37bd6d6ba0f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:01:53 +0100 Subject: [PATCH 063/200] moved ProjectAtDB to the models_library --- .../src/models_library/projects.py | 83 ++++++++++++++----- .../api/routes/computations.py | 3 +- .../models/domains/projects.py | 36 -------- .../modules/db/repositories/comp_tasks.py | 3 +- .../modules/db/repositories/projects.py | 3 +- 5 files changed, 66 insertions(+), 62 deletions(-) delete mode 100644 services/director-v2/src/simcore_service_director_v2/models/domains/projects.py diff --git a/packages/models-library/src/models_library/projects.py b/packages/models-library/src/models_library/projects.py index 1b0398a8dd1..6fae47000e1 100644 --- a/packages/models-library/src/models_library/projects.py +++ b/packages/models-library/src/models_library/projects.py @@ -2,6 +2,8 @@ Models a study's project document """ from copy import deepcopy +from datetime import datetime +from enum import Enum from typing import Any, Dict, List, Optional from uuid import UUID @@ -21,7 +23,19 @@ Workbench = Dict[NodeID_AsDictKey, Node] -class Project(BaseModel): +# NOTE: careful this is in sync with packages/postgres-database/src/simcore_postgres_database/models/projects.py!!! +class ProjectType(str, Enum): + """ + template: template project + standard: standard project + """ + + TEMPLATE = "TEMPLATE" + STANDARD = "STANDARD" + + +class ProjectCommons(BaseModel): + # Description of the project uuid: ProjectID = Field( ..., description="project unique identifier", @@ -30,8 +44,6 @@ class Project(BaseModel): "9bcf8feb-c1b1-41b6-b201-639cd6ccdba8", ], ) - - # Description of the project name: str = Field( ..., description="project name", examples=["Temporal Distortion Simulator"] ) @@ -46,25 +58,66 @@ class Project(BaseModel): examples=["https://placeimg.com/171/96/tech/grayscale/?0.jpg"], ) - # Ownership and Access (SEE projects_access.py) - prjOwner: EmailStr = Field(..., description="user email") - accessRights: Dict[GroupID, AccessRights] = Field( - ..., - description="object containing the GroupID as key and read/write/execution permissions as value", + creation_date: datetime = Field(...) + last_change_date: datetime = Field(...) + + # Pipeline of nodes (SEE projects_nodes.py) + workbench: Workbench = ... + + @validator("thumbnail", always=True, pre=True) + @classmethod + def convert_empty_str_to_none(v): + if isinstance(v, str) and v == "": + return None + return v + + +class ProjectAtDB(ProjectCommons): + # specific DB fields + id: int = Field(..., description="The table primary index") + project_type: ProjectType = Field(..., alias="type", description="The project type") + prj_owner: Optional[int] = Field(..., description="The project owner id") + published: Optional[bool] = Field( + False, description="Defines if a study is available publicly" ) + @validator("project_type", pre=True) + @classmethod + def convert_sql_alchemy_enum(v): + if isinstance(v, Enum): + return v.value + return v + + class Config: + orm_mode = True + use_enum_values = True + + +class Project(ProjectCommons): + # NOTE: This is the pydantic pendant of project-v0.0.1.json used in the API of the webserver/webclient + # NOT for usage with DB!! + + # Ownership and Access (SEE projects_access.py) + prj_owner: EmailStr = Field(..., description="user email", alias="prjOwner") + # Timestamps TODO: should we use datetime?? - creationDate: str = Field( + creation_date: str = Field( ..., regex=DATE_RE, description="project creation date", examples=["2018-07-01T11:13:43Z"], + alias="creationDate", ) - lastChangeDate: str = Field( + last_change_date: str = Field( ..., regex=DATE_RE, description="last save date", examples=["2018-07-01T11:13:43Z"], + alias="lastChangeDate", + ) + accessRights: Dict[GroupID, AccessRights] = Field( + ..., + description="object containing the GroupID as key and read/write/execution permissions as value", ) # Classification @@ -75,9 +128,6 @@ class Project(BaseModel): examples=["some:id:to:a:classifier"], ) - # Pipeline of nodes (SEE projects_nodes.py) - workbench: Workbench = ... - # Project state (SEE projects_state.py) state: Optional[ProjectState] = None @@ -92,13 +142,6 @@ class Project(BaseModel): # Dev only dev: Optional[Dict] = Field(description="object used for development purposes only") - @validator("thumbnail", pre=True, always=True) - @classmethod - def null_thumbnail(cls, v): - if isinstance(v, str) and v == "": - return None - return v - class Config: description = "Document that stores metadata, pipeline and UI setup of a study" title = "osparc-simcore project" diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index 48f9974937c..07d8eea55d5 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -3,7 +3,7 @@ import networkx as nx from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException -from models_library.projects import ProjectID +from models_library.projects import ProjectAtDB, ProjectID from models_library.projects_state import RunningState from simcore_service_director_v2.models.domains.comp_pipelines import CompPipelineAtDB from starlette import status @@ -17,7 +17,6 @@ ) from ...models.domains.comp_tasks import CompTaskAtDB -from ...models.domains.projects import ProjectAtDB from ...models.schemas.comp_tasks import ( ComputationTaskCreate, ComputationTaskDelete, diff --git a/services/director-v2/src/simcore_service_director_v2/models/domains/projects.py b/services/director-v2/src/simcore_service_director_v2/models/domains/projects.py deleted file mode 100644 index ecf5fba17ca..00000000000 --- a/services/director-v2/src/simcore_service_director_v2/models/domains/projects.py +++ /dev/null @@ -1,36 +0,0 @@ -from datetime import datetime -from typing import Dict, Optional - -from models_library.projects import Project, Workbench -from pydantic import Field -from pydantic.class_validators import validator -from pydantic.networks import HttpUrl -from simcore_postgres_database.models.projects import ProjectType - - -class ProjectAtDB(Project): - id: int - project_type: ProjectType = Field(..., alias="type") - prjOwner: Optional[int] = Field(..., alias="prj_owner") - accessRights: Dict = Field(..., alias="access_rights") - creationDate: datetime = Field( - ..., - alias="creation_date", - ) - lastChangeDate: datetime = Field( - ..., - alias="last_change_date", - ) - published: Optional[bool] = Field(False) - thumbnail: Optional[HttpUrl] = Field(None) - workbench: Workbench - - @validator("thumbnail", always=True, pre=True) - @classmethod - def convert_empty_str_to_none(v): - if isinstance(v, str) and v == "": - return None - return v - - class Config: - orm_mode = True diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py index 3bf19c479aa..4ab0366833a 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py @@ -4,7 +4,7 @@ import sqlalchemy as sa from aiopg.sa.result import RowProxy -from models_library.projects import ProjectID +from models_library.projects import ProjectAtDB, ProjectID from models_library.projects_nodes import Node from models_library.projects_nodes_io import NodeID from models_library.projects_state import RunningState @@ -17,7 +17,6 @@ from sqlalchemy.dialects.postgresql import insert from ....models.domains.comp_tasks import CompTaskAtDB, Image, NodeSchema -from ....models.domains.projects import ProjectAtDB from ....models.schemas.services import NodeRequirement, ServiceExtras from ....utils.async_utils import run_sequentially_in_context from ....utils.computations import to_node_class diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/projects.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/projects.py index 44477995b25..ed57dc0bf1d 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/projects.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/projects.py @@ -2,9 +2,8 @@ import sqlalchemy as sa from aiopg.sa.result import RowProxy -from models_library.projects import ProjectID +from models_library.projects import ProjectAtDB, ProjectID -from ....models.domains.projects import ProjectAtDB from ....utils.exceptions import ProjectNotFoundError from ..tables import projects from ._base import BaseRepository From 0a8618bf6484aa023dec5efb29c5fd4d3ab724b8 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:02:54 +0100 Subject: [PATCH 064/200] typo --- packages/models-library/src/models_library/projects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models-library/src/models_library/projects.py b/packages/models-library/src/models_library/projects.py index 6fae47000e1..fdc41883b6a 100644 --- a/packages/models-library/src/models_library/projects.py +++ b/packages/models-library/src/models_library/projects.py @@ -83,7 +83,7 @@ class ProjectAtDB(ProjectCommons): @validator("project_type", pre=True) @classmethod - def convert_sql_alchemy_enum(v): + def convert_sql_alchemy_enum(cls, v): if isinstance(v, Enum): return v.value return v From 3ef013951473b586109f9c4172db2dd55220033d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:06:17 +0100 Subject: [PATCH 065/200] fix model to use python snake case --- packages/models-library/src/models_library/projects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/models-library/src/models_library/projects.py b/packages/models-library/src/models_library/projects.py index fdc41883b6a..4079dcf3086 100644 --- a/packages/models-library/src/models_library/projects.py +++ b/packages/models-library/src/models_library/projects.py @@ -115,9 +115,10 @@ class Project(ProjectCommons): examples=["2018-07-01T11:13:43Z"], alias="lastChangeDate", ) - accessRights: Dict[GroupID, AccessRights] = Field( + access_rights: Dict[GroupID, AccessRights] = Field( ..., description="object containing the GroupID as key and read/write/execution permissions as value", + alias="accessRights", ) # Classification From 4311fb38b80f3f9f85060118fa59ac34c55da6ab Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:28:31 +0100 Subject: [PATCH 066/200] fixing import --- services/director-v2/tests/integration/test_computation_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 6dd30d0369c..53a37bb23fc 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -15,6 +15,7 @@ import pytest import sqlalchemy as sa +from models_library.projects import ProjectAtDB from models_library.projects_nodes import NodeIOState, NodeRunnableState, NodeState from models_library.projects_nodes_io import NodeID from models_library.projects_pipeline import PipelineDetails @@ -26,7 +27,6 @@ from simcore_postgres_database.models.comp_tasks import comp_tasks from simcore_postgres_database.models.projects import ProjectType, projects from simcore_postgres_database.models.users import UserRole, UserStatus, users -from simcore_service_director_v2.models.domains.projects import ProjectAtDB from simcore_service_director_v2.models.schemas.comp_tasks import ComputationTaskOut from sqlalchemy import literal_column from starlette import status From 48247afd0a1552fbee38aa6306879d725b4dd3fd Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:32:05 +0100 Subject: [PATCH 067/200] fixed generation of the payload --- .../exporter/formatters/formatter_v1.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index 4281046fbd4..1dc9483a100 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -228,11 +228,7 @@ async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: node_io_payload = {"inputs": None, "outputs": None} node = project.workbench.get(str(node_id)) if node: - node_io_payload = node.dict( - include={"inputs", "outputs"}, - by_alias=True, - exclude_unset=True, - ) + node_io_payload = {"inputs": node.inputs, "outputs": node.outputs} return node_io_payload From 400c8f04c32d65e5cfb8ac6c6e076d786a40e1e5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Thu, 4 Feb 2021 23:53:06 +0100 Subject: [PATCH 068/200] @pcrespov bonus: sidecar says which mode it uses in the logo --- services/sidecar/src/simcore_service_sidecar/celery.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/sidecar/src/simcore_service_sidecar/celery.py b/services/sidecar/src/simcore_service_sidecar/celery.py index b59766235bc..1630e9b6eec 100644 --- a/services/sidecar/src/simcore_service_sidecar/celery.py +++ b/services/sidecar/src/simcore_service_sidecar/celery.py @@ -3,6 +3,7 @@ from celery.signals import worker_ready, worker_shutting_down from .__version__ import __version__ +from .boot_mode import get_boot_mode from .celery_configurator import create_celery_app from .celery_task_utils import cancel_task from .cli import run_sidecar @@ -23,9 +24,9 @@ '..`''.) | |(_/| | ' |(| '--. /_) |OO )\| |_.' | | |_.' | .-._) \ ,| |_.'| | / : | .--' || |`-'| | .-. | | . '.' \ /(_| | | '--' / | `---.(_' '--'\ | | | | | |\ \ - `-----' `--' `-------' `------' `-----' `--' `--' `--' '--' {0} + `-----' `--' `-------' `------' `-----' `--' `--' `--' '--' {0} - {1} """.format( - __version__ + __version__, get_boot_mode().value ) From 99196280847ed166ff0cd061e422543e61d711fc Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 5 Feb 2021 00:01:27 +0100 Subject: [PATCH 069/200] final fix to the migration so that it works correctly --- .../versions/20ec678d7dad_nullable_project_columns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py index 102eda6222a..a41b322bce9 100644 --- a/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/20ec678d7dad_nullable_project_columns.py @@ -26,10 +26,10 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.execute(sa.DDL("UPDATE projects SET thumbnail = '' WHERE thumbnail = NULL")) + op.execute(sa.DDL("UPDATE projects SET thumbnail = '' WHERE thumbnail IS NULL")) op.alter_column("projects", "thumbnail", existing_type=sa.VARCHAR(), nullable=False) - op.execute(sa.DDL("UPDATE projects SET description = '' WHERE description = NULL")) + op.execute(sa.DDL("UPDATE projects SET description = '' WHERE description IS NULL")) op.alter_column( "projects", "description", existing_type=sa.VARCHAR(), nullable=False ) From ab5ad4f80dcdba579f847a3aad7c0a8a786b1beb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 5 Feb 2021 08:22:18 +0100 Subject: [PATCH 070/200] reverted orjson --- packages/models-library/requirements/_base.in | 1 - packages/models-library/src/models_library/utils/nodes.py | 6 +++--- packages/models-library/tests/test_utils_nodes.py | 6 +++--- packages/simcore-sdk/tests/mock/default_config.json | 2 +- requirements/devenv.txt | 4 +--- services/director-v2/tests/mocks/fake_workbench.json | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/models-library/requirements/_base.in b/packages/models-library/requirements/_base.in index 46ec9192494..c0592c46c0d 100644 --- a/packages/models-library/requirements/_base.in +++ b/packages/models-library/requirements/_base.in @@ -3,5 +3,4 @@ # -c ../../../requirements/constraints.txt -orjson pydantic[email] diff --git a/packages/models-library/src/models_library/utils/nodes.py b/packages/models-library/src/models_library/utils/nodes.py index 5441d3efa65..3a130651966 100644 --- a/packages/models-library/src/models_library/utils/nodes.py +++ b/packages/models-library/src/models_library/utils/nodes.py @@ -1,10 +1,10 @@ import hashlib +import json import logging from copy import deepcopy from pprint import pformat from typing import Any, Callable, Coroutine, Dict -import orjson from models_library.projects_nodes import NodeID from models_library.projects_nodes_io import PortLink from pydantic import BaseModel @@ -47,9 +47,9 @@ async def compute_node_hash( if payload is not None: resolved_payload[port_type][port_key] = payload - # now create the hash using orjson since there might be UUIDs in the Node + # now create the hash logger.debug("io_payload generated is %s", pformat(resolved_payload)) - block_string = orjson.dumps(resolved_payload, option=orjson.OPT_SORT_KEYS) + block_string = json.dumps(resolved_payload, sort_keys=True).encode("utf-8") logger.debug("block string generated is %s", block_string) raw_hash = hashlib.sha256(block_string) logger.debug("generated hash %s", raw_hash) diff --git a/packages/models-library/tests/test_utils_nodes.py b/packages/models-library/tests/test_utils_nodes.py index 242d6b17a36..4b92301aefb 100644 --- a/packages/models-library/tests/test_utils_nodes.py +++ b/packages/models-library/tests/test_utils_nodes.py @@ -26,11 +26,11 @@ def node_id() -> NodeID: [ ( {"inputs": None, "outputs": None}, - "23db227371a8c18f25fcb51fef16a74b6ba4136c7988b8da50f17fc5fcff8524", + "6c4d5b04b166697ef6eddc63d6c1ee4092dccf8af91a0f7efc55bc423984ea5a", ), ( {"inputs": {}, "outputs": {}}, - "3e8b860b6c32dc75b859f3d59c56dfcc0410bacdc623eb3d0d90f36d8720efb0", + "d98878dbcffbb908ee6d96d3ca87cc0c083f75683488c50ce9c945bef0588047", ), ( { @@ -58,7 +58,7 @@ def node_id() -> NodeID: ), }, }, - "8acedaf90992acdc0c7d9b6774448206042b424cb9569d06a30519225d1b9e22", + "829cb6d6e62142b27d0a07f56f279bdf68fa9ddcb6e61fdc0a77f2d2e9a18752", ), ], ) diff --git a/packages/simcore-sdk/tests/mock/default_config.json b/packages/simcore-sdk/tests/mock/default_config.json index 715326b5c99..1f52097f531 100644 --- a/packages/simcore-sdk/tests/mock/default_config.json +++ b/packages/simcore-sdk/tests/mock/default_config.json @@ -56,5 +56,5 @@ "path": "/simcore/outputControllerOut.dat" } }, - "run_hash": "ec9f455ecc0a2a39668ef1b7806ef1f914a482ee45645cb19e13ed136cc46ad3" + "run_hash": "08e7efdeaffd57d453ab458bffdedb8bfbbca0cb2312772a555044162b3c2e59" } diff --git a/requirements/devenv.txt b/requirements/devenv.txt index 995c5043b88..cda9642648b 100644 --- a/requirements/devenv.txt +++ b/requirements/devenv.txt @@ -20,9 +20,7 @@ black isort # dependency manager -# last version completely changes the output formatting of requirements files -# [https://github.com/jazzband/pip-tools/pull/1237] there is currently no way to disable it -pip-tools==5.4.0 +pip-tools # version manager bump2version diff --git a/services/director-v2/tests/mocks/fake_workbench.json b/services/director-v2/tests/mocks/fake_workbench.json index a9d20e260dc..74c9f06a4f7 100644 --- a/services/director-v2/tests/mocks/fake_workbench.json +++ b/services/director-v2/tests/mocks/fake_workbench.json @@ -40,7 +40,7 @@ "eTag": "3029u20fdlskjhddfssdlkj" } }, - "runHash": "c2032ce676602599f449a9dda7e78693dd23f30469309aebbfc34755db7f73e8" + "runHash": "d8c418ad1a720861949017920a6676682adce13a87b7712c857f2eef927a06dd" }, "415fefd1-d08b-53c1-adb0-16bed3a687ef": { "key": "simcore/services/comp/itis/sleeper", From b90c0abe496a81bb668d60c7de7736ac4e409636 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 5 Feb 2021 09:09:31 +0100 Subject: [PATCH 071/200] correct run_hash based on old_hash --- .../src/models_library/utils/nodes.py | 22 ++++++++- .../exporter/formatters/formatter_v1.py | 49 ++++++++++++------- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/packages/models-library/src/models_library/utils/nodes.py b/packages/models-library/src/models_library/utils/nodes.py index 3a130651966..50681b44f08 100644 --- a/packages/models-library/src/models_library/utils/nodes.py +++ b/packages/models-library/src/models_library/utils/nodes.py @@ -5,13 +5,31 @@ from pprint import pformat from typing import Any, Callable, Coroutine, Dict -from models_library.projects_nodes import NodeID -from models_library.projects_nodes_io import PortLink from pydantic import BaseModel +from ..projects import Project +from ..projects_nodes import NodeID +from ..projects_nodes_io import PortLink + logger = logging.getLogger(__name__) +def project_node_io_payload_cb( + project: Project, +) -> Callable[[NodeID], Coroutine[Any, Any, Dict[str, Any]]]: + """callback fct to use together with compute_node_hash when a Project as input""" + + async def node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: + node_io_payload = {"inputs": None, "outputs": None} + node = project.workbench.get(str(node_id)) + if node: + node_io_payload = {"inputs": node.inputs, "outputs": node.outputs} + + return node_io_payload + + return node_io_payload_cb + + async def compute_node_hash( node_id: NodeID, get_node_io_payload_cb: Callable[[NodeID], Coroutine[Any, Any, Dict[str, Any]]], diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index 1dc9483a100..f782e350fa8 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -5,14 +5,13 @@ from collections import deque from itertools import chain from pathlib import Path -from typing import Any, Deque, Dict +from typing import Deque, Dict import aiofiles from aiohttp import ClientSession, ClientTimeout, web from models_library.projects import AccessRights, Project from models_library.projects_nodes_io import NodeID -from models_library.projects_state import RunningState -from models_library.utils.nodes import compute_node_hash +from models_library.utils.nodes import compute_node_hash, project_node_io_payload_cb from simcore_service_webserver.director_v2 import create_or_update_pipeline from simcore_service_webserver.projects.projects_api import ( delete_project, @@ -223,20 +222,32 @@ async def add_new_project(app: web.Application, project: Project, user_id: int): await create_or_update_pipeline(app, user_id, project.uuid) -async def _fix_node_run_hashes_based_on_states(project: Project) -> None: - async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: - node_io_payload = {"inputs": None, "outputs": None} - node = project.workbench.get(str(node_id)) - if node: - node_io_payload = {"inputs": node.inputs, "outputs": node.outputs} - - return node_io_payload - - for node_id, node in project.workbench.items(): - if node.state == RunningState.SUCCESS: - # this node run hash shall be re-computed - new_node_run_hash = await compute_node_hash(node_id, get_node_io_payload_cb) - node.run_hash = new_node_run_hash +async def _fix_node_run_hashes_based_on_old_project( + project: Project, original_project: Project, node_mapping: Dict[NodeID, NodeID] +) -> None: + for old_node_id, old_node in original_project.workbench.items(): + new_node_id = node_mapping.get(old_node_id) + if new_node_id is None: + # this should not happen + continue + new_node = project.workbench.get(new_node_id) + if new_node is None: + # this should also not happen + continue + + # check the node status in the old project + old_computed_hash = await compute_node_hash( + old_node_id, project_node_io_payload_cb(original_project) + ) + node_needs_update = old_computed_hash != old_node.run_hash + # set the new node hash + new_node.run_hash = ( + None + if node_needs_update + else await compute_node_hash( + new_node_id, project_node_io_payload_cb(project) + ) + ) async def import_files_and_validate_project( @@ -316,7 +327,9 @@ async def import_files_and_validate_project( project_uuid = str(project.uuid) try: - await _fix_node_run_hashes_based_on_states(project) + await _fix_node_run_hashes_based_on_old_project( + project, project_file, shuffled_data + ) await add_new_project(app, project, user_id) except Exception as e: log.warning( From 87daf861a5fe3825ccbada1c5ca6f2870516327a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 5 Feb 2021 10:13:40 +0100 Subject: [PATCH 072/200] fix the project eTags --- .../exporter/formatters/formatter_v1.py | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index f782e350fa8..b96d4d12903 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -5,12 +5,12 @@ from collections import deque from itertools import chain from pathlib import Path -from typing import Deque, Dict +from typing import Deque, Dict, List, Tuple import aiofiles from aiohttp import ClientSession, ClientTimeout, web from models_library.projects import AccessRights, Project -from models_library.projects_nodes_io import NodeID +from models_library.projects_nodes_io import BaseFileLink, NodeID from models_library.utils.nodes import compute_node_hash, project_node_io_payload_cb from simcore_service_webserver.director_v2 import create_or_update_pipeline from simcore_service_webserver.projects.projects_api import ( @@ -165,7 +165,7 @@ async def upload_file_to_storage( link_and_path: LinkAndPath2, user_id: int, session: ClientSession, -) -> ETag: +) -> Tuple[LinkAndPath2, ETag]: try: upload_url = await get_file_upload_url( app=app, @@ -200,7 +200,7 @@ async def file_sender(file_name=None): log.debug( "Upload status=%s, result: '%s', Etag %s", resp.status, upload_result, e_tag ) - return e_tag + return (link_and_path, e_tag) async def add_new_project(app: web.Application, project: Project, user_id: int): @@ -250,6 +250,33 @@ async def _fix_node_run_hashes_based_on_old_project( ) +async def _fix_file_e_tags( + project: Project, links_to_etags: List[Tuple[LinkAndPath2, ETag]] +) -> None: + for link_and_path, e_tag in links_to_etags: + file_path = link_and_path.relative_path_to_file + if len(file_path.parts) < 3: + log.warning( + "fixing eTag while importing issue: the path is not expected, skipping %s", + file_path, + ) + continue + node_id = file_path.parts[-2] + + # now try to fix the eTag if any + node = project.workbench.get(node_id) + if node is None: + log.warning( + "node %s could not be found in project, skipping eTag fix", + node_id, + ) + continue + # find the file in the outputs if any + for output in node.outputs.values(): + if isinstance(output, BaseFileLink) and output.path == str(file_path): + output.e_tag = e_tag + + async def import_files_and_validate_project( app: web.Application, user_id: int, root_folder: Path ) -> str: @@ -302,10 +329,7 @@ async def import_files_and_validate_project( session=session, ) ) - await asyncio.gather(*run_in_parallel) - - # FIXME: @GitHK: the eTag changes since this is a new upload. Can you please fix this one? I modified the upload - # function such that it returns it now. + links_to_new_e_tags = await asyncio.gather(*run_in_parallel) # finally create and add the project project = Project( @@ -330,6 +354,7 @@ async def import_files_and_validate_project( await _fix_node_run_hashes_based_on_old_project( project, project_file, shuffled_data ) + await _fix_file_e_tags(project, links_to_new_e_tags) await add_new_project(app, project, user_id) except Exception as e: log.warning( From c408cbb22c3ba52a33cb20d3ac5f3f6ff60d2393 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 5 Feb 2021 10:16:34 +0100 Subject: [PATCH 073/200] skip eTag from comparison --- services/web/server/tests/integration/test_exporter.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/web/server/tests/integration/test_exporter.py b/services/web/server/tests/integration/test_exporter.py index a3c607caff9..75b2f604686 100644 --- a/services/web/server/tests/integration/test_exporter.py +++ b/services/web/server/tests/integration/test_exporter.py @@ -69,8 +69,9 @@ "uuid", "creation_date", "last_change_date", - "runHash", REMAPPING_KEY, + "runHash", # this changes after import, but the runnable states should remain the same + "eTag", # this must change } @@ -283,7 +284,9 @@ def replace_uuids_with_sequences(original_project: Dict[str, Any]) -> Dict[str, return project -def dict_without_keys(dict_data: Dict[str, Any], keys: Set[str]) -> Dict[str, Any]: +def dict_without_keys( + dict_data: Dict[str, Any], skipped_keys: Set[str] +) -> Dict[str, Any]: def _delete_keys_from_dict( dictionary: Dict[str, Any], keys: Set[str] ) -> Dict[str, Any]: @@ -296,7 +299,7 @@ def _delete_keys_from_dict( modified_dict[key] = deepcopy(value) return modified_dict - return _delete_keys_from_dict(dict_data, keys) + return _delete_keys_from_dict(dict_data, skipped_keys) def assert_combined_entires_condition( From 334f460798c68b0a0f5a94eb8c0c5b4c8763a629 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Fri, 5 Feb 2021 11:56:00 +0100 Subject: [PATCH 074/200] do not check runnableState? --- services/web/server/tests/integration/test_exporter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/server/tests/integration/test_exporter.py b/services/web/server/tests/integration/test_exporter.py index 75b2f604686..1287005b730 100644 --- a/services/web/server/tests/integration/test_exporter.py +++ b/services/web/server/tests/integration/test_exporter.py @@ -72,6 +72,7 @@ REMAPPING_KEY, "runHash", # this changes after import, but the runnable states should remain the same "eTag", # this must change + "runnableState", # this should actually not be in the DB } From cdbacbb3412caf2ea116f1ac79d62a1596513f66 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 11:06:15 +0100 Subject: [PATCH 075/200] @GitHK review: remove inner fct --- .../server/tests/integration/test_exporter.py | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/services/web/server/tests/integration/test_exporter.py b/services/web/server/tests/integration/test_exporter.py index 1287005b730..418ae11a5b2 100644 --- a/services/web/server/tests/integration/test_exporter.py +++ b/services/web/server/tests/integration/test_exporter.py @@ -1,17 +1,18 @@ +import asyncio + # pylint:disable=redefined-outer-name,unused-argument,too-many-arguments import cgi +import itertools import json import logging +import operator import sys -import asyncio import tempfile +from collections import deque from contextlib import contextmanager from copy import deepcopy -import operator -import itertools from pathlib import Path -from typing import Any, Dict, List, Set, Callable, Tuple -from collections import deque +from typing import Any, Callable, Dict, List, Set, Tuple import aiofiles import aiohttp @@ -27,6 +28,8 @@ from simcore_service_webserver.director import setup_director from simcore_service_webserver.director_v2 import setup_director_v2 from simcore_service_webserver.exporter import setup_exporter +from simcore_service_webserver.exporter.async_hashing import Algorithm, checksum +from simcore_service_webserver.exporter.file_downloader import ParallelDownloader from simcore_service_webserver.login import setup_login from simcore_service_webserver.projects import setup_projects from simcore_service_webserver.resource_manager import setup_resource_manager @@ -35,11 +38,9 @@ from simcore_service_webserver.security_roles import UserRole from simcore_service_webserver.session import setup_session from simcore_service_webserver.socketio import setup_socketio -from simcore_service_webserver.users import setup_users -from simcore_service_webserver.storage_handlers import get_file_download_url from simcore_service_webserver.storage import setup_storage -from simcore_service_webserver.exporter.file_downloader import ParallelDownloader -from simcore_service_webserver.exporter.async_hashing import Algorithm, checksum +from simcore_service_webserver.storage_handlers import get_file_download_url +from simcore_service_webserver.users import setup_users from yarl import URL log = logging.getLogger(__name__) @@ -288,19 +289,15 @@ def replace_uuids_with_sequences(original_project: Dict[str, Any]) -> Dict[str, def dict_without_keys( dict_data: Dict[str, Any], skipped_keys: Set[str] ) -> Dict[str, Any]: - def _delete_keys_from_dict( - dictionary: Dict[str, Any], keys: Set[str] - ) -> Dict[str, Any]: - modified_dict = {} - for key, value in dictionary.items(): - if key not in keys: - if isinstance(value, dict): - modified_dict[key] = _delete_keys_from_dict(value, keys) - else: - modified_dict[key] = deepcopy(value) - return modified_dict - - return _delete_keys_from_dict(dict_data, skipped_keys) + + modified_dict = {} + for key, value in dict_data.items(): + if key not in skipped_keys: + if isinstance(value, dict): + modified_dict[key] = dict_without_keys(value, skipped_keys) + else: + modified_dict[key] = deepcopy(value) + return modified_dict def assert_combined_entires_condition( From b8b131d383212526a3591d318533ecb107eba66d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 11:07:11 +0100 Subject: [PATCH 076/200] bad merge --- .../src/models_library/projects_nodes.py | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index 10311b5657e..18438282adf 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -194,24 +194,3 @@ def schema_extra(schema, _model: "Node"): if prop_name in schema.get("properties", {}): was = deepcopy(schema["properties"][prop_name]) schema["properties"][prop_name] = {"anyOf": [{"type": "null"}, was]} - - -@unique -class NodeIOState(str, Enum): - OK = "OK" - OUTDATED = "OUTDATED" - - -@unique -class NodeRunnableState(str, Enum): - WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" - READY = "READY" - - -class NodeState(BaseModel): - io_state: NodeIOState = Field( - ..., description="represents the state of the inputs outputs" - ) - runnable_state: NodeRunnableState = Field( - ..., description="represent the runnable state of the node" - ) From 750df6dbf9b98764c3fa2925537659403793b84c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 11:18:21 +0100 Subject: [PATCH 077/200] @pcrespov review: use Field(...) instead of ... --- packages/models-library/src/models_library/projects.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models-library/src/models_library/projects.py b/packages/models-library/src/models_library/projects.py index 4079dcf3086..97d5fa4d9db 100644 --- a/packages/models-library/src/models_library/projects.py +++ b/packages/models-library/src/models_library/projects.py @@ -62,7 +62,7 @@ class ProjectCommons(BaseModel): last_change_date: datetime = Field(...) # Pipeline of nodes (SEE projects_nodes.py) - workbench: Workbench = ... + workbench: Workbench = Field(...) @validator("thumbnail", always=True, pre=True) @classmethod From 53f30978344918a2b0cc6e1da70232ef7bb3c563 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 11:25:58 +0100 Subject: [PATCH 078/200] @pcrespov review: add a test to check values in enums are the same --- packages/models-library/tests/test_projects.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/models-library/tests/test_projects.py b/packages/models-library/tests/test_projects.py index dd38509baab..dd546344c29 100644 --- a/packages/models-library/tests/test_projects.py +++ b/packages/models-library/tests/test_projects.py @@ -39,3 +39,13 @@ def test_project_with_thumbnail_as_empty_string(minimal_project: Dict[str, Any]) assert project assert project.thumbnail == None + + +def test_project_type_in_models_package_same_as_in_postgres_database_package(): + from models_library.projects import ProjectType as ml_project_type + from simcore_postgres_database.models.projects import ProjectType as pg_project_type + + # pylint: disable=no-member + assert ( + ml_project_type.__members__.values() == pg_project_type.__members__.values() + ), f"The enum in models_library package and postgres package shall have the same values. models_pck: {ml_project_type.__members__}, postgres_pck: {pg_project_type.__members__}" From 606d1e3680d29f9788fb7caa22ba445f2553f249 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 12:37:30 +0100 Subject: [PATCH 079/200] fix test to check if types are the same --- packages/models-library/requirements/ci.txt | 1 + packages/models-library/requirements/dev.txt | 1 + packages/models-library/tests/test_projects.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/models-library/requirements/ci.txt b/packages/models-library/requirements/ci.txt index 8bc78af6b28..42fce5541dd 100644 --- a/packages/models-library/requirements/ci.txt +++ b/packages/models-library/requirements/ci.txt @@ -12,6 +12,7 @@ # installs this repo's packages ../pytest-simcore/ +../postgres-database/[migration] # current module . diff --git a/packages/models-library/requirements/dev.txt b/packages/models-library/requirements/dev.txt index 2d0adb4dfbd..82b0ac898cc 100644 --- a/packages/models-library/requirements/dev.txt +++ b/packages/models-library/requirements/dev.txt @@ -13,6 +13,7 @@ # installs this repo's packages -e ../pytest-simcore/ +-e ../postgres-database/[migration] # current module -e . diff --git a/packages/models-library/tests/test_projects.py b/packages/models-library/tests/test_projects.py index dd546344c29..2d3f3eec923 100644 --- a/packages/models-library/tests/test_projects.py +++ b/packages/models-library/tests/test_projects.py @@ -47,5 +47,5 @@ def test_project_type_in_models_package_same_as_in_postgres_database_package(): # pylint: disable=no-member assert ( - ml_project_type.__members__.values() == pg_project_type.__members__.values() + ml_project_type.__members__.keys() == pg_project_type.__members__.keys() ), f"The enum in models_library package and postgres package shall have the same values. models_pck: {ml_project_type.__members__}, postgres_pck: {pg_project_type.__members__}" From 579193fb008106d820a23545efa3a0cd1139d8b1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 13:56:28 +0100 Subject: [PATCH 080/200] change state to object --- .../src/models_library/projects_nodes.py | 59 ++++++++----------- .../src/models_library/projects_pipeline.py | 8 +-- .../tests/test_project_nodes.py | 14 +++-- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index 18438282adf..6f529d6fad9 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -3,7 +3,6 @@ """ from copy import deepcopy -from enum import Enum, unique from typing import Dict, List, Optional, Union from pydantic import ( @@ -56,25 +55,23 @@ Outputs = Dict[OutputID, OutputTypes] -@unique -class NodeIOState(str, Enum): - OK = "OK" - OUTDATED = "OUTDATED" - - -@unique -class NodeRunnableState(str, Enum): - WAITING_FOR_DEPENDENCIES = "WAITING_FOR_DEPENDENCIES" - READY = "READY" - - class NodeState(BaseModel): - io_state: NodeIOState = Field( - ..., description="represents the state of the inputs outputs" + modified: bool = Field( + True, description="true if the node's outputs need to be re-computed" ) - runnable_state: NodeRunnableState = Field( - ..., description="represent the runnable state of the node" + dependencies: List[NodeID] = Field( + default_factory=list, + description="contains the node inputs dependencies if they need to be computed first", ) + current_status: RunningState = Field( + RunningState.NOT_STARTED, + description="the node's current state", + example=["RUNNING", "FAILED"], + alias="currentStatus", + ) + + class Config: + extra = Extra.forbid class Node(BaseModel): @@ -149,22 +146,8 @@ class Node(BaseModel): # NOTE: use projects_ui.py position: Optional[Position] = Field(None, deprecated=True) - io_state: Optional[NodeIOState] = Field( - NodeIOState.OUTDATED, - description="The node's inpyts/outputs state", - alias="ioState", - ) - - runnable_state: Optional[NodeRunnableState] = Field( - NodeRunnableState.READY, - description="The node's runnable state", - alias="runnableState", - ) - - state: Optional[RunningState] = Field( - RunningState.NOT_STARTED, - description="the node's running state", - example=["RUNNING", "FAILED"], + state: Optional[NodeState] = Field( + default_factory=NodeState, description="The node's state object" ) @validator("thumbnail", pre=True) @@ -174,13 +157,21 @@ def convert_empty_str_to_none(cls, v): return None return v - @validator("state", pre=True) @classmethod def convert_old_enum_name(cls, v): if v == "FAILURE": return RunningState.FAILED return v + @validator("state", pre=True) + @classmethod + def convert_from_enum(cls, v): + if isinstance(v, str): + # the old version of state was a enum of RunningState + running_state_value = cls.convert_old_enum_name(v) + return NodeState(currentStatus=running_state_value) + return v + class Config: extra = Extra.forbid diff --git a/packages/models-library/src/models_library/projects_pipeline.py b/packages/models-library/src/models_library/projects_pipeline.py index b7d6e3a41dc..a15a1d8a5dd 100644 --- a/packages/models-library/src/models_library/projects_pipeline.py +++ b/packages/models-library/src/models_library/projects_pipeline.py @@ -43,12 +43,12 @@ class Config: }, "node_states": { "2fb4808a-e403-4a46-b52c-892560d27862": { - "io_state": "OUTDATED", - "runnable_state": "READY", + "modified": True, + "dependencies": [], }, "19a40c7b-0a40-458a-92df-c77a5df7c886": { - "io_state": "OK", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": False, + "dependencies": ["2fb4808a-e403-4a46-b52c-892560d27862"], }, }, }, diff --git a/packages/models-library/tests/test_project_nodes.py b/packages/models-library/tests/test_project_nodes.py index 4a247a8ac31..9fbab0e715d 100644 --- a/packages/models-library/tests/test_project_nodes.py +++ b/packages/models-library/tests/test_project_nodes.py @@ -24,7 +24,9 @@ def test_create_minimal_node(minimal_node_data_sample: Dict[str, Any]): # a nice way to see how the simplest node looks like assert node.inputs == {} assert node.outputs == {} - assert node.state == RunningState.NOT_STARTED + assert node.state.current_status == RunningState.NOT_STARTED + assert node.state.modified is True + assert node.state.dependencies == [] assert node.parent is None assert node.progress is None @@ -40,7 +42,7 @@ def test_create_minimal_node_with_new_data_type( old_node_data.update( { "thumbnail": "https://www.google.com/imgres?imgurl=https%3A%2F%2Fregtechassociation.org%2Fwp-content%2Fuploads%2F2018%2F10%2FStandards-stock-image-1400x650.jpg&imgrefurl=https%3A%2F%2Fregtechassociation.org%2Fnews%2Firta-launches-new-open-standard-principles-for-regtech-firms-in-support-of-key-initiatives-for-2018-19%2Fstandards-stock-image-1400x650%2F&tbnid=se_y-TktvwvEMM&vet=12ahUKEwjmsNDs66ruAhWEtqQKHSLRBT8QMygBegUIARCEAQ..i&docid=UiHvpBPeE3G8KM&w=1400&h=650&q=standard%20image&ved=2ahUKEwjmsNDs66ruAhWEtqQKHSLRBT8QMygBegUIARCEAQ", - "state": "FAILED", + "state": {"currentStatus": "STARTED"}, } ) @@ -50,7 +52,9 @@ def test_create_minimal_node_with_new_data_type( == "https://www.google.com/imgres?imgurl=https%3A%2F%2Fregtechassociation.org%2Fwp-content%2Fuploads%2F2018%2F10%2FStandards-stock-image-1400x650.jpg&imgrefurl=https%3A%2F%2Fregtechassociation.org%2Fnews%2Firta-launches-new-open-standard-principles-for-regtech-firms-in-support-of-key-initiatives-for-2018-19%2Fstandards-stock-image-1400x650%2F&tbnid=se_y-TktvwvEMM&vet=12ahUKEwjmsNDs66ruAhWEtqQKHSLRBT8QMygBegUIARCEAQ..i&docid=UiHvpBPeE3G8KM&w=1400&h=650&q=standard%20image&ved=2ahUKEwjmsNDs66ruAhWEtqQKHSLRBT8QMygBegUIARCEAQ" ) - assert node.state == RunningState.FAILED + assert node.state.current_status == RunningState.STARTED + assert node.state.modified is True + assert node.state.dependencies == [] def test_backwards_compatibility_node_data(minimal_node_data_sample: Dict[str, Any]): @@ -61,6 +65,8 @@ def test_backwards_compatibility_node_data(minimal_node_data_sample: Dict[str, A node = Node(**old_node_data) assert node.thumbnail is None - assert node.state == RunningState.FAILED + assert node.state.current_status == RunningState.FAILED + assert node.state.modified is True + assert node.state.dependencies == [] assert node.dict(exclude_unset=True) != old_node_data From 2d210b2b498815d9160b07e5b5bcab4a28e3984b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Mon, 15 Feb 2021 22:05:21 +0100 Subject: [PATCH 081/200] fix some typos --- .../models-library/src/models_library/projects_nodes.py | 4 ++-- packages/models-library/tests/test_models_fit_schemas.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index 6f529d6fad9..0bca22e8f2d 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -79,7 +79,7 @@ class Node(BaseModel): ..., description="distinctive name for the node based on the docker registry path", regex=SERVICE_KEY_RE, - example=[ + examples=[ "simcore/services/comp/sleeper", "simcore/services/dynamic/3dviewer", "simcore/services/frontend/file-picker", @@ -121,7 +121,7 @@ class Node(BaseModel): input_nodes: Optional[List[NodeID]] = Field( default_factory=list, description="node IDs of where the node is connected to", - example=["nodeUuid1", "nodeUuid2"], + examples=["nodeUuid1", "nodeUuid2"], alias="inputNodes", ) diff --git a/packages/models-library/tests/test_models_fit_schemas.py b/packages/models-library/tests/test_models_fit_schemas.py index 48b99554256..e2ccd54133b 100644 --- a/packages/models-library/tests/test_models_fit_schemas.py +++ b/packages/models-library/tests/test_models_fit_schemas.py @@ -6,10 +6,9 @@ from typing import Callable import pytest -from pydantic.main import BaseModel - from models_library.projects import Project from models_library.services import ServiceDockerData +from pydantic.main import BaseModel @pytest.mark.parametrize( @@ -25,6 +24,10 @@ def test_generated_schema_same_as_original( generated_schema = json.loads(pydantic_model.schema_json(indent=2)) original_schema = json_schema_dict(original_json_schema) + # NOTE: A change is considered an addition when the destination schema has become more permissive relative to the source schema. For example {"type": "string"} -> {"type": ["string", "number"]}. + # A change is considered a removal when the destination schema has become more restrictive relative to the source schema. For example {"type": ["string", "number"]} -> {"type": "string"}. + # The addition and removal changes detected are returned in JsonSchema format. These schemas represent the set of values that have been added or removed. + # run one direction original schema encompass generated one process_completion = diff_json_schemas(original_schema, generated_schema) From 0ed1abacefd4b9c0638727dbb2b1b62d9d733c3b Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 09:07:56 +0100 Subject: [PATCH 082/200] fix some of the schema exports --- .../src/models_library/projects_nodes.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index 0bca22e8f2d..f0a575a4829 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -66,7 +66,7 @@ class NodeState(BaseModel): current_status: RunningState = Field( RunningState.NOT_STARTED, description="the node's current state", - example=["RUNNING", "FAILED"], + examples=["RUNNING", "FAILED"], alias="currentStatus", ) @@ -89,10 +89,10 @@ class Node(BaseModel): ..., description="semantic version number of the node", regex=VERSION_RE, - example=["1.0.0", "0.0.1"], + examples=["1.0.0", "0.0.1"], ) label: str = Field( - ..., description="The short name of the node", example=["JupyterLab"] + ..., description="The short name of the node", examples=["JupyterLab"] ) progress: Optional[float] = Field( None, ge=0, le=100, description="the node progress value" @@ -100,14 +100,14 @@ class Node(BaseModel): thumbnail: Optional[HttpUrl] = Field( None, description="url of the latest screenshot of the node", - example=["https://placeimg.com/171/96/tech/grayscale/?0.jpg"], + examples=["https://placeimg.com/171/96/tech/grayscale/?0.jpg"], ) # RUN HASH run_hash: Optional[str] = Field( None, description="the hex digest of the resolved inputs +outputs hash at the time when the last outputs were generated", - example=["a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2"], + examples=["a4337bc45a8fc544c03f52dc550cd6e1e87021bc896588bd79e901e2"], alias="runHash", ) @@ -133,14 +133,14 @@ class Node(BaseModel): output_nodes: Optional[List[NodeID]] = Field( None, description="Used in group-nodes. Node IDs of those connected to the output", - example=["nodeUuid1", "nodeUuid2"], + examples=["nodeUuid1", "nodeUuid2"], alias="outputNodes", ) parent: Optional[NodeID] = Field( None, description="Parent's (group-nodes') node ID s. Used to group", - example=["nodeUUid1", "nodeUuid2"], + examples=["nodeUUid1", "nodeUuid2"], ) # NOTE: use projects_ui.py @@ -181,7 +181,7 @@ class Config: def schema_extra(schema, _model: "Node"): # NOTE: the variant with anyOf[{type: null}, { other }] is compatible with OpenAPI # The other as type = [null, other] is only jsonschema compatible - for prop_name in ["runHash"]: + for prop_name in ["parent", "runHash"]: if prop_name in schema.get("properties", {}): was = deepcopy(schema["properties"][prop_name]) schema["properties"][prop_name] = {"anyOf": [{"type": "null"}, was]} From cfcc3238a7e9675ad2de8a38b6a60558a091321a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 09:13:53 +0100 Subject: [PATCH 083/200] updated project json schema --- api/specs/common/schemas/project-v0.0.1.json | 75 +++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index 07c3ce894fb..7cb77c6254f 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -396,42 +396,47 @@ }, "deprecated": true }, - "ioState": { - "title": "IO State", - "description": "the node's Inputs/Outputs state", - "default": "OUTDATED", - "type": "string", - "enum": [ - "OUTDATED", - "OK" - ] - }, - "runnableState": { - "title": "Runnable State", - "description": "the node's runnable state", - "default": "READY", - "type": "string", - "enum": [ - "READY", - "WAITING_FOR_DEPENDENCIES" - ] - }, "state": { - "title": "RunningState", - "description": "the node's running state", - "default": "NOT_STARTED", - "enum": [ - "UNKNOWN", - "NOT_STARTED", - "PUBLISHED", - "PENDING", - "STARTED", - "RETRY", - "SUCCESS", - "FAILED", - "ABORTED" - ], - "type": "string" + "title": "NodeState", + "type": "object", + "properties": { + "modified": { + "title": "Modified", + "description": "true if the node's outputs need to be re-computed", + "default": true, + "type": "boolean" + }, + "dependencies": { + "title": "Dependencies", + "description": "contains the node inputs dependencies if they need to be computed first", + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "currentStatus": { + "description": "the node's current state", + "default": "NOT_STARTED", + "examples": [ + "RUNNING", + "FAILED" + ], + "enum": [ + "UNKNOWN", + "PUBLISHED", + "NOT_STARTED", + "PENDING", + "STARTED", + "RETRY", + "SUCCESS", + "FAILED", + "ABORTED" + ], + "type": "string" + } + }, + "additionalProperties": false } } } From a1bd14f1a32b267fc723ec07aa87702e7229f1bb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 09:18:07 +0100 Subject: [PATCH 084/200] updated project json --- .../schemas/project-v0.0.1-converted.yaml | 65 +- .../api/v0/schemas/project-v0.0.1.json | 75 ++- .../api/v0/openapi.yaml | 63 +- .../api/v0/schemas/project-v0.0.1.json | 75 ++- .../api/v0/openapi.yaml | 630 +++++++++--------- .../api/v0/schemas/project-v0.0.1.json | 75 ++- 6 files changed, 518 insertions(+), 465 deletions(-) diff --git a/api/specs/common/schemas/project-v0.0.1-converted.yaml b/api/specs/common/schemas/project-v0.0.1-converted.yaml index d7954252f8b..7a6b522e8cf 100644 --- a/api/specs/common/schemas/project-v0.0.1-converted.yaml +++ b/api/specs/common/schemas/project-v0.0.1-converted.yaml @@ -285,37 +285,42 @@ properties: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: >- + contains the node inputs dependencies if they need to be + computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object diff --git a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json index 07c3ce894fb..7cb77c6254f 100644 --- a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json +++ b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json @@ -396,42 +396,47 @@ }, "deprecated": true }, - "ioState": { - "title": "IO State", - "description": "the node's Inputs/Outputs state", - "default": "OUTDATED", - "type": "string", - "enum": [ - "OUTDATED", - "OK" - ] - }, - "runnableState": { - "title": "Runnable State", - "description": "the node's runnable state", - "default": "READY", - "type": "string", - "enum": [ - "READY", - "WAITING_FOR_DEPENDENCIES" - ] - }, "state": { - "title": "RunningState", - "description": "the node's running state", - "default": "NOT_STARTED", - "enum": [ - "UNKNOWN", - "NOT_STARTED", - "PUBLISHED", - "PENDING", - "STARTED", - "RETRY", - "SUCCESS", - "FAILED", - "ABORTED" - ], - "type": "string" + "title": "NodeState", + "type": "object", + "properties": { + "modified": { + "title": "Modified", + "description": "true if the node's outputs need to be re-computed", + "default": true, + "type": "boolean" + }, + "dependencies": { + "title": "Dependencies", + "description": "contains the node inputs dependencies if they need to be computed first", + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "currentStatus": { + "description": "the node's current state", + "default": "NOT_STARTED", + "examples": [ + "RUNNING", + "FAILED" + ], + "enum": [ + "UNKNOWN", + "PUBLISHED", + "NOT_STARTED", + "PENDING", + "STARTED", + "RETRY", + "SUCCESS", + "FAILED", + "ABORTED" + ], + "type": "string" + } + }, + "additionalProperties": false } } } diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index 1ccc7759895..d35ea1aede1 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -998,37 +998,40 @@ components: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object diff --git a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json index 07c3ce894fb..7cb77c6254f 100644 --- a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json +++ b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json @@ -396,42 +396,47 @@ }, "deprecated": true }, - "ioState": { - "title": "IO State", - "description": "the node's Inputs/Outputs state", - "default": "OUTDATED", - "type": "string", - "enum": [ - "OUTDATED", - "OK" - ] - }, - "runnableState": { - "title": "Runnable State", - "description": "the node's runnable state", - "default": "READY", - "type": "string", - "enum": [ - "READY", - "WAITING_FOR_DEPENDENCIES" - ] - }, "state": { - "title": "RunningState", - "description": "the node's running state", - "default": "NOT_STARTED", - "enum": [ - "UNKNOWN", - "NOT_STARTED", - "PUBLISHED", - "PENDING", - "STARTED", - "RETRY", - "SUCCESS", - "FAILED", - "ABORTED" - ], - "type": "string" + "title": "NodeState", + "type": "object", + "properties": { + "modified": { + "title": "Modified", + "description": "true if the node's outputs need to be re-computed", + "default": true, + "type": "boolean" + }, + "dependencies": { + "title": "Dependencies", + "description": "contains the node inputs dependencies if they need to be computed first", + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "currentStatus": { + "description": "the node's current state", + "default": "NOT_STARTED", + "examples": [ + "RUNNING", + "FAILED" + ], + "enum": [ + "UNKNOWN", + "PUBLISHED", + "NOT_STARTED", + "PENDING", + "STARTED", + "RETRY", + "SUCCESS", + "FAILED", + "ABORTED" + ], + "type": "string" + } + }, + "additionalProperties": false } } } 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 f607b411292..3c660b57971 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 @@ -6374,37 +6374,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -6965,37 +6968,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -7436,37 +7442,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -8025,37 +8034,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -8620,37 +8632,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -9206,37 +9221,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -9677,37 +9695,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -10288,37 +10309,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -12027,37 +12051,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object @@ -12615,37 +12642,40 @@ paths: example: - '15' deprecated: true - ioState: - title: IO State - description: the node's Inputs/Outputs state - default: OUTDATED - type: string - enum: - - OUTDATED - - OK - runnableState: - title: Runnable State - description: the node's runnable state - default: READY - type: string - enum: - - READY - - WAITING_FOR_DEPENDENCIES state: - title: RunningState - description: the node's running state - default: NOT_STARTED - enum: - - UNKNOWN - - NOT_STARTED - - PUBLISHED - - PENDING - - STARTED - - RETRY - - SUCCESS - - FAILED - - ABORTED - type: string + title: NodeState + type: object + properties: + modified: + title: Modified + description: true if the node's outputs need to be re-computed + default: true + type: boolean + dependencies: + title: Dependencies + description: contains the node inputs dependencies if they need to be computed first + type: array + items: + type: string + format: uuid + currentStatus: + description: the node's current state + default: NOT_STARTED + example: + - RUNNING + - FAILED + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - STARTED + - RETRY + - SUCCESS + - FAILED + - ABORTED + type: string + additionalProperties: false additionalProperties: true ui: type: object diff --git a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json index 07c3ce894fb..7cb77c6254f 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json +++ b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json @@ -396,42 +396,47 @@ }, "deprecated": true }, - "ioState": { - "title": "IO State", - "description": "the node's Inputs/Outputs state", - "default": "OUTDATED", - "type": "string", - "enum": [ - "OUTDATED", - "OK" - ] - }, - "runnableState": { - "title": "Runnable State", - "description": "the node's runnable state", - "default": "READY", - "type": "string", - "enum": [ - "READY", - "WAITING_FOR_DEPENDENCIES" - ] - }, "state": { - "title": "RunningState", - "description": "the node's running state", - "default": "NOT_STARTED", - "enum": [ - "UNKNOWN", - "NOT_STARTED", - "PUBLISHED", - "PENDING", - "STARTED", - "RETRY", - "SUCCESS", - "FAILED", - "ABORTED" - ], - "type": "string" + "title": "NodeState", + "type": "object", + "properties": { + "modified": { + "title": "Modified", + "description": "true if the node's outputs need to be re-computed", + "default": true, + "type": "boolean" + }, + "dependencies": { + "title": "Dependencies", + "description": "contains the node inputs dependencies if they need to be computed first", + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "currentStatus": { + "description": "the node's current state", + "default": "NOT_STARTED", + "examples": [ + "RUNNING", + "FAILED" + ], + "enum": [ + "UNKNOWN", + "PUBLISHED", + "NOT_STARTED", + "PENDING", + "STARTED", + "RETRY", + "SUCCESS", + "FAILED", + "ABORTED" + ], + "type": "string" + } + }, + "additionalProperties": false } } } From a31f62376cd04c724ab626e9ddd6368b3d541c3c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 09:57:31 +0100 Subject: [PATCH 085/200] moved node states --- .../simcore_service_director_v2/utils/dags.py | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index 4cb02f54d90..cd99615e560 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -3,12 +3,7 @@ import networkx as nx from models_library.projects import Workbench -from models_library.projects_nodes import ( - NodeID, - NodeIOState, - NodeRunnableState, - NodeState, -) +from models_library.projects_nodes import NodeID, NodeState from models_library.projects_nodes_io import PortLink from models_library.projects_pipeline import PipelineDetails from models_library.utils.nodes import compute_node_hash @@ -65,52 +60,60 @@ def create_complete_dag_from_tasks(tasks: List[CompTaskAtDB]) -> nx.DiGraph: return dag_graph -async def compute_node_io_state( +async def compute_node_modified_state( nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID -) -> NodeIOState: +) -> bool: node = nodes_data_view[str(node_id)] # if the node has no output it is outdated for sure if not node["outputs"]: - return NodeIOState.OUTDATED + return True for output_port in node["outputs"]: if output_port is None: - return NodeIOState.OUTDATED + return True # maybe our inputs changed? let's compute the node hash and compare with the saved one async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: return nodes_data_view[str(node_id)] computed_hash = await compute_node_hash(node_id, get_node_io_payload_cb) if computed_hash != node["run_hash"]: - return NodeIOState.OUTDATED - return NodeIOState.OK + return True + return False -async def compute_node_runnable_state(nodes_data_view, node_id) -> NodeRunnableState: +async def compute_node_dependencies_state(nodes_data_view, node_id) -> List[NodeID]: node = nodes_data_view[str(node_id)] # check if the previous node is outdated or waits for dependencies... in which case this one has to wait + non_computed_dependencies: List[NodeID] = [] for input_port in node.get("inputs", {}).values(): if isinstance(input_port, PortLink): if node_needs_computation(nodes_data_view, input_port.node_uuid): - return NodeRunnableState.WAITING_FOR_DEPENDENCIES + non_computed_dependencies.append(input_port.node_uuid) # all good. ready - return NodeRunnableState.READY + return non_computed_dependencies + + +kNODE_MODIFIED_STATE = "modified_state" +kNODE_DEPENDENCIES_TO_COMPUTE = "dependencies_state" async def compute_node_states( nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID ): node = nodes_data_view[str(node_id)] - node["io_state"] = await compute_node_io_state(nodes_data_view, node_id) - node["runnable_state"] = await compute_node_runnable_state(nodes_data_view, node_id) + node[kNODE_MODIFIED_STATE] = await compute_node_modified_state( + nodes_data_view, node_id + ) + node[kNODE_DEPENDENCIES_TO_COMPUTE] = await compute_node_dependencies_state( + nodes_data_view, node_id + ) def node_needs_computation( nodes_data_view: nx.classes.reportviews.NodeDataView, node_id: NodeID ) -> bool: node = nodes_data_view[str(node_id)] - return (node.get("io_state", NodeIOState.OK) == NodeIOState.OUTDATED) or ( - node.get("runnable_state", NodeRunnableState.READY) - == NodeRunnableState.WAITING_FOR_DEPENDENCIES + return node.get(kNODE_MODIFIED_STATE, False) or node.get( + kNODE_DEPENDENCIES_TO_COMPUTE, None ) @@ -173,8 +176,8 @@ async def compute_pipeline_details( adjacency_list=nx.to_dict_of_lists(pipeline_dag), node_states={ node_id: NodeState( - io_state=node_data.get("io_state"), - runnable_state=node_data.get("runnable_state"), + modified=node_data.get(kNODE_MODIFIED_STATE), + dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE), ) for node_id, node_data in complete_dag.nodes.data() if node_id in pipeline_dag.nodes From af9a248338181a040144ff3d5e53b9a2b220d5ba Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:24:54 +0100 Subject: [PATCH 086/200] make dependencies a set of unique node uuids fix director-v2 tests --- .../src/models_library/projects_nodes.py | 4 +- .../simcore_service_director_v2/utils/dags.py | 6 +- .../tests/integration/test_computation_api.py | 65 ++++++++++--------- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/packages/models-library/src/models_library/projects_nodes.py b/packages/models-library/src/models_library/projects_nodes.py index f0a575a4829..16a5d950423 100644 --- a/packages/models-library/src/models_library/projects_nodes.py +++ b/packages/models-library/src/models_library/projects_nodes.py @@ -3,7 +3,7 @@ """ from copy import deepcopy -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Set, Union from pydantic import ( BaseModel, @@ -59,7 +59,7 @@ class NodeState(BaseModel): modified: bool = Field( True, description="true if the node's outputs need to be re-computed" ) - dependencies: List[NodeID] = Field( + dependencies: Set[NodeID] = Field( default_factory=list, description="contains the node inputs dependencies if they need to be computed first", ) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index cd99615e560..3da71ea81c9 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -83,13 +83,13 @@ async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: async def compute_node_dependencies_state(nodes_data_view, node_id) -> List[NodeID]: node = nodes_data_view[str(node_id)] # check if the previous node is outdated or waits for dependencies... in which case this one has to wait - non_computed_dependencies: List[NodeID] = [] + non_computed_dependencies: Set[NodeID] = set() for input_port in node.get("inputs", {}).values(): if isinstance(input_port, PortLink): if node_needs_computation(nodes_data_view, input_port.node_uuid): - non_computed_dependencies.append(input_port.node_uuid) + non_computed_dependencies.add(input_port.node_uuid) # all good. ready - return non_computed_dependencies + return list(non_computed_dependencies) kNODE_MODIFIED_STATE = "modified_state" diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index 53a37bb23fc..e9ac4d8a038 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -16,7 +16,7 @@ import pytest import sqlalchemy as sa from models_library.projects import ProjectAtDB -from models_library.projects_nodes import NodeIOState, NodeRunnableState, NodeState +from models_library.projects_nodes import NodeState from models_library.projects_nodes_io import NodeID from models_library.projects_pipeline import PipelineDetails from models_library.projects_state import RunningState @@ -278,8 +278,8 @@ def fake_workbench_computational_pipeline_details_completed( ) -> PipelineDetails: completed_pipeline_details = deepcopy(fake_workbench_computational_pipeline_details) for node_state in completed_pipeline_details.node_states.values(): - node_state.io_state = NodeIOState.OK - node_state.runnable_state = NodeRunnableState.READY + node_state.modified = False + node_state.dependencies = [] return completed_pipeline_details @@ -344,10 +344,10 @@ def test_start_empty_computation( subgraph_elements=[0, 1], exp_pipeline_adj_list={1: []}, exp_node_states={ - 1: NodeState( - io_state=NodeIOState.OUTDATED, - runnable_state=NodeRunnableState.READY, - ) + 1: { + "modified": True, + "dependencies": [], + } }, ), id="element 0,1", @@ -357,22 +357,22 @@ def test_start_empty_computation( subgraph_elements=[1, 2, 4], exp_pipeline_adj_list={1: [2], 2: [4], 3: [4], 4: []}, exp_node_states={ - 1: NodeState( - io_state=NodeIOState.OUTDATED, - runnable_state=NodeRunnableState.READY, - ), - 2: NodeState( - io_state=NodeIOState.OUTDATED, - runnable_state=NodeRunnableState.WAITING_FOR_DEPENDENCIES, - ), - 3: NodeState( - io_state=NodeIOState.OUTDATED, - runnable_state=NodeRunnableState.READY, - ), - 4: NodeState( - io_state=NodeIOState.OUTDATED, - runnable_state=NodeRunnableState.WAITING_FOR_DEPENDENCIES, - ), + 1: { + "modified": True, + "dependencies": [], + }, + 2: { + "modified": True, + "dependencies": [1], + }, + 3: { + "modified": True, + "dependencies": [], + }, + 4: { + "modified": True, + "dependencies": [2, 3], + }, }, ), id="element 1,2,4", @@ -387,14 +387,14 @@ def test_run_partial_computation( fake_workbench_without_outputs: Dict[str, Any], subgraph_elements: List[int], exp_pipeline_adj_list: Dict[int, List[str]], - exp_node_states: Dict[int, NodeState], + exp_node_states: Dict[int, Dict[str, Any]], ): sleepers_project: ProjectAtDB = project(workbench=fake_workbench_without_outputs) def _convert_to_pipeline_details( project: ProjectAtDB, exp_pipeline_adj_list: Dict[int, List[str]], - exp_node_states: Dict[int, NodeState], + exp_node_states: Dict[int, Dict[str, Any]], ) -> PipelineDetails: workbench_node_uuids = list(project.workbench.keys()) converted_adj_list: Dict[NodeID, Dict[NodeID, List[NodeID]]] = {} @@ -403,7 +403,13 @@ def _convert_to_pipeline_details( NodeID(workbench_node_uuids[n]) for n in next_nodes ] converted_node_states: Dict[NodeID, NodeState] = { - NodeID(workbench_node_uuids[n]): s for n, s in exp_node_states.items() + NodeID(workbench_node_uuids[n]): NodeState( + modified=s["modified"], + dependencies={ + workbench_node_uuids[dep_n] for dep_n in s["dependencies"] + }, + ) + for n, s in exp_node_states.items() } return PipelineDetails( adjacency_list=converted_adj_list, node_states=converted_node_states @@ -444,12 +450,7 @@ def _convert_to_pipeline_details( expected_pipeline_details = _convert_to_pipeline_details( sleepers_project, exp_pipeline_adj_list, - { - n: NodeState( - io_state=NodeIOState.OK, runnable_state=NodeRunnableState.READY - ) - for n in exp_node_states - }, + {n: {"modified": False, "dependencies": []} for n in exp_node_states}, ) _assert_computation_task_out_obj( client, From 7442e68dfe5b352486f7e68d115f947bda88e751 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:25:46 +0100 Subject: [PATCH 087/200] updated API --- services/director-v2/openapi.json | 68 +++++++++++++------------------ 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/services/director-v2/openapi.json b/services/director-v2/openapi.json index 1bd81f2cba9..e65b54dbdd8 100644 --- a/services/director-v2/openapi.json +++ b/services/director-v2/openapi.json @@ -822,12 +822,14 @@ }, "node_states": { "2fb4808a-e403-4a46-b52c-892560d27862": { - "io_state": "OUTDATED", - "runnable_state": "READY" + "modified": true, + "dependencies": [] }, "19a40c7b-0a40-458a-92df-c77a5df7c886": { - "io_state": "OK", - "runnable_state": "WAITING_FOR_DEPENDENCIES" + "modified": false, + "dependencies": [ + "2fb4808a-e403-4a46-b52c-892560d27862" + ] } } } @@ -896,15 +898,6 @@ } } }, - "NodeIOState": { - "title": "NodeIOState", - "enum": [ - "OK", - "OUTDATED" - ], - "type": "string", - "description": "An enumeration." - }, "NodeRequirement": { "title": "NodeRequirement", "enum": [ @@ -915,40 +908,37 @@ "type": "string", "description": "An enumeration." }, - "NodeRunnableState": { - "title": "NodeRunnableState", - "enum": [ - "WAITING_FOR_DEPENDENCIES", - "READY" - ], - "type": "string", - "description": "An enumeration." - }, "NodeState": { "title": "NodeState", - "required": [ - "io_state", - "runnable_state" - ], "type": "object", "properties": { - "io_state": { - "allOf": [ - { - "$ref": "#/components/schemas/NodeIOState" - } - ], - "description": "represents the state of the inputs outputs" + "modified": { + "title": "Modified", + "type": "boolean", + "description": "true if the node's outputs need to be re-computed", + "default": true }, - "runnable_state": { + "dependencies": { + "title": "Dependencies", + "uniqueItems": true, + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "contains the node inputs dependencies if they need to be computed first" + }, + "currentStatus": { "allOf": [ { - "$ref": "#/components/schemas/NodeRunnableState" + "$ref": "#/components/schemas/RunningState" } ], - "description": "represent the runnable state of the node" + "description": "the node's current state", + "default": "NOT_STARTED" } - } + }, + "additionalProperties": false }, "PipelineDetails": { "title": "PipelineDetails", @@ -1102,7 +1092,7 @@ "$ref": "#/components/schemas/ServiceState" } ], - "description": "the service state * 'pending' - The service is waiting for resources to start * 'pulling' - The service is being pulled from the registry * 'starting' - The service is starting * 'running' - The service is running * 'complete' - The service completed * 'failed' - The service failed to start\n" + "description": "the service state * 'pending' - The service is waiting for resources to start * 'pulling' - The service is being pulled from the registry * 'starting' - The service is starting * 'running' - The service is running * 'complete' - The service completed * 'failed' - The service failed to start" }, "service_message": { "title": "Service Message", @@ -1188,7 +1178,7 @@ } }, "ServiceDockerData": { - "title": "simcore node", + "title": "ServiceDockerData", "required": [ "name", "description", From e5254720135b900d4ac2f6596731d1fb5ec86a17 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:26:08 +0100 Subject: [PATCH 088/200] the dependencies are a set --- api/specs/common/schemas/project-v0.0.1.json | 1 + 1 file changed, 1 insertion(+) diff --git a/api/specs/common/schemas/project-v0.0.1.json b/api/specs/common/schemas/project-v0.0.1.json index 7cb77c6254f..29e79aefaf8 100644 --- a/api/specs/common/schemas/project-v0.0.1.json +++ b/api/specs/common/schemas/project-v0.0.1.json @@ -410,6 +410,7 @@ "title": "Dependencies", "description": "contains the node inputs dependencies if they need to be computed first", "type": "array", + "uniqueItems": true, "items": { "type": "string", "format": "uuid" From c34eaffd00830c8927f802dccd490e0abd635d3c Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:26:34 +0100 Subject: [PATCH 089/200] create a set of node ids --- .../director-v2/src/simcore_service_director_v2/utils/dags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py index 3da71ea81c9..95b037f5c05 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py @@ -80,7 +80,7 @@ async def get_node_io_payload_cb(node_id: NodeID) -> Dict[str, Any]: return False -async def compute_node_dependencies_state(nodes_data_view, node_id) -> List[NodeID]: +async def compute_node_dependencies_state(nodes_data_view, node_id) -> Set[NodeID]: node = nodes_data_view[str(node_id)] # check if the previous node is outdated or waits for dependencies... in which case this one has to wait non_computed_dependencies: Set[NodeID] = set() @@ -89,7 +89,7 @@ async def compute_node_dependencies_state(nodes_data_view, node_id) -> List[Node if node_needs_computation(nodes_data_view, input_port.node_uuid): non_computed_dependencies.add(input_port.node_uuid) # all good. ready - return list(non_computed_dependencies) + return non_computed_dependencies kNODE_MODIFIED_STATE = "modified_state" From 5ae8855c1c3171725eef14b6eae114822e496dc2 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:39:34 +0100 Subject: [PATCH 090/200] changed state definition --- .../tests/integration/test_computation_api.py | 2 +- ...e_workbench_computational_node_states.json | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py index e9ac4d8a038..d30208cb398 100644 --- a/services/director-v2/tests/integration/test_computation_api.py +++ b/services/director-v2/tests/integration/test_computation_api.py @@ -279,7 +279,7 @@ def fake_workbench_computational_pipeline_details_completed( completed_pipeline_details = deepcopy(fake_workbench_computational_pipeline_details) for node_state in completed_pipeline_details.node_states.values(): node_state.modified = False - node_state.dependencies = [] + node_state.dependencies = set() return completed_pipeline_details diff --git a/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json b/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json index ad238f7434e..65e147c8d41 100644 --- a/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json +++ b/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json @@ -1,18 +1,23 @@ { "3a710d8b-565c-5f46-870b-b45ebe195fc7": { - "io_state": "OUTDATED", - "runnable_state": "READY" + "modified": true, + "dependencies": [] }, "e1e2ea96-ce8f-5abc-8712-b8ed312a782c": { - "io_state": "OUTDATED", - "runnable_state": "READY" + "modified": true, + "dependencies": [] }, "415fefd1-d08b-53c1-adb0-16bed3a687ef": { - "io_state": "OUTDATED", - "runnable_state": "WAITING_FOR_DEPENDENCIES" + "modified": true, + "dependencies": [ + "3a710d8b-565c-5f46-870b-b45ebe195fc7" + ] }, "6ede1209-b459-5735-91fc-761aa584808d": { - "io_state": "OUTDATED", - "runnable_state": "WAITING_FOR_DEPENDENCIES" + "modified": true, + "dependencies": [ + "e1e2ea96-ce8f-5abc-8712-b8ed312a782c", + "415fefd1-d08b-53c1-adb0-16bed3a687ef" + ] } } From 2aabcf9ec175557f16df96122e1182391780626f Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:42:06 +0100 Subject: [PATCH 091/200] returned dependencies are a set --- api/specs/common/schemas/project-v0.0.1-converted.yaml | 1 + .../api/v0/schemas/project-v0.0.1.json | 1 + .../src/simcore_service_storage/api/v0/openapi.yaml | 1 + .../api/v0/schemas/project-v0.0.1.json | 1 + .../src/simcore_service_webserver/api/v0/openapi.yaml | 10 ++++++++++ .../api/v0/schemas/project-v0.0.1.json | 1 + 6 files changed, 15 insertions(+) diff --git a/api/specs/common/schemas/project-v0.0.1-converted.yaml b/api/specs/common/schemas/project-v0.0.1-converted.yaml index 7a6b522e8cf..808e319bfbd 100644 --- a/api/specs/common/schemas/project-v0.0.1-converted.yaml +++ b/api/specs/common/schemas/project-v0.0.1-converted.yaml @@ -300,6 +300,7 @@ properties: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid diff --git a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json index 7cb77c6254f..29e79aefaf8 100644 --- a/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json +++ b/services/director/src/simcore_service_director/api/v0/schemas/project-v0.0.1.json @@ -410,6 +410,7 @@ "title": "Dependencies", "description": "contains the node inputs dependencies if they need to be computed first", "type": "array", + "uniqueItems": true, "items": { "type": "string", "format": "uuid" diff --git a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml index d35ea1aede1..095190ffd29 100644 --- a/services/storage/src/simcore_service_storage/api/v0/openapi.yaml +++ b/services/storage/src/simcore_service_storage/api/v0/openapi.yaml @@ -1011,6 +1011,7 @@ components: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid diff --git a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json index 7cb77c6254f..29e79aefaf8 100644 --- a/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json +++ b/services/storage/src/simcore_service_storage/api/v0/schemas/project-v0.0.1.json @@ -410,6 +410,7 @@ "title": "Dependencies", "description": "contains the node inputs dependencies if they need to be computed first", "type": "array", + "uniqueItems": true, "items": { "type": "string", "format": "uuid" 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 3c660b57971..de6fa92393a 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 @@ -6387,6 +6387,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -6981,6 +6982,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -7455,6 +7457,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -8047,6 +8050,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -8645,6 +8649,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -9234,6 +9239,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -9708,6 +9714,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -10322,6 +10329,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -12064,6 +12072,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid @@ -12655,6 +12664,7 @@ paths: title: Dependencies description: contains the node inputs dependencies if they need to be computed first type: array + uniqueItems: true items: type: string format: uuid diff --git a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json index 7cb77c6254f..29e79aefaf8 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json +++ b/services/web/server/src/simcore_service_webserver/api/v0/schemas/project-v0.0.1.json @@ -410,6 +410,7 @@ "title": "Dependencies", "description": "contains the node inputs dependencies if they need to be computed first", "type": "array", + "uniqueItems": true, "items": { "type": "string", "format": "uuid" From 2335362e91f5fc53d438e8d356d2fc77ba5742c5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 11:47:20 +0100 Subject: [PATCH 092/200] new pylint issue? --- packages/service-library/src/servicelib/logging_utils.py | 4 +++- .../src/simcore_service_director_v2/utils/logging_utils.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/service-library/src/servicelib/logging_utils.py b/packages/service-library/src/servicelib/logging_utils.py index f7375c7a844..e9ac475a262 100644 --- a/packages/service-library/src/servicelib/logging_utils.py +++ b/packages/service-library/src/servicelib/logging_utils.py @@ -68,7 +68,9 @@ def format(self, record): def config_all_loggers(): loggers = [logging.getLogger()] + [ - logging.getLogger(name) for name in logging.root.manager.loggerDict + # pylint: disable=no-member + logging.getLogger(name) + for name in logging.root.manager.loggerDict ] for logger in loggers: set_logging_handler(logger) diff --git a/services/director-v2/src/simcore_service_director_v2/utils/logging_utils.py b/services/director-v2/src/simcore_service_director_v2/utils/logging_utils.py index b7272dbeab5..87972b95488 100644 --- a/services/director-v2/src/simcore_service_director_v2/utils/logging_utils.py +++ b/services/director-v2/src/simcore_service_director_v2/utils/logging_utils.py @@ -71,7 +71,9 @@ def format(self, record): def config_all_loggers(): loggers = [logging.getLogger()] + [ - logging.getLogger(name) for name in logging.root.manager.loggerDict + # pylint: disable=no-member + logging.getLogger(name) + for name in logging.root.manager.loggerDict ] for logger in loggers: set_logging_handler(logger) From acc032c72f045ff87b617726b4c9d9774130bde5 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 15:58:43 +0100 Subject: [PATCH 093/200] migration of workbench according to new state object --- .../e6df5998cc74_migrate_node_states.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/postgres-database/src/simcore_postgres_database/migration/versions/e6df5998cc74_migrate_node_states.py diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/e6df5998cc74_migrate_node_states.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/e6df5998cc74_migrate_node_states.py new file mode 100644 index 00000000000..bd4bf14184d --- /dev/null +++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/e6df5998cc74_migrate_node_states.py @@ -0,0 +1,43 @@ +"""migrate node states + +Revision ID: e6df5998cc74 +Revises: 772c01ca8a90 +Create Date: 2021-02-16 14:28:23.296317+00:00 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "e6df5998cc74" +down_revision = "772c01ca8a90" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute( + sa.DDL( + """ +UPDATE projects + SET workbench = (regexp_replace(workbench::text, '"state": ("[^"]*")', '"state": {"currentStatus": \\1}', 'g'))::json + WHERE workbench::text LIKE '%%state%%' + """ + ) + ) + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.execute( + sa.DDL( + """ + UPDATE projects + SET workbench = (regexp_replace(workbench::text, '"state": {("[^"]+": [^,]+, )*"currentStatus": ("[^"]+")}', '"state": \\2', 'g'))::json + WHERE workbench::text LIKE '%%state%%' + """ + ) + ) + + +# ### end Alembic commands ### From 294d6070116fe797ceecf9112132120a6b6d3f9d Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 15:59:55 +0100 Subject: [PATCH 094/200] fixed updating of node state --- .../projects/projects_api.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 6b1e35c178e..7931ae3cfa0 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -8,6 +8,7 @@ """ # pylint: disable=too-many-arguments +import json import logging from collections import defaultdict from pprint import pformat @@ -291,15 +292,18 @@ async def update_project_node_state( app: web.Application, user_id: int, project_id: str, node_id: str, new_state: str ) -> Dict: log.debug( - "updating node %s state in project %s for user %s", node_id, project_id, user_id + "updating node %s current state in project %s for user %s", + node_id, + project_id, + user_id, ) project = await get_project_for_user(app, project_id, user_id) if not node_id in project["workbench"]: raise NodeNotFoundError(project_id, node_id) - if project["workbench"][node_id].get("state") == new_state: + if project["workbench"][node_id].get("state", {}).get("currentStatus") == new_state: # nothing to do here return project - project["workbench"][node_id]["state"] = new_state + project["workbench"][node_id]["state"].update({"currentStatus": new_state}) if RunningState(new_state) in [ RunningState.PUBLISHED, RunningState.PENDING, @@ -522,8 +526,10 @@ async def add_project_states_for_user( prj_node = project["workbench"].get(str(node_id)) if prj_node is None: continue - prj_node["ioState"] = node_state.io_state - prj_node["runnableState"] = node_state.runnable_state + node_state_dict = json.loads( + node_state.json(by_alias=True, exclude_unset=True) + ) + prj_node["state"].update(node_state_dict) project["state"] = ProjectState( locked=lock_state, state=ProjectRunningState(value=running_state) From e828bb37619d0d2684406d2f4e8e8a2f0c625e5a Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:22:58 +0100 Subject: [PATCH 095/200] state object contains all the different node status --- .../client/source/class/osparc/data/model/Node.js | 12 +++--------- .../source/class/osparc/data/model/NodeStatus.js | 12 ++++++------ .../source/class/osparc/desktop/WorkbenchView.js | 2 +- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 9f52b6d6ffb..4e8334b09dd 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -331,15 +331,9 @@ qx.Class.define("osparc.data.model.Node", { this.populateInputOutputData(nodeData); if (nodeData.state) { - this.getStatus().setRunningStatus(nodeData.state); - } - - if (nodeData.ioState) { - this.getStatus().setIoState(nodeData.ioState); - } - - if (nodeData.runnableState) { - this.getStatus().setRunnableState(nodeData.runnableState); + this.getStatus().setRunningStatus(nodeData.state.currentStatus); + this.getStatus().setModifiedStatus(nodeData.state.modified); + this.getStatus().setDependenciesStatus(nodeData.state.dependencies); } if ("progress" in nodeData) { diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index ad9b7e11975..f88cf97573e 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -46,16 +46,16 @@ qx.Class.define("osparc.data.model.NodeStatus", { event: "changeInteractiveStatus" }, - ioState: { - check: ["OUTDATED", "OK"], + modifiedStatus: { + check: "Boolean", nullable: true, - event: "changeIoStateStatus" + event: "changeModifiedStatus" }, - runnableState: { - check: ["READY", "WAITING_FOR_DEPENDENCIES"], + dependenciesStatus: { + check: "Array", nullable: true, - event: "changeRunnableStateStatus" + event: "changeDependenciesStatus" } } }); diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index 0567ec9bf17..ab5f2747682 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -535,7 +535,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { if (node && nodeData) { node.setOutputData(nodeData.outputs); if ("state" in nodeData && node.isComputational()) { - node.getStatus().setRunningStatus(nodeData["state"]); + node.getStatus().setRunningStatus(nodeData["state"]["currentStatus"]); } if ("progress" in nodeData) { const progress = Number.parseInt(nodeData["progress"]); From b058dfb74c26ab28de004b4daed3d70eaacd5ad4 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:37:29 +0100 Subject: [PATCH 096/200] exclude current status from node states --- .../src/simcore_service_webserver/projects/projects_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 7931ae3cfa0..62e8826675d 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -527,7 +527,9 @@ async def add_project_states_for_user( if prj_node is None: continue node_state_dict = json.loads( - node_state.json(by_alias=True, exclude_unset=True) + node_state.json( + by_alias=True, exclude_unset=True, exclude={"current_status"} + ) ) prj_node["state"].update(node_state_dict) From 31fa7c9f278ea3f6f5e5e1117f7df6db7f9bac6e Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:02:17 +0100 Subject: [PATCH 097/200] check if values are set --- .../client/source/class/osparc/data/model/Node.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 4e8334b09dd..4767904f2c6 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -331,9 +331,15 @@ qx.Class.define("osparc.data.model.Node", { this.populateInputOutputData(nodeData); if (nodeData.state) { - this.getStatus().setRunningStatus(nodeData.state.currentStatus); - this.getStatus().setModifiedStatus(nodeData.state.modified); - this.getStatus().setDependenciesStatus(nodeData.state.dependencies); + if (nodeData.state.currentStatus) { + this.getStatus().setRunningStatus(nodeData.state.currentStatus); + } + if (nodeData.state.modified) { + this.getStatus().setModifiedStatus(nodeData.state.modified); + } + if (nodeData.state.dependencies) { + this.getStatus().setDependenciesStatus(nodeData.state.dependencies); + } } if ("progress" in nodeData) { From e001ddce2cb13bdfb5eb612071ae381a65a3f561 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:03:46 +0100 Subject: [PATCH 098/200] ensure we have a dictionary if no value was set before --- .../src/simcore_service_webserver/projects/projects_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py index 62e8826675d..593a51ee4a0 100644 --- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py @@ -303,7 +303,9 @@ async def update_project_node_state( if project["workbench"][node_id].get("state", {}).get("currentStatus") == new_state: # nothing to do here return project - project["workbench"][node_id]["state"].update({"currentStatus": new_state}) + project["workbench"][node_id].setdefault("state", {}).update( + {"currentStatus": new_state} + ) if RunningState(new_state) in [ RunningState.PUBLISHED, RunningState.PENDING, @@ -531,7 +533,7 @@ async def add_project_states_for_user( by_alias=True, exclude_unset=True, exclude={"current_status"} ) ) - prj_node["state"].update(node_state_dict) + prj_node.setdefault("state", {}).update(node_state_dict) project["state"] = ProjectState( locked=lock_state, state=ProjectRunningState(value=running_state) From 9a7d643e34ab0f70c1b675e7d328c000745dfab1 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:19:49 +0100 Subject: [PATCH 099/200] remove the runtime states from the imported project --- .../exporter/formatters/formatter_v1.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index b96d4d12903..b46f12ecfb2 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -210,7 +210,12 @@ async def add_new_project(app: web.Application, project: Project, user_id: int): db = app[APP_PROJECT_DBAPI] # validated project is transform in dict via json to use only primitive types - project_in: Dict = json.loads(project.json(exclude_none=True, by_alias=True)) + project_in: Dict = json.loads( + project.json( + exclude_none=True, + by_alias=True, + ) + ) # update metadata (uuid, timestamps, ownership) and save _project_db: Dict = await db.add_project( @@ -277,6 +282,12 @@ async def _fix_file_e_tags( output.e_tag = e_tag +async def _remove_runtime_states(project: Project): + for node_data in project.workbench.values(): + node_data.state.modified = None + node_data.state.dependencies = None + + async def import_files_and_validate_project( app: web.Application, user_id: int, root_folder: Path ) -> str: @@ -355,6 +366,7 @@ async def import_files_and_validate_project( project, project_file, shuffled_data ) await _fix_file_e_tags(project, links_to_new_e_tags) + await _remove_runtime_states(project) await add_new_project(app, project, user_id) except Exception as e: log.warning( From ee872fca780f335779f7751e4c3c31284853e304 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 17:40:52 +0100 Subject: [PATCH 100/200] added some more logs --- .../exporter/formatters/formatter_v1.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index b46f12ecfb2..809e3aae639 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -234,16 +234,24 @@ async def _fix_node_run_hashes_based_on_old_project( new_node_id = node_mapping.get(old_node_id) if new_node_id is None: # this should not happen + log.warning("could not find new node id %s", new_node_id) continue new_node = project.workbench.get(new_node_id) if new_node is None: # this should also not happen + log.warning("could not find new node data from id %s", new_node_id) continue # check the node status in the old project old_computed_hash = await compute_node_hash( old_node_id, project_node_io_payload_cb(original_project) ) + log.debug( + "node %s old run hash: %s, computed old hash: %s", + old_node_id, + old_node.run_hash, + old_computed_hash, + ) node_needs_update = old_computed_hash != old_node.run_hash # set the new node hash new_node.run_hash = ( From e0f8f69023ffa4c68a0f088d093d865fb1ca8c05 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 18:31:18 +0100 Subject: [PATCH 101/200] fixed ETag reading --- .../exporter/formatters/formatter_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py index 809e3aae639..905acb8d5a7 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/formatter_v1.py @@ -196,7 +196,7 @@ async def file_sender(file_name=None): raise ExporterException( f"Client replied with status={resp.status} and body '{upload_result}'" ) - e_tag = resp.headers.get("Etag", None) + e_tag = json.loads(resp.headers.get("Etag", None)) log.debug( "Upload status=%s, result: '%s', Etag %s", resp.status, upload_result, e_tag ) From 20e049262cf8013c504c786777d54418ab1312fb Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 18:31:44 +0100 Subject: [PATCH 102/200] export only set payloda --- .../exporter/formatters/base_models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/exporter/formatters/base_models.py b/services/web/server/src/simcore_service_webserver/exporter/formatters/base_models.py index 12e6beb9d41..5f451deb8bb 100644 --- a/services/web/server/src/simcore_service_webserver/exporter/formatters/base_models.py +++ b/services/web/server/src/simcore_service_webserver/exporter/formatters/base_models.py @@ -97,6 +97,8 @@ async def model_to_file(cls, root_dir: Path, **kwargs: Dict[str, Any]) -> None: ), ) ) - to_store = model_obj.json(exclude={"storage_path"}, by_alias=True) + to_store = model_obj.json( + exclude={"storage_path"}, by_alias=True, exclude_unset=True + ) await model_obj.storage_path.data_to_file(to_store) return model_obj From ddacf8a6e87cc3d7ae03143bb961cc939e36ecd9 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Tue, 16 Feb 2021 22:42:52 +0100 Subject: [PATCH 103/200] fixed aiohttp mockup for director-v2 --- .../services_api_mocks_for_aiohttp_clients.py | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py index 8cf51693a01..fd299b4f129 100644 --- a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py +++ b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py @@ -22,25 +22,26 @@ } FULL_PROJECT_NODE_STATES: Dict[str, Dict[str, str]] = { - "62bca361-8594-48c8-875e-b8577e868aec": { - "io_state": "OUTDATED", - "runnable_state": "READY", - }, + "62bca361-8594-48c8-875e-b8577e868aec": {"modified": True, "dependencies": []}, "e0d7a1a5-0700-42c7-b033-97f72ac4a5cd": { - "io_state": "OUTDATED", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": True, + "dependencies": ["62bca361-8594-48c8-875e-b8577e868aec"], }, "5284bb5b-b068-4d0e-9075-3d5d8eec5060": { - "io_state": "OK", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": True, + "dependencies": ["62bca361-8594-48c8-875e-b8577e868aec"], }, "750454a8-b450-43ce-a012-40b669f7d28c": { - "io_state": "OUTDATED", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": True, + "dependencies": ["62bca361-8594-48c8-875e-b8577e868aec"], }, "e83a359a-1efe-41d3-83aa-a285afbfaf12": { - "io_state": "OK", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": True, + "dependencies": [ + "e0d7a1a5-0700-42c7-b033-97f72ac4a5cd", + "5284bb5b-b068-4d0e-9075-3d5d8eec5060", + "750454a8-b450-43ce-a012-40b669f7d28c", + ], }, } @@ -67,14 +68,17 @@ def creation_cb(url, **kwargs) -> CallbackResult: "62237c33-8d6c-4709-aa92-c3cf693dd6d2", "0bdf824f-57cb-4e38-949e-fd12c184f000", ] - node_states[node_id] = {"io_state": "OUTDATED", "runnable_state": "READY"} + node_states[node_id] = {"state": {"modified": True, "dependencies": []}} node_states["62237c33-8d6c-4709-aa92-c3cf693dd6d2"] = { - "io_state": "OUTDATED", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": True, + "dependencies": ["2f493631-30b4-4ad8-90f2-a74e4b46fe73"], } node_states["0bdf824f-57cb-4e38-949e-fd12c184f000"] = { - "io_state": "OUTDATED", - "runnable_state": "WAITING_FOR_DEPENDENCIES", + "modified": True, + "dependencies": [ + "2f493631-30b4-4ad8-90f2-a74e4b46fe73", + "62237c33-8d6c-4709-aa92-c3cf693dd6d2", + ], } return CallbackResult( From cb10a2ba16f81bc4002e13f04ce7c028cefd7324 Mon Sep 17 00:00:00 2001 From: sanderegg <35365065+sanderegg@users.noreply.github.com> Date: Wed, 17 Feb 2021 00:57:21 +0100 Subject: [PATCH 104/200] runnableState disappeared from the project --- services/web/server/tests/integration/test_exporter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/web/server/tests/integration/test_exporter.py b/services/web/server/tests/integration/test_exporter.py index 418ae11a5b2..eb49f2a6ff5 100644 --- a/services/web/server/tests/integration/test_exporter.py +++ b/services/web/server/tests/integration/test_exporter.py @@ -70,10 +70,9 @@ "uuid", "creation_date", "last_change_date", - REMAPPING_KEY, "runHash", # this changes after import, but the runnable states should remain the same "eTag", # this must change - "runnableState", # this should actually not be in the DB + REMAPPING_KEY, } From fc00561688af2c569c10b4cb099ba3f34b49546c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 10:26:03 +0100 Subject: [PATCH 105/200] NodeStatus props renaming --- .../source/class/osparc/data/model/Node.js | 28 +++++++++---------- .../class/osparc/data/model/NodeStatus.js | 16 +++++------ .../class/osparc/desktop/WorkbenchView.js | 6 ++-- .../class/osparc/ui/basic/NodeStatusUI.js | 16 +++++------ 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 4767904f2c6..69cac8e530c 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -332,13 +332,13 @@ qx.Class.define("osparc.data.model.Node", { if (nodeData.state) { if (nodeData.state.currentStatus) { - this.getStatus().setRunningStatus(nodeData.state.currentStatus); + this.getStatus().setRunning(nodeData.state.currentStatus); } if (nodeData.state.modified) { - this.getStatus().setModifiedStatus(nodeData.state.modified); + this.getStatus().setModified(nodeData.state.modified); } if (nodeData.state.dependencies) { - this.getStatus().setDependenciesStatus(nodeData.state.dependencies); + this.getStatus().setDependencies(nodeData.state.dependencies); } } @@ -422,7 +422,7 @@ qx.Class.define("osparc.data.model.Node", { msg: errorMsg }; this.fireDataEvent("showInLogger", errorMsgData); - this.getStatus().setInteractiveStatus("failed"); + this.getStatus().setInteractive("failed"); osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("There was an error while starting the node."), "ERROR"); }); }, @@ -751,7 +751,7 @@ qx.Class.define("osparc.data.model.Node", { }, __getLoadingPageHeader: function() { - const status = this.getStatus().getInteractiveStatus(); + const status = this.getStatus().getInteractive(); const label = this.getLabel(); if (status) { const sta = status.charAt(0).toUpperCase() + status.slice(1); @@ -766,7 +766,7 @@ qx.Class.define("osparc.data.model.Node", { this.addListener("changeLabel", e => { loadingPage.setHeader(this.__getLoadingPageHeader()); }, this); - this.getStatus().addListener("changeInteractiveStatus", e => { + this.getStatus().addListener("changeInteractive", e => { loadingPage.setHeader(this.__getLoadingPageHeader()); }, this); this.setLoadingPage(loadingPage); @@ -905,7 +905,7 @@ qx.Class.define("osparc.data.model.Node", { const status = this.getStatus(); status.setProgress(0); - status.setInteractiveStatus("starting"); + status.setInteractive("starting"); this.__nodeState(); } @@ -915,20 +915,20 @@ qx.Class.define("osparc.data.model.Node", { const status = this.getStatus(); switch (serviceState) { case "idle": { - status.setInteractiveStatus("idle"); + status.setInteractive("idle"); const interval = 1000; qx.event.Timer.once(() => this.__nodeState(), this, interval); break; } case "starting": case "pulling": { - status.setInteractiveStatus(serviceState); + status.setInteractive(serviceState); const interval = 5000; qx.event.Timer.once(() => this.__nodeState(), this, interval); break; } case "pending": { - status.setInteractiveStatus("pending"); + status.setInteractive("pending"); const interval = 10000; qx.event.Timer.once(() => this.__nodeState(), this, interval); break; @@ -950,7 +950,7 @@ qx.Class.define("osparc.data.model.Node", { case "complete": break; case "failed": { - status.setInteractiveStatus("failed"); + status.setInteractive("failed"); const msg = "Service failed: " + data["service_message"]; const msgData = { nodeId: this.getNodeId(), @@ -991,7 +991,7 @@ qx.Class.define("osparc.data.model.Node", { msg: errorMsg }; this.fireDataEvent("showInLogger", errorMsgData); - this.getStatus().setInteractiveStatus("failed"); + this.getStatus().setInteractive("failed"); osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("There was an error while starting the node."), "ERROR"); }); }, @@ -1021,7 +1021,7 @@ qx.Class.define("osparc.data.model.Node", { }, this); pingRequest.addListenerOnce("fail", e => { const error = e.getTarget().getResponse(); - this.getStatus().setInteractiveStatus("connecting"); + this.getStatus().setInteractive("connecting"); console.log("service not ready yet, waiting... " + error); // Check if node is still there const study = osparc.store.Store.getInstance().getCurrentStudy(); @@ -1035,7 +1035,7 @@ qx.Class.define("osparc.data.model.Node", { }, __serviceReadyIn: function(srvUrl) { this.setServiceUrl(srvUrl); - this.getStatus().setInteractiveStatus("ready"); + this.getStatus().setInteractive("ready"); const msg = "Service ready on " + srvUrl; const msgData = { nodeId: this.getNodeId(), diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index f88cf97573e..f898be2f50d 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -34,28 +34,28 @@ qx.Class.define("osparc.data.model.NodeStatus", { event: "changeProgress" }, - runningStatus: { + running: { check: ["UNKNOWN", "NOT_STARTED", "PUBLISHED", "PENDING", "STARTED", "RETRY", "SUCCESS", "FAILED", "ABORTED"], nullable: true, - event: "changeRunningStatus" + event: "changeRunning" }, - interactiveStatus: { + interactive: { check: ["idle", "starting", "pulling", "pending", "connecting", "ready", "failed"], nullable: true, - event: "changeInteractiveStatus" + event: "changeInteractive" }, - modifiedStatus: { + modified: { check: "Boolean", nullable: true, - event: "changeModifiedStatus" + event: "changeModified" }, - dependenciesStatus: { + dependencies: { check: "Array", nullable: true, - event: "changeDependenciesStatus" + event: "changeDependencies" } } }); diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index ab5f2747682..e22dea8d283 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -534,13 +534,13 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const node = workbench.getNode(nodeId); if (node && nodeData) { node.setOutputData(nodeData.outputs); - if ("state" in nodeData && node.isComputational()) { - node.getStatus().setRunningStatus(nodeData["state"]["currentStatus"]); - } if ("progress" in nodeData) { const progress = Number.parseInt(nodeData["progress"]); node.getStatus().setProgress(progress); } + if ("state" in nodeData && node.isComputational()) { + node.getStatus().setRunning(nodeData["state"]["currentStatus"]); + } } else if (osparc.data.Permissions.getInstance().isTester()) { console.log("Ignored ws 'nodeUpdated' msg", d); } diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index eb2115857ca..61273a94614 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -55,7 +55,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { }, __setupComputational: function() { - this.__node.getStatus().bind("runningStatus", this.__label, "value", { + this.__node.getStatus().bind("running", this.__label, "value", { converter: state => { if (state) { this.show(); @@ -69,7 +69,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { } }); - this.__node.getStatus().bind("runningStatus", this.__icon, "source", { + this.__node.getStatus().bind("running", this.__icon, "source", { converter: state => { switch (state) { case "SUCCESS": @@ -90,7 +90,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { }, onUpdate: (source, target) => { target.show(); - const state = source.getRunningStatus(); + const state = source.getRunning(); switch (state) { case "SUCCESS": this.__removeClass(this.__icon.getContentElement(), "rotate"); @@ -119,7 +119,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { }, __setupInteractive: function() { - this.__node.getStatus().bind("interactiveStatus", this.__label, "value", { + this.__node.getStatus().bind("interactive", this.__label, "value", { converter: status => { if (status === "ready") { return this.tr("Ready"); @@ -138,7 +138,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { } }); - this.__node.getStatus().bind("interactiveStatus", this.__icon, "source", { + this.__node.getStatus().bind("interactive", this.__icon, "source", { converter: status => { if (status === "ready") { return "@FontAwesome5Solid/check/12"; @@ -156,12 +156,12 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { return ""; }, onUpdate: (source, target) => { - if (source.getInteractiveStatus() == null) { + if (source.getInteractive() == null) { this.__removeClass(this.__icon.getContentElement(), "rotate"); - } else if (source.getInteractiveStatus() === "ready") { + } else if (source.getInteractive() === "ready") { this.__removeClass(this.__icon.getContentElement(), "rotate"); target.setTextColor("ready-green"); - } else if (source.getInteractiveStatus() === "failed") { + } else if (source.getInteractive() === "failed") { this.__removeClass(this.__icon.getContentElement(), "rotate"); target.setTextColor("failed-red"); } else { From 3c67b2e8ca5edb662dfcd8fe205f7a0cc8b52975 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 11:05:06 +0100 Subject: [PATCH 106/200] warning-yellow and busy-orange color added --- services/web/client/source/class/osparc/theme/mixin/Color.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/theme/mixin/Color.js b/services/web/client/source/class/osparc/theme/mixin/Color.js index 17bf9ef8fe0..45e2dbd99b1 100644 --- a/services/web/client/source/class/osparc/theme/mixin/Color.js +++ b/services/web/client/source/class/osparc/theme/mixin/Color.js @@ -2,9 +2,10 @@ qx.Theme.define("osparc.theme.mixin.Color", { colors: { "activitytree-background-cpu": "#2C7CCE", "activitytree-background-memory": "#358475", - // "ready-green": "#33925A", "ready-green": "#58A6FF", // It is not really green because of reasons + "warning-yellow": "#FFFF00", "failed-red": "#FF2D2D", + "busy-orange": "#FFA500", "contrasted-text-dark": "#222222", "contrasted-text-light": "#EEEEEE" } From 72ca1551e43b203dcd740f8374b1abda6e576874 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 11:12:31 +0100 Subject: [PATCH 107/200] border bound to running status --- .../osparc/component/workbench/NodeUI.js | 20 +++++++++++ .../source/class/osparc/theme/Decoration.js | 35 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 71307adfaa7..595ff4248ee 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -192,6 +192,26 @@ qx.Class.define("osparc.component.workbench.NodeUI", { if (node.isComputational() || node.isFilePicker()) { node.getStatus().bind("progress", this.__progressBar, "value"); } + node.getStatus().bind("running", this, "decorator", { + converter: state => { + switch (state) { + case "SUCCESS": + return "border-ok"; + case "FAILED": + case "ABORTED": + return "border-error"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "border-busy"; + case "UNKNOWN": + case "NOT_STARTED": + default: + return "no-border"; + } + } + }); }, getInputPort: function() { diff --git a/services/web/client/source/class/osparc/theme/Decoration.js b/services/web/client/source/class/osparc/theme/Decoration.js index 943f5e393dd..db8602b85fe 100644 --- a/services/web/client/source/class/osparc/theme/Decoration.js +++ b/services/web/client/source/class/osparc/theme/Decoration.js @@ -143,6 +143,41 @@ qx.Theme.define("osparc.theme.Decoration", { } }, + "border-status": { + decorator: qx.ui.decoration.MSingleBorder, + style: { + width: 1 + } + }, + + "border-ok": { + include: "border-status", + style: { + color: "ready-green" + } + }, + + "border-warning": { + include: "border-status", + style: { + color: "warning-yellow" + } + }, + + "border-error": { + include: "border-status", + style: { + color: "failed-red" + } + }, + + "border-busy": { + include: "border-status", + style: { + color: "busy-orange" + } + }, + "border-editable": { style: { width: 1, From e6907678a903ea31ab87201881d12e1c3c840bf4 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 11:40:39 +0100 Subject: [PATCH 108/200] osparc.utils.StatusUI created --- .../osparc/component/workbench/NodeUI.js | 19 +------- .../source/class/osparc/utils/StatusUI.js | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 services/web/client/source/class/osparc/utils/StatusUI.js diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 595ff4248ee..79ba627ce2f 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -193,24 +193,7 @@ qx.Class.define("osparc.component.workbench.NodeUI", { node.getStatus().bind("progress", this.__progressBar, "value"); } node.getStatus().bind("running", this, "decorator", { - converter: state => { - switch (state) { - case "SUCCESS": - return "border-ok"; - case "FAILED": - case "ABORTED": - return "border-error"; - case "PENDING": - case "PUBLISHED": - case "STARTED": - case "RETRY": - return "border-busy"; - case "UNKNOWN": - case "NOT_STARTED": - default: - return "no-border"; - } - } + converter: state => osparc.utils.StatusUI.getBorderDecorator(state); }); }, diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js new file mode 100644 index 00000000000..d8946339d2b --- /dev/null +++ b/services/web/client/source/class/osparc/utils/StatusUI.js @@ -0,0 +1,46 @@ +/* ************************************************************************ + + osparc - the simcore frontend + + https://osparc.io + + Copyright: + 2021 IT'IS Foundation, https://itis.swiss + + License: + MIT: https://opensource.org/licenses/MIT + + Authors: + * Odei Maiz (odeimaiz) + +************************************************************************ */ + +/** + * Collection of methods for dealing with ports. + * + */ + +qx.Class.define("osparc.utils.StatusUI", { + type: "static", + + statics: { + getBorderDecorator: function(state) { + switch (state) { + case "SUCCESS": + return "border-ok"; + case "FAILED": + case "ABORTED": + return "border-error"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "border-busy"; + case "UNKNOWN": + case "NOT_STARTED": + default: + return "no-border"; + } + } + } +}); From 80e1566a21addeb3c456da3babbcaedaf777d842 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 11:41:02 +0100 Subject: [PATCH 109/200] Update NodeUI.js --- .../client/source/class/osparc/component/workbench/NodeUI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 79ba627ce2f..9497e7d77b8 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -193,7 +193,7 @@ qx.Class.define("osparc.component.workbench.NodeUI", { node.getStatus().bind("progress", this.__progressBar, "value"); } node.getStatus().bind("running", this, "decorator", { - converter: state => osparc.utils.StatusUI.getBorderDecorator(state); + converter: state => osparc.utils.StatusUI.getBorderDecorator(state) }); }, From 3daad8b1ca90d52494a36ae3304482b003f26b86 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 12:33:46 +0100 Subject: [PATCH 110/200] more to StatusUI --- .../class/osparc/ui/basic/NodeStatusUI.js | 53 ++----------------- .../source/class/osparc/utils/StatusUI.js | 51 ++++++++++++++++++ 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index 61273a94614..a6326f58339 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -70,24 +70,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { }); this.__node.getStatus().bind("running", this.__icon, "source", { - converter: state => { - switch (state) { - case "SUCCESS": - return "@FontAwesome5Solid/check/12"; - case "FAILED": - case "ABORTED": - return "@FontAwesome5Solid/exclamation-circle/12"; - case "PENDING": - case "PUBLISHED": - case "STARTED": - case "RETRY": - return "@FontAwesome5Solid/circle-notch/12"; - case "UNKNOWN": - case "NOT_STARTED": - default: - return ""; - } - }, + converter: state => osparc.utils.StatusUI.getIconSource(state), onUpdate: (source, target) => { target.show(); const state = source.getRunning(); @@ -120,41 +103,11 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { __setupInteractive: function() { this.__node.getStatus().bind("interactive", this.__label, "value", { - converter: status => { - if (status === "ready") { - return this.tr("Ready"); - } else if (status === "failed") { - return this.tr("Error"); - } else if (status === "starting") { - return this.tr("Starting..."); - } else if (status === "pending") { - return this.tr("Pending..."); - } else if (status === "pulling") { - return this.tr("Pulling..."); - } else if (status === "connecting") { - return this.tr("Connecting..."); - } - return status; - } + converter: state => osparc.utils.StatusUI.getLabelValue(state) }); this.__node.getStatus().bind("interactive", this.__icon, "source", { - converter: status => { - if (status === "ready") { - return "@FontAwesome5Solid/check/12"; - } else if (status === "failed") { - return "@FontAwesome5Solid/exclamation-circle/12"; - } else if (status === "starting") { - return "@FontAwesome5Solid/circle-notch/12"; - } else if (status === "pending") { - return "@FontAwesome5Solid/circle-notch/12"; - } else if (status === "pulling") { - return "@FontAwesome5Solid/circle-notch/12"; - } else if (status === "connecting") { - return "@FontAwesome5Solid/circle-notch/12"; - } - return ""; - }, + converter: state => osparc.utils.StatusUI.getIconSource(state), onUpdate: (source, target) => { if (source.getInteractive() == null) { this.__removeClass(this.__icon.getContentElement(), "rotate"); diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js index d8946339d2b..02d32e253e0 100644 --- a/services/web/client/source/class/osparc/utils/StatusUI.js +++ b/services/web/client/source/class/osparc/utils/StatusUI.js @@ -24,6 +24,57 @@ qx.Class.define("osparc.utils.StatusUI", { type: "static", statics: { + getIconSource: function(state) { + switch (state) { + // computationals + case "SUCCESS": + return "@FontAwesome5Solid/check/12"; + case "FAILED": + case "ABORTED": + return "@FontAwesome5Solid/exclamation-circle/12"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "@FontAwesome5Solid/circle-notch/12"; + + // dynamics + case "ready": + return "@FontAwesome5Solid/check/12"; + case "failed": + return "@FontAwesome5Solid/exclamation-circle/12"; + case "starting": + case "pending": + case "pulling": + case "connecting": + return "@FontAwesome5Solid/circle-notch/12"; + + case "UNKNOWN": + case "NOT_STARTED": + default: + return ""; + } + }, + + getLabelValue: function(state) { + switch (state) { + case "ready": + return this.tr("Ready"); + case "failed": + return this.tr("Error"); + case "starting": + return this.tr("Starting..."); + case "pending": + return this.tr("Pending..."); + case "pulling": + return this.tr("Pulling..."); + case "connecting": + return this.tr("Connecting..."); + default: + return state; + } + }, + getBorderDecorator: function(state) { switch (state) { case "SUCCESS": From 176ed2ee0b64dc672674cadb570ef1c4a73d6324 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 12:47:08 +0100 Subject: [PATCH 111/200] more to StatusUI --- .../class/osparc/ui/basic/NodeStatusUI.js | 30 +++++++++---------- .../source/class/osparc/utils/StatusUI.js | 20 +++++++++++++ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index a6326f58339..b286fec88b0 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -76,13 +76,10 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { const state = source.getRunning(); switch (state) { case "SUCCESS": - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("ready-green"); - return; case "FAILED": case "ABORTED": this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("failed-red"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); return; case "PENDING": case "PUBLISHED": @@ -109,17 +106,20 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { this.__node.getStatus().bind("interactive", this.__icon, "source", { converter: state => osparc.utils.StatusUI.getIconSource(state), onUpdate: (source, target) => { - if (source.getInteractive() == null) { - this.__removeClass(this.__icon.getContentElement(), "rotate"); - } else if (source.getInteractive() === "ready") { - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("ready-green"); - } else if (source.getInteractive() === "failed") { - this.__removeClass(this.__icon.getContentElement(), "rotate"); - target.setTextColor("failed-red"); - } else { - this.__addClass(this.__icon.getContentElement(), "rotate"); - target.resetTextColor(); + const state = source.getInteractive(); + switch (state) { + case null: + this.__removeClass(this.__icon.getContentElement(), "rotate"); + break; + case "ready": + case "failed": + this.__removeClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); + break; + default: + this.__addClass(this.__icon.getContentElement(), "rotate"); + target.resetTextColor(); + break; } } }); diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js index 02d32e253e0..4e9e386e725 100644 --- a/services/web/client/source/class/osparc/utils/StatusUI.js +++ b/services/web/client/source/class/osparc/utils/StatusUI.js @@ -75,6 +75,26 @@ qx.Class.define("osparc.utils.StatusUI", { } }, + getColor: function(state) { + switch (state) { + // computationals + case "SUCCESS": + return "ready-green"; + case "FAILED": + case "ABORTED": + return "failed-red"; + + // dynamics + case "ready": + return "ready-green"; + case "failed": + return "failed-red"; + + default: + return null; + } + }, + getBorderDecorator: function(state) { switch (state) { case "SUCCESS": From f2833cff33ddd116ac97f0f638e282804b3f86c4 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 15:02:34 +0100 Subject: [PATCH 112/200] NodeStatusUI text color bound to state --- .../class/osparc/ui/basic/NodeStatusUI.js | 16 +++++--- .../source/class/osparc/utils/StatusUI.js | 37 ++++++++++++++----- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index b286fec88b0..da6ca4a9159 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -59,13 +59,15 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { converter: state => { if (state) { this.show(); - if (state === "STARTED") { - state = "Running"; - } - return qx.lang.String.firstUp(state.toLowerCase()); + const labelValue = osparc.utils.StatusUI.getLabelValue(state); + return qx.lang.String.firstUp(labelValue.toLowerCase()); } this.exclude(); return null; + }, + onUpdate: (source, target) => { + const state = source.getRunning(); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); } }); @@ -100,7 +102,11 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { __setupInteractive: function() { this.__node.getStatus().bind("interactive", this.__label, "value", { - converter: state => osparc.utils.StatusUI.getLabelValue(state) + converter: state => osparc.utils.StatusUI.getLabelValue(state), + onUpdate: (source, target) => { + const state = source.getInteractive(); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); + } }); this.__node.getStatus().bind("interactive", this.__icon, "source", { diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js index 4e9e386e725..41dd952ab57 100644 --- a/services/web/client/source/class/osparc/utils/StatusUI.js +++ b/services/web/client/source/class/osparc/utils/StatusUI.js @@ -16,7 +16,7 @@ ************************************************************************ */ /** - * Collection of methods for dealing with ports. + * Collection of methods for dealing status decorators. * */ @@ -37,6 +37,9 @@ qx.Class.define("osparc.utils.StatusUI", { case "STARTED": case "RETRY": return "@FontAwesome5Solid/circle-notch/12"; + case "UNKNOWN": + case "NOT_STARTED": + return ""; // dynamics case "ready": @@ -49,8 +52,6 @@ qx.Class.define("osparc.utils.StatusUI", { case "connecting": return "@FontAwesome5Solid/circle-notch/12"; - case "UNKNOWN": - case "NOT_STARTED": default: return ""; } @@ -58,18 +59,24 @@ qx.Class.define("osparc.utils.StatusUI", { getLabelValue: function(state) { switch (state) { + // computationals + case "STARTED": + return qx.locale.Manager.tr("Running"); + + // dynamics case "ready": - return this.tr("Ready"); + return qx.locale.Manager.tr("Ready"); case "failed": - return this.tr("Error"); + return qx.locale.Manager.tr("Error"); case "starting": - return this.tr("Starting..."); + return qx.locale.Manager.tr("Starting..."); case "pending": - return this.tr("Pending..."); + return qx.locale.Manager.tr("Pending..."); case "pulling": - return this.tr("Pulling..."); + return qx.locale.Manager.tr("Pulling..."); case "connecting": - return this.tr("Connecting..."); + return qx.locale.Manager.tr("Connecting..."); + default: return state; } @@ -83,6 +90,14 @@ qx.Class.define("osparc.utils.StatusUI", { case "FAILED": case "ABORTED": return "failed-red"; + case "PENDING": + case "PUBLISHED": + case "STARTED": + case "RETRY": + return "busy-orange"; + case "UNKNOWN": + case "NOT_STARTED": + return "text"; // dynamics case "ready": @@ -91,7 +106,7 @@ qx.Class.define("osparc.utils.StatusUI", { return "failed-red"; default: - return null; + return "text"; } }, @@ -109,6 +124,8 @@ qx.Class.define("osparc.utils.StatusUI", { return "border-busy"; case "UNKNOWN": case "NOT_STARTED": + return "no-border"; + default: return "no-border"; } From 2e44f562fd4bd189df89a54a52bda68baaf0ee5e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 15:03:22 +0100 Subject: [PATCH 113/200] "in" and "out" on NodeUI bound to "dependencies" and "modified" --- .../class/osparc/component/workbench/NodeUI.js | 9 +++++++++ .../source/class/osparc/data/model/NodeStatus.js | 12 ++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 9497e7d77b8..9baaa9cd473 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -215,6 +215,15 @@ qx.Class.define("osparc.component.workbench.NodeUI", { isInput: isInput, ui: portLabel }; + if (isInput) { + this.getNode().getStatus().bind("dependencies", portLabel, "textColor", { + converter: dependencies => osparc.utils.StatusUI.getColor(dependencies.length ? "ready" : "failed") + }); + } else { + this.getNode().getStatus().bind("modified", portLabel, "textColor", { + converter: modified => osparc.utils.StatusUI.getColor(modified ? "failed" : "ready") + }); + } label.ui.isInput = isInput; this.__addDragDropMechanism(label.ui, isInput); if (isInput) { diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index f898be2f50d..aea08196a26 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -46,16 +46,16 @@ qx.Class.define("osparc.data.model.NodeStatus", { event: "changeInteractive" }, - modified: { - check: "Boolean", - nullable: true, - event: "changeModified" - }, - dependencies: { check: "Array", nullable: true, event: "changeDependencies" + }, + + modified: { + check: "Boolean", + nullable: true, + event: "changeModified" } } }); From 8180d645181a0c0bff66160a4e3800a9dcb0aeea Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Wed, 17 Feb 2021 17:26:35 +0100 Subject: [PATCH 114/200] Edges with colors --- .../source/class/osparc/component/workbench/EdgeUI.js | 11 +++++++++++ .../source/class/osparc/component/workbench/NodeUI.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/component/workbench/EdgeUI.js b/services/web/client/source/class/osparc/component/workbench/EdgeUI.js index b79b515a946..841fdf8cdec 100644 --- a/services/web/client/source/class/osparc/component/workbench/EdgeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/EdgeUI.js @@ -44,6 +44,17 @@ qx.Class.define("osparc.component.workbench.EdgeUI", { this.setEdge(edge); this.setRepresentation(representation); + edge.getInputNode().getStatus().addListener("changeModified", e => { + const modified = e.getData(); + const newColor = osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); + const newColorHex = qx.theme.manager.Color.getInstance().resolve(newColor); + osparc.component.workbench.SvgWidget.updateCurveColor(representation, newColorHex); + }); + const modified = edge.getInputNode().getStatus().getModified(); + const newColor = osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); + const newColorHex = qx.theme.manager.Color.getInstance().resolve(newColor); + osparc.component.workbench.SvgWidget.updateCurveColor(representation, newColorHex); + this.subscribeToFilterGroup("workbench"); }, diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 9baaa9cd473..2c9eead2de8 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -217,7 +217,7 @@ qx.Class.define("osparc.component.workbench.NodeUI", { }; if (isInput) { this.getNode().getStatus().bind("dependencies", portLabel, "textColor", { - converter: dependencies => osparc.utils.StatusUI.getColor(dependencies.length ? "ready" : "failed") + converter: dependencies => osparc.utils.StatusUI.getColor(dependencies.length ? "failed" : "ready") }); } else { this.getNode().getStatus().bind("modified", portLabel, "textColor", { From 5fd6821d575f568c8da8a252a5dedfa81624af9e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 10:22:30 +0100 Subject: [PATCH 115/200] bad merge --- packages/models-library/requirements/ci.txt | 1 + .../web/server/tests/integration/test_exporter.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/packages/models-library/requirements/ci.txt b/packages/models-library/requirements/ci.txt index c30fb277fdf..7420b314e63 100644 --- a/packages/models-library/requirements/ci.txt +++ b/packages/models-library/requirements/ci.txt @@ -11,6 +11,7 @@ -r _test.txt # installs this repo's packages +../pytest-simcore/ ../postgres-database/ # current module diff --git a/services/web/server/tests/integration/test_exporter.py b/services/web/server/tests/integration/test_exporter.py index 0b1bdd5cb94..a5c907a6508 100644 --- a/services/web/server/tests/integration/test_exporter.py +++ b/services/web/server/tests/integration/test_exporter.py @@ -285,6 +285,21 @@ def replace_uuids_with_sequences(original_project: Dict[str, Any]) -> Dict[str, return project +def dict_with_keys(dict_data: Dict[str, Any], kept_keys: Set[str]) -> Dict[str, Any]: + modified_dict = {} + for key, value in dict_data.items(): + if key in kept_keys: + # keep the whole object + modified_dict[key] = deepcopy(value) + # if it's a nested dict go deeper + elif isinstance(value, dict): + possible_nested_dict = dict_with_keys(value, kept_keys) + if possible_nested_dict: + modified_dict[key] = possible_nested_dict + + return modified_dict + + def dict_without_keys( dict_data: Dict[str, Any], skipped_keys: Set[str] ) -> Dict[str, Any]: From a00ad25101277d3d230d54def4b434e5ecda398b Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 10:39:18 +0100 Subject: [PATCH 116/200] Listen to nodeUpdated state --- .../web/client/source/class/osparc/data/model/Node.js | 6 +++--- .../client/source/class/osparc/desktop/WorkbenchView.js | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index ea194b18841..69cac8e530c 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -332,13 +332,13 @@ qx.Class.define("osparc.data.model.Node", { if (nodeData.state) { if (nodeData.state.currentStatus) { - this.getStatus().setRunningStatus(nodeData.state.currentStatus); + this.getStatus().setRunning(nodeData.state.currentStatus); } if (nodeData.state.modified) { - this.getStatus().setModifiedStatus(nodeData.state.modified); + this.getStatus().setModified(nodeData.state.modified); } if (nodeData.state.dependencies) { - this.getStatus().setDependenciesStatus(nodeData.state.dependencies); + this.getStatus().setDependencies(nodeData.state.dependencies); } } diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index e22dea8d283..6fb57af06d1 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -538,8 +538,13 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const progress = Number.parseInt(nodeData["progress"]); node.getStatus().setProgress(progress); } - if ("state" in nodeData && node.isComputational()) { - node.getStatus().setRunning(nodeData["state"]["currentStatus"]); + if ("state" in nodeData) { + const state = nodeData["state"]; + if (node.isComputational()) { + node.getStatus().setRunning(state["currentStatus"]); + } + this.getStatus().setModified(state["modified"]); + this.getStatus().setDependencies(state["dependencies"]); } } else if (osparc.data.Permissions.getInstance().isTester()) { console.log("Ignored ws 'nodeUpdated' msg", d); From ec00adcefb5343b6298da32662eaa73087fd52e6 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 10:46:07 +0100 Subject: [PATCH 117/200] Update WorkbenchView.js --- .../source/class/osparc/desktop/WorkbenchView.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index 6fb57af06d1..49d0c1542bf 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -540,11 +540,15 @@ qx.Class.define("osparc.desktop.WorkbenchView", { } if ("state" in nodeData) { const state = nodeData["state"]; - if (node.isComputational()) { + if ("currentStatus" in state && node.isComputational()) { node.getStatus().setRunning(state["currentStatus"]); } - this.getStatus().setModified(state["modified"]); - this.getStatus().setDependencies(state["dependencies"]); + if ("modified" in state) { + node.getStatus().setModified(state["modified"]); + } + if ("dependencies" in state) { + node.getStatus().setDependencies(state["dependencies"]); + } } } else if (osparc.data.Permissions.getInstance().isTester()) { console.log("Ignored ws 'nodeUpdated' msg", d); From 475a1b944ed5bbccd8a353c09467b6c7e4494fcc Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 13:34:32 +0100 Subject: [PATCH 118/200] init props --- .../osparc/component/workbench/EdgeUI.js | 34 ++++++++++++++----- .../osparc/component/workbench/NodeUI.js | 14 ++++++-- .../osparc/component/workbench/WorkbenchUI.js | 10 +++--- .../class/osparc/data/model/NodeStatus.js | 4 +++ 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/services/web/client/source/class/osparc/component/workbench/EdgeUI.js b/services/web/client/source/class/osparc/component/workbench/EdgeUI.js index 841fdf8cdec..2b1dad1a9be 100644 --- a/services/web/client/source/class/osparc/component/workbench/EdgeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/EdgeUI.js @@ -44,16 +44,10 @@ qx.Class.define("osparc.component.workbench.EdgeUI", { this.setEdge(edge); this.setRepresentation(representation); - edge.getInputNode().getStatus().addListener("changeModified", e => { - const modified = e.getData(); - const newColor = osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); - const newColorHex = qx.theme.manager.Color.getInstance().resolve(newColor); - osparc.component.workbench.SvgWidget.updateCurveColor(representation, newColorHex); + edge.getInputNode().getStatus().addListener("changeModified", () => { + this.__updateCurveColor(); }); - const modified = edge.getInputNode().getStatus().getModified(); - const newColor = osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); - const newColorHex = qx.theme.manager.Color.getInstance().resolve(newColor); - osparc.component.workbench.SvgWidget.updateCurveColor(representation, newColorHex); + this.__updateCurveColor(); this.subscribeToFilterGroup("workbench"); }, @@ -70,6 +64,28 @@ qx.Class.define("osparc.component.workbench.EdgeUI", { }, members: { + __updateCurveColor: function() { + const modified = this.getEdge().getInputNode().getStatus() + .getModified(); + let newColor = null; + if (modified === null) { + newColor = qx.theme.manager.Color.getInstance().resolve("workbench-edge-comp-active"); + } else { + newColor = osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); + } + const newColorHex = qx.theme.manager.Color.getInstance().resolve(newColor); + osparc.component.workbench.SvgWidget.updateCurveColor(this.getRepresentation(), newColorHex); + }, + + setSelected: function(selected) { + if (selected) { + const selectedColor = qx.theme.manager.Color.getInstance().resolve("workbench-edge-selected"); + osparc.component.workbench.SvgWidget.updateCurveColor(this.getRepresentation(), selectedColor); + } else { + this.__updateCurveColor(); + } + }, + getEdgeId: function() { return this.getEdge().getEdgeId(); }, diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 2c9eead2de8..7d870d1e38b 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -217,11 +217,21 @@ qx.Class.define("osparc.component.workbench.NodeUI", { }; if (isInput) { this.getNode().getStatus().bind("dependencies", portLabel, "textColor", { - converter: dependencies => osparc.utils.StatusUI.getColor(dependencies.length ? "failed" : "ready") + converter: dependencies => { + if (dependencies !== null) { + return osparc.utils.StatusUI.getColor(dependencies.length ? "failed" : "ready"); + } + return osparc.utils.StatusUI.getColor(null); + } }); } else { this.getNode().getStatus().bind("modified", portLabel, "textColor", { - converter: modified => osparc.utils.StatusUI.getColor(modified ? "failed" : "ready") + converter: modified => { + if (modified !== null) { + return osparc.utils.StatusUI.getColor(modified ? "failed" : "ready"); + } + return osparc.utils.StatusUI.getColor(null); + } }); } label.ui.isInput = isInput; diff --git a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js index 88b5224e380..c6889462d8a 100644 --- a/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js +++ b/services/web/client/source/class/osparc/component/workbench/WorkbenchUI.js @@ -968,17 +968,15 @@ qx.Class.define("osparc.component.workbench.WorkbenchUI", { const oldId = this.__selectedItemId; if (oldId) { if (this.__isSelectedItemAnEdge()) { - const unselectedEdge = this.__getEdgeUI(oldId); - const unselectedColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge-comp-active"]; - osparc.component.workbench.SvgWidget.updateCurveColor(unselectedEdge.getRepresentation(), unselectedColor); + const edge = this.__getEdgeUI(oldId); + edge.setSelected(false); } } this.__selectedItemId = newID; if (this.__isSelectedItemAnEdge()) { - const selectedEdge = this.__getEdgeUI(newID); - const selectedColor = qx.theme.manager.Color.getInstance().getTheme().colors["workbench-edge-selected"]; - osparc.component.workbench.SvgWidget.updateCurveColor(selectedEdge.getRepresentation(), selectedColor); + const edge = this.__getEdgeUI(newID); + edge.setSelected(true); } else if (newID) { this.fireDataEvent("changeSelectedNode", newID); } diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index aea08196a26..4b4597f1e0f 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -37,24 +37,28 @@ qx.Class.define("osparc.data.model.NodeStatus", { running: { check: ["UNKNOWN", "NOT_STARTED", "PUBLISHED", "PENDING", "STARTED", "RETRY", "SUCCESS", "FAILED", "ABORTED"], nullable: true, + init: null, event: "changeRunning" }, interactive: { check: ["idle", "starting", "pulling", "pending", "connecting", "ready", "failed"], nullable: true, + init: null, event: "changeInteractive" }, dependencies: { check: "Array", nullable: true, + init: null, event: "changeDependencies" }, modified: { check: "Boolean", nullable: true, + init: null, event: "changeModified" } } From 80d6fe0e1fee45006617d7d51a7c69ff2ea86b73 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 15:25:26 +0100 Subject: [PATCH 119/200] Only show Thumbnail in Medium if not default thumbnail --- .../client/source/class/osparc/studycard/Medium.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/osparc/studycard/Medium.js b/services/web/client/source/class/osparc/studycard/Medium.js index 732d915e5d4..b009106f7ca 100644 --- a/services/web/client/source/class/osparc/studycard/Medium.js +++ b/services/web/client/source/class/osparc/studycard/Medium.js @@ -105,7 +105,10 @@ qx.Class.define("osparc.studycard.Medium", { this._add(extraInfoLayout); thumbnailWidth = Math.min(thumbnailWidth, this.self().THUMBNAIL_MAX_WIDTH); const thumbnail = this.__createThumbnail(thumbnailWidth, maxThumbnailHeight); - this._add(thumbnail); + if (thumbnail.getChildControl("image").getSource() !== osparc.dashboard.StudyBrowserButtonItem.STUDY_ICON) { + // Only show if not default thumbnail + this._add(thumbnail); + } } else { const hBox = new qx.ui.container.Composite(new qx.ui.layout.HBox(3).set({ alignX: "center" @@ -114,9 +117,12 @@ qx.Class.define("osparc.studycard.Medium", { thumbnailWidth -= this.self().EXTRA_INFO_WIDTH; thumbnailWidth = Math.min(thumbnailWidth, this.self().THUMBNAIL_MAX_WIDTH); const thumbnail = this.__createThumbnail(thumbnailWidth, maxThumbnailHeight); - hBox.add(thumbnail, { - flex: 1 - }); + if (thumbnail.getChildControl("image").getSource() !== osparc.dashboard.StudyBrowserButtonItem.STUDY_ICON) { + // Only show if not default thumbnail + hBox.add(thumbnail, { + flex: 1 + }); + } this._add(hBox); } From e47edc466390615791cd368a1debfbee037b0de6 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 15:25:43 +0100 Subject: [PATCH 120/200] Don't paint borders --- .../client/source/class/osparc/component/workbench/NodeUI.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js index 7d870d1e38b..5174502a61c 100644 --- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js +++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js @@ -192,9 +192,12 @@ qx.Class.define("osparc.component.workbench.NodeUI", { if (node.isComputational() || node.isFilePicker()) { node.getStatus().bind("progress", this.__progressBar, "value"); } + /* node.getStatus().bind("running", this, "decorator", { + // Paint borders converter: state => osparc.utils.StatusUI.getBorderDecorator(state) }); + */ }, getInputPort: function() { From a724dc6d980e9ebda7e437df0798b1f681afb564 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 15:52:28 +0100 Subject: [PATCH 121/200] Update StudyEditor.js --- .../source/class/osparc/desktop/StudyEditor.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js index 2dd1b64da67..a904f87948b 100644 --- a/services/web/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js @@ -348,11 +348,16 @@ qx.Class.define("osparc.desktop.StudyEditor", { const delta = diffPatcher.diff(this.__lastSavedStudy, newObj); if (delta) { let deltaKeys = Object.keys(delta); - // lastChangeDate should not be taken into account as data change - const index = deltaKeys.indexOf("lastChangeDate"); - if (index > -1) { - deltaKeys.splice(index, 1); - } + // lastChangeDate and creationDate should not be taken into account as data change + [ + "creationDate", + "lastChangeDate" + ].forEach(prop => { + const index = deltaKeys.indexOf(prop); + if (index > -1) { + deltaKeys.splice(index, 1); + } + }); if (deltaKeys.length > 0) { this.updateStudyDocument(false); } From f8f1dde7f0e534aad7e2808201100992d7178310 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 15:54:23 +0100 Subject: [PATCH 122/200] reuse updateStudy from StudyModel --- .../source/class/osparc/data/model/Study.js | 38 ++++++++----------- .../class/osparc/desktop/StudyEditor.js | 15 ++------ 2 files changed, 18 insertions(+), 35 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Study.js b/services/web/client/source/class/osparc/data/model/Study.js index 23705509cb0..2d436a40f51 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -189,20 +189,6 @@ qx.Class.define("osparc.data.model.Study", { return myNewStudyObject; }, - updateStudy: function(params) { - return new Promise(resolve => { - osparc.data.Resources.fetch("studies", "put", { - url: { - projectId: params.uuid - }, - data: params - }).then(data => { - qx.event.message.Bus.getInstance().dispatchByName("updateStudy", data); - resolve(data); - }); - }); - }, - getProperties: function() { return Object.keys(qx.util.PropertyUtil.getProperties(osparc.data.model.Study)); }, @@ -313,16 +299,22 @@ qx.Class.define("osparc.data.model.Study", { return jsonObject; }, - updateStudy: function(params) { + updateStudy: function(params, run = false) { return new Promise(resolve => { - this.self().updateStudy({ - ...this.serialize(), - ...params - }) - .then(data => { - this.__updateModel(data); - resolve(data); - }); + osparc.data.Resources.fetch("studies", "put", { + url: { + projectId: this.getUuid(), + run + }, + data: { + ...this.serialize(), + ...params + } + }).then(data => { + this.__updateModel(data); + qx.event.message.Bus.getInstance().dispatchByName("updateStudy", data); + resolve(data); + }); }); }, diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js index a904f87948b..4864c79df7d 100644 --- a/services/web/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js @@ -381,21 +381,12 @@ qx.Class.define("osparc.desktop.StudyEditor", { }); } - this.getStudy().setLastChangeDate(new Date()); const newObj = this.getStudy().serialize(); - const prjUuid = this.getStudy().getUuid(); - - const params = { - url: { - projectId: prjUuid, - run - }, - data: newObj - }; - return osparc.data.Resources.fetch("studies", "put", params) + return this.getStudy().updateStudy(newObj, run) .then(data => { this.__lastSavedStudy = osparc.wrapper.JsonDiffPatch.getInstance().clone(newObj); - }).catch(error => { + }) + .catch(error => { console.error(error); osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("Error saving the study"), "ERROR"); this.getLogger().error(null, "Error updating pipeline"); From 26665713b57227478bf9439d80596ba700826d8f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 16:10:32 +0100 Subject: [PATCH 123/200] Update Workbench.js --- .../class/osparc/data/model/Workbench.js | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Workbench.js b/services/web/client/source/class/osparc/data/model/Workbench.js index 72ad28f4506..3e05ac8d588 100644 --- a/services/web/client/source/class/osparc/data/model/Workbench.js +++ b/services/web/client/source/class/osparc/data/model/Workbench.js @@ -340,20 +340,20 @@ qx.Class.define("osparc.data.model.Workbench", { }, __deserializeNodes: function(workbenchData, workbenchUIData = {}) { - let keys = Object.keys(workbenchData); + const nodeIds = Object.keys(workbenchData); // Create first all the nodes - for (let i=0; i 1) { - if (keys[nKeys-1] === keys[nKeys-2]) { + if (nodeIds[nKeys-1] === nodeIds[nKeys-2]) { console.log(nodeId, "will never be created, parent missing", nodeData.parent); return; } @@ -371,21 +371,23 @@ qx.Class.define("osparc.data.model.Workbench", { } // Then populate them (this will avoid issues of connecting nodes that might not be created yet) - for (let i=0; i { + this.getNode(nodeId).giveUniqueName(); + }); + }, + + populateNodesData: function(workbenchData, workbenchUIData) { + Object.entries(workbenchData).forEach(([nodeId, nodeData]) => { this.getNode(nodeId).populateNodeData(nodeData); if ("position" in nodeData) { this.getNode(nodeId).populateNodeUIData(nodeData); } - if ("workbench" in workbenchUIData && nodeId in workbenchUIData.workbench) { + if (workbenchUIData && "workbench" in workbenchUIData && nodeId in workbenchUIData.workbench) { this.getNode(nodeId).populateNodeUIData(workbenchUIData.workbench[nodeId]); } - } - for (let i=0; i Date: Thu, 18 Feb 2021 16:14:31 +0100 Subject: [PATCH 124/200] Update Node.js --- .../source/class/osparc/data/model/Node.js | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 69cac8e530c..2e00b408170 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -330,22 +330,12 @@ qx.Class.define("osparc.data.model.Node", { this.populateInputOutputData(nodeData); - if (nodeData.state) { - if (nodeData.state.currentStatus) { - this.getStatus().setRunning(nodeData.state.currentStatus); - } - if (nodeData.state.modified) { - this.getStatus().setModified(nodeData.state.modified); - } - if (nodeData.state.dependencies) { - this.getStatus().setDependencies(nodeData.state.dependencies); - } - } - if ("progress" in nodeData) { this.getStatus().setProgress(nodeData.progress); } + this.populateStates(nodeData); + if (nodeData.thumbnail) { this.setThumbnail(nodeData.thumbnail); } @@ -378,6 +368,20 @@ qx.Class.define("osparc.data.model.Node", { this.addOutputNodes(nodeData.outputNodes); }, + populateStates: function(nodeData) { + if (nodeData.state) { + if (nodeData.state.currentStatus) { + this.getStatus().setRunning(nodeData.state.currentStatus); + } + if (nodeData.state.modified) { + this.getStatus().setModified(nodeData.state.modified); + } + if (nodeData.state.dependencies) { + this.getStatus().setDependencies(nodeData.state.dependencies); + } + } + }, + giveUniqueName: function() { const label = this.getLabel(); this.__giveUniqueName(label, 2); From de6e26c54a680113d32ec831eed15e00ad215d93 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 16:20:47 +0100 Subject: [PATCH 125/200] Update Workbench.js --- .../web/client/source/class/osparc/data/model/Workbench.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Workbench.js b/services/web/client/source/class/osparc/data/model/Workbench.js index 3e05ac8d588..a46bb75e63b 100644 --- a/services/web/client/source/class/osparc/data/model/Workbench.js +++ b/services/web/client/source/class/osparc/data/model/Workbench.js @@ -371,14 +371,14 @@ qx.Class.define("osparc.data.model.Workbench", { } // Then populate them (this will avoid issues of connecting nodes that might not be created yet) - this.populateNodesData(workbenchData, workbenchUIData); + this.__populateNodesData(workbenchData, workbenchUIData); nodeIds.forEach(nodeId => { this.getNode(nodeId).giveUniqueName(); }); }, - populateNodesData: function(workbenchData, workbenchUIData) { + __populateNodesData: function(workbenchData, workbenchUIData) { Object.entries(workbenchData).forEach(([nodeId, nodeData]) => { this.getNode(nodeId).populateNodeData(nodeData); if ("position" in nodeData) { From aadae34999958b6bee96f7486a6baae7afe26e36 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 16:21:12 +0100 Subject: [PATCH 126/200] update node states after put --- .../web/client/source/class/osparc/data/model/Study.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/web/client/source/class/osparc/data/model/Study.js b/services/web/client/source/class/osparc/data/model/Study.js index 2d436a40f51..b930a95a401 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -330,6 +330,14 @@ qx.Class.define("osparc.data.model.Study", { ui: this.getUi(), sweeper: this.getSweeper() }); + + const nodes = this.getWorkbench().getNodes(true); + nodes.forEach(node => { + const nodeId = node.getNodeId(); + if (nodeId in data.workbench) { + node.populateStates(data.workbench[nodeId]); + } + }); } } }); From d8033c5932d7986a1c29c51ba6820e37f2fd06a7 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 16:25:09 +0100 Subject: [PATCH 127/200] update node states after put II --- services/web/client/source/class/osparc/data/model/Study.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/data/model/Study.js b/services/web/client/source/class/osparc/data/model/Study.js index b930a95a401..292c75e2005 100644 --- a/services/web/client/source/class/osparc/data/model/Study.js +++ b/services/web/client/source/class/osparc/data/model/Study.js @@ -332,7 +332,7 @@ qx.Class.define("osparc.data.model.Study", { }); const nodes = this.getWorkbench().getNodes(true); - nodes.forEach(node => { + Object.values(nodes).forEach(node => { const nodeId = node.getNodeId(); if (nodeId in data.workbench) { node.populateStates(data.workbench[nodeId]); From cdf15feb64e4083514294667196599fef5363a9e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 16:55:15 +0100 Subject: [PATCH 128/200] Confirmation Window refactoring --- .../class/osparc/dashboard/StudyBrowser.js | 5 +++- .../class/osparc/ui/window/Confirmation.js | 30 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/services/web/client/source/class/osparc/dashboard/StudyBrowser.js b/services/web/client/source/class/osparc/dashboard/StudyBrowser.js index 0258651f8f4..dc9e22d5fed 100644 --- a/services/web/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/web/client/source/class/osparc/dashboard/StudyBrowser.js @@ -842,7 +842,10 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __createConfirmWindow: function(isMulti) { const msg = isMulti ? this.tr("Are you sure you want to delete the studies?") : this.tr("Are you sure you want to delete the study?"); - return new osparc.ui.window.Confirmation(msg); + const confirmationWin = new osparc.ui.window.Confirmation(msg); + const confirmButton = confirmationWin.getControlChild("confirm-button"); + osparc.utils.Utils.setIdToWidget(confirmButton, "confirmDeleteStudyBtn"); + return confirmationWin; } } }); diff --git a/services/web/client/source/class/osparc/ui/window/Confirmation.js b/services/web/client/source/class/osparc/ui/window/Confirmation.js index 714997f96a8..e433d9d10bf 100644 --- a/services/web/client/source/class/osparc/ui/window/Confirmation.js +++ b/services/web/client/source/class/osparc/ui/window/Confirmation.js @@ -16,18 +16,14 @@ qx.Class.define("osparc.ui.window.Confirmation", { * @extends osparc.ui.window.Dialog * @param {String} message Message that will be displayed to the user. */ - construct: function(message) { + construct: function(message, confirmBtnText = this.tr("Yes")) { this.base(arguments, this.tr("Confirmation"), null, message); this.addCancelButton(); - const btnYes = new qx.ui.toolbar.Button("Yes"); - osparc.utils.Utils.setIdToWidget(btnYes, "confirmDeleteStudyBtn"); - btnYes.addListener("execute", e => { - this.setConfirmed(true); - this.close(1); - }, this); - this.addButton(btnYes); + this._createChildControlImpl("confirm-button").set({ + label: confirmBtnText + }); }, properties: { @@ -35,5 +31,23 @@ qx.Class.define("osparc.ui.window.Confirmation", { check: "Boolean", init: false } + }, + + members: { + _createChildControlImpl: function(id) { + let control; + switch (id) { + case "confirm-button": { + control = new qx.ui.toolbar.Button(); + control.addListener("execute", e => { + this.setConfirmed(true); + this.close(1); + }, this); + this.addButton(control); + break; + } + } + return control || this.base(arguments, id); + } } }); From df316fc20f07cd7411bcbe3c2b66f6e5b70d0e32 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 16:58:20 +0100 Subject: [PATCH 129/200] forceRestart --- .../class/osparc/desktop/StudyEditor.js | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js index 4864c79df7d..230f4af208f 100644 --- a/services/web/client/source/class/osparc/desktop/StudyEditor.js +++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js @@ -194,7 +194,7 @@ qx.Class.define("osparc.desktop.StudyEditor", { } }, - __requestStartPipeline: function(studyId, selectedNodeIDs = []) { + __requestStartPipeline: function(studyId, selectedNodeIDs = [], forceRestart = false) { const url = "/computation/pipeline/" + encodeURIComponent(studyId) + ":start"; const req = new osparc.io.request.ApiRequest(url, "POST"); const startStopButtonsWB = this.__workbenchView.getStartStopButtons(); @@ -210,20 +210,30 @@ qx.Class.define("osparc.desktop.StudyEditor", { this.getLogger().error(null, "Pipeline is already running"); } else if (e.getTarget().getStatus() == "422") { this.getLogger().info(null, "The pipeline is up-to-date"); + const msg = this.tr("The pipeline is up-to-date. Do you want to re-run it?"); + const win = new osparc.ui.window.Confirmation(msg); + win.center(); + win.open(); + win.addListener("close", () => { + if (win.getConfirmed()) { + this.__requestStartPipeline(studyId, selectedNodeIDs, true); + } + }, this); } else { this.getLogger().error(null, "Failed submitting pipeline"); } startStopButtonsWB.setRunning(false); startStopButtonsSS.setRunning(false); }, this); + + req.setRequestData({ + "subgraph": selectedNodeIDs, + "force_restart": forceRestart + }); + req.send(); if (selectedNodeIDs.length) { - req.setRequestData({ - "subgraph": selectedNodeIDs - }); - req.send(); this.getLogger().info(null, "Starting partial pipeline"); } else { - req.send(); this.getLogger().info(null, "Starting pipeline"); } From 3ac67b6e72055c1ccedbbc2459f145b393107db2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 17:05:02 +0100 Subject: [PATCH 130/200] Update Node.js --- services/web/client/source/class/osparc/data/model/Node.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 2e00b408170..8d68a50a76a 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -370,15 +370,15 @@ qx.Class.define("osparc.data.model.Node", { populateStates: function(nodeData) { if (nodeData.state) { + if (nodeData.state.dependencies) { + this.getStatus().setDependencies(nodeData.state.dependencies); + } if (nodeData.state.currentStatus) { this.getStatus().setRunning(nodeData.state.currentStatus); } if (nodeData.state.modified) { this.getStatus().setModified(nodeData.state.modified); } - if (nodeData.state.dependencies) { - this.getStatus().setDependencies(nodeData.state.dependencies); - } } }, From 5c95d1e30099831e5011c07ded8b06fee5e5978e Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 17:08:17 +0100 Subject: [PATCH 131/200] refactoring --- .../client/source/class/osparc/data/model/Node.js | 8 ++++---- .../source/class/osparc/desktop/WorkbenchView.js | 13 +------------ 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 8d68a50a76a..9c108f84fca 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -369,14 +369,14 @@ qx.Class.define("osparc.data.model.Node", { }, populateStates: function(nodeData) { - if (nodeData.state) { - if (nodeData.state.dependencies) { + if ("state" in nodeData) { + if ("dependencies" in nodeData.state) { this.getStatus().setDependencies(nodeData.state.dependencies); } - if (nodeData.state.currentStatus) { + if ("currentStatus" in nodeData.state && this.isComputational()) { this.getStatus().setRunning(nodeData.state.currentStatus); } - if (nodeData.state.modified) { + if ("modified" in nodeData.state) { this.getStatus().setModified(nodeData.state.modified); } } diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index 49d0c1542bf..b5b430193f4 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -538,18 +538,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const progress = Number.parseInt(nodeData["progress"]); node.getStatus().setProgress(progress); } - if ("state" in nodeData) { - const state = nodeData["state"]; - if ("currentStatus" in state && node.isComputational()) { - node.getStatus().setRunning(state["currentStatus"]); - } - if ("modified" in state) { - node.getStatus().setModified(state["modified"]); - } - if ("dependencies" in state) { - node.getStatus().setDependencies(state["dependencies"]); - } - } + node.populateStates(nodeData); } else if (osparc.data.Permissions.getInstance().isTester()) { console.log("Ignored ws 'nodeUpdated' msg", d); } From 1aab024ed49272130c90174ffb8a0964c44d0b85 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 17:18:04 +0100 Subject: [PATCH 132/200] colors for interactive status --- .../source/class/osparc/ui/basic/NodeStatusUI.js | 12 ++++++++---- .../web/client/source/class/osparc/utils/StatusUI.js | 6 ++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index da6ca4a9159..b3fedb53679 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -114,18 +114,22 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { onUpdate: (source, target) => { const state = source.getInteractive(); switch (state) { - case null: - this.__removeClass(this.__icon.getContentElement(), "rotate"); - break; case "ready": case "failed": this.__removeClass(this.__icon.getContentElement(), "rotate"); target.setTextColor(osparc.utils.StatusUI.getColor(state)); break; - default: + case "idle": + case "starting": + case "pulling": + case "pending": + case "connecting": this.__addClass(this.__icon.getContentElement(), "rotate"); target.resetTextColor(); break; + default: + this.__removeClass(this.__icon.getContentElement(), "rotate"); + break; } } }); diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js index 41dd952ab57..0d8934ef7b2 100644 --- a/services/web/client/source/class/osparc/utils/StatusUI.js +++ b/services/web/client/source/class/osparc/utils/StatusUI.js @@ -104,6 +104,12 @@ qx.Class.define("osparc.utils.StatusUI", { return "ready-green"; case "failed": return "failed-red"; + case "idle": + case "starting": + case "pulling": + case "pending": + case "connecting": + return "busy-orange"; default: return "text"; From 11d528f01716e02508b93d8e2c00d3ab8b026690 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 17:20:09 +0100 Subject: [PATCH 133/200] Update NodeStatusUI.js --- .../web/client/source/class/osparc/ui/basic/NodeStatusUI.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index b3fedb53679..e5701442c74 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -120,15 +120,19 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { target.setTextColor(osparc.utils.StatusUI.getColor(state)); break; case "idle": + this.__removeClass(this.__icon.getContentElement(), "rotate"); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); + break; case "starting": case "pulling": case "pending": case "connecting": this.__addClass(this.__icon.getContentElement(), "rotate"); - target.resetTextColor(); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); break; default: this.__removeClass(this.__icon.getContentElement(), "rotate"); + target.resetTextColor(); break; } } From 195328ccd5fac76715c45bf60e4b874d4d03082c Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Thu, 18 Feb 2021 17:23:22 +0100 Subject: [PATCH 134/200] refactoring --- .../class/osparc/ui/basic/NodeStatusUI.js | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index e5701442c74..23f16607eeb 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -31,12 +31,8 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { } }, - members: { - __node: null, - __label: null, - __icon: null, - - __addClass: function(element, className) { + statics: { + addClass: function(element, className) { if (element) { const currentClass = element.getAttribute("class"); if (currentClass && currentClass.includes(className.trim())) { @@ -46,13 +42,19 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { } }, - __removeClass: function(element, className) { + removeClass: function(element, className) { const currentClass = element.getAttribute("class"); if (currentClass) { const regex = new RegExp(className.trim(), "g"); element.setAttribute("class", currentClass.replace(regex, "")); } }, + }, + + members: { + __node: null, + __label: null, + __icon: null, __setupComputational: function() { this.__node.getStatus().bind("running", this.__label, "value", { @@ -80,14 +82,14 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { case "SUCCESS": case "FAILED": case "ABORTED": - this.__removeClass(this.__icon.getContentElement(), "rotate"); + this.self().removeClass(this.__icon.getContentElement(), "rotate"); target.setTextColor(osparc.utils.StatusUI.getColor(state)); return; case "PENDING": case "PUBLISHED": case "STARTED": case "RETRY": - this.__addClass(this.__icon.getContentElement(), "rotate"); + this.self().addClass(this.__icon.getContentElement(), "rotate"); target.resetTextColor(); return; case "UNKNOWN": @@ -116,22 +118,22 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { switch (state) { case "ready": case "failed": - this.__removeClass(this.__icon.getContentElement(), "rotate"); + this.self().removeClass(this.__icon.getContentElement(), "rotate"); target.setTextColor(osparc.utils.StatusUI.getColor(state)); break; case "idle": - this.__removeClass(this.__icon.getContentElement(), "rotate"); + this.self().removeClass(this.__icon.getContentElement(), "rotate"); target.setTextColor(osparc.utils.StatusUI.getColor(state)); break; case "starting": case "pulling": case "pending": case "connecting": - this.__addClass(this.__icon.getContentElement(), "rotate"); + this.self().addClass(this.__icon.getContentElement(), "rotate"); target.setTextColor(osparc.utils.StatusUI.getColor(state)); break; default: - this.__removeClass(this.__icon.getContentElement(), "rotate"); + this.self().removeClass(this.__icon.getContentElement(), "rotate"); target.resetTextColor(); break; } From 4028581353fc9c25015be57b2d32ab7c5d322a9f Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 19 Feb 2021 10:41:56 +0100 Subject: [PATCH 135/200] Update NodeStatusUI.js --- .../web/client/source/class/osparc/ui/basic/NodeStatusUI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index 23f16607eeb..fd5e7458ab7 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -90,7 +90,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { case "STARTED": case "RETRY": this.self().addClass(this.__icon.getContentElement(), "rotate"); - target.resetTextColor(); + target.setTextColor(osparc.utils.StatusUI.getColor(state)); return; case "UNKNOWN": case "NOT_STARTED": From 20286ef3babf9b8a53cf46455cfd4944a13aafdc Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 19 Feb 2021 10:42:09 +0100 Subject: [PATCH 136/200] Update jupyters.js --- tests/e2e/tutorials/jupyters.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/e2e/tutorials/jupyters.js b/tests/e2e/tutorials/jupyters.js index b7e7088fcb3..1fe216aaa3e 100644 --- a/tests/e2e/tutorials/jupyters.js +++ b/tests/e2e/tutorials/jupyters.js @@ -55,7 +55,8 @@ async function runTutorial() { await tutorial.takeScreenshot("pressRunNB_" + (i+1)); } - await tutorial.retrieve(); + // TODO: Better check that the kernel is finished + await tutorial.waitFor(3000); console.log('Checking results for the notebook:'); await tutorial.openNodeFiles(1); @@ -69,8 +70,6 @@ async function runTutorial() { // open jupyter lab await tutorial.openNode(2); - await tutorial.retrieve(); - const iframeHandles2 = await tutorial.getIframe(); // expected three iframes = loading + jupyterNB + jupyterLab const jLabIframe = await iframeHandles2[2].contentFrame(); From 76f01a7f5fb2433fc64821ffe93ffaff0f9f5e65 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Fri, 19 Feb 2021 10:42:52 +0100 Subject: [PATCH 137/200] Update NodeStatusUI.js --- .../web/client/source/class/osparc/ui/basic/NodeStatusUI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js index fd5e7458ab7..7fe0abb6b38 100644 --- a/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js +++ b/services/web/client/source/class/osparc/ui/basic/NodeStatusUI.js @@ -48,7 +48,7 @@ qx.Class.define("osparc.ui.basic.NodeStatusUI", { const regex = new RegExp(className.trim(), "g"); element.setAttribute("class", currentClass.replace(regex, "")); } - }, + } }, members: { From 48d761f9586a3fb3d68748d5e55942ceffd6e4dd Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 11:19:00 +0100 Subject: [PATCH 138/200] Update StudyBrowser.js --- .../web/client/source/class/osparc/dashboard/StudyBrowser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/dashboard/StudyBrowser.js b/services/web/client/source/class/osparc/dashboard/StudyBrowser.js index dc9e22d5fed..927b3af8dc9 100644 --- a/services/web/client/source/class/osparc/dashboard/StudyBrowser.js +++ b/services/web/client/source/class/osparc/dashboard/StudyBrowser.js @@ -843,7 +843,7 @@ qx.Class.define("osparc.dashboard.StudyBrowser", { __createConfirmWindow: function(isMulti) { const msg = isMulti ? this.tr("Are you sure you want to delete the studies?") : this.tr("Are you sure you want to delete the study?"); const confirmationWin = new osparc.ui.window.Confirmation(msg); - const confirmButton = confirmationWin.getControlChild("confirm-button"); + const confirmButton = confirmationWin.getChildControl("confirm-button"); osparc.utils.Utils.setIdToWidget(confirmButton, "confirmDeleteStudyBtn"); return confirmationWin; } From edec650e81e3219faf0befb8623faba8e45cd6f2 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 11:49:50 +0100 Subject: [PATCH 139/200] Interpretation: Set Modified to true if it has Dependencies --- .../source/class/osparc/data/model/Node.js | 2 +- .../class/osparc/data/model/NodeStatus.js | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js index 9c108f84fca..e3982db07d0 100644 --- a/services/web/client/source/class/osparc/data/model/Node.js +++ b/services/web/client/source/class/osparc/data/model/Node.js @@ -377,7 +377,7 @@ qx.Class.define("osparc.data.model.Node", { this.getStatus().setRunning(nodeData.state.currentStatus); } if ("modified" in nodeData.state) { - this.getStatus().setModified(nodeData.state.modified); + this.getStatus().setModified(nodeData.state.modified || this.getStatus().hasDependencies()); } } }, diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js index 4b4597f1e0f..944e737f793 100644 --- a/services/web/client/source/class/osparc/data/model/NodeStatus.js +++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js @@ -52,14 +52,36 @@ qx.Class.define("osparc.data.model.NodeStatus", { check: "Array", nullable: true, init: null, - event: "changeDependencies" + event: "changeDependencies", + apply: "__applyDependencies" }, modified: { check: "Boolean", nullable: true, init: null, - event: "changeModified" + event: "changeModified", + apply: "__applyModified" + } + }, + + members: { + hasDependencies: function() { + const dependencies = this.getDependencies(); + if (dependencies && dependencies.length) { + return true; + } + return false; + }, + + __applyDependencies: function() { + this.setModified(this.hasDependencies()); + }, + + __applyModified: function(modified) { + if (modified === false) { + this.setModified(this.hasDependencies()); + } } } }); From 0936e51b42c69fd05c9e10314ed9c2dea89ca514 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 12:23:49 +0100 Subject: [PATCH 140/200] minor --- .../web/client/source/class/osparc/desktop/WorkbenchView.js | 2 +- tests/e2e/utils/utils.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index b5b430193f4..cba1e37ec4d 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -318,7 +318,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(study.getWorkbench()); const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView); - osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "loggerTitleLabel"); + osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "studyLoggerTitleLabel"); this.__sidePanel.addOrReplaceAt(loggerPanel, 2, { flex: 1 }); diff --git a/tests/e2e/utils/utils.js b/tests/e2e/utils/utils.js index c807294fa3b..53d7ced521d 100644 --- a/tests/e2e/utils/utils.js +++ b/tests/e2e/utils/utils.js @@ -369,7 +369,7 @@ function isElementVisible (page, selector) { async function clickLoggerTitle(page) { console.log("Click LoggerTitle"); - await this.waitAndClick(page, '[osparc-test-id="loggerTitleLabel"]') + await this.waitAndClick(page, '[osparc-test-id="studyLoggerTitleLabel"]') } From 5186ed1797048886f4e8a0528fa8f83f8d71fa39 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 12:24:27 +0100 Subject: [PATCH 141/200] minor --- .../source/class/osparc/component/widget/logger/LoggerView.js | 2 +- .../web/client/source/class/osparc/desktop/WorkbenchView.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js b/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js index 3d683c831c3..e41601a1bf1 100644 --- a/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js +++ b/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js @@ -35,7 +35,7 @@ * Here is a little example of how to use the widget. * *
- *   let loggerView = new osparc.component.widget.logger.LoggerView(workbench);
+ *   let loggerView = new osparc.component.widget.logger.LoggerView();
  *   this.getRoot().add(loggerView);
  *   loggerView.info(null, "Hello world");
  * 
diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js index cba1e37ec4d..93534031f58 100644 --- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js +++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js @@ -316,7 +316,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", { flex: 1 }); - const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(study.getWorkbench()); + const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView(); const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView); osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "studyLoggerTitleLabel"); this.__sidePanel.addOrReplaceAt(loggerPanel, 2, { From 78a1f597230734bba76b9d1a93231ba5eff8d343 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 12:24:47 +0100 Subject: [PATCH 142/200] renaming --- .../source/class/osparc/component/node/BaseNodeView.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js index 4540cfb4a6b..0a319c96f56 100644 --- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js +++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js @@ -67,17 +67,17 @@ qx.Class.define("osparc.component.node.BaseNodeView", { _mapperLayout: null, _iFrameLayout: null, __buttonContainer: null, - __filesButton: null, + __outFilesButton: null, populateLayout: function() { this.__cleanLayout(); this.getNode().bind("label", this.__title, "value"); + this._addButtons(); this.__addInputPortsUIs(); this.__addOutputPortsUIs(); this._addSettings(); this._addIFrame(); - this._addButtons(); }, __buildLayout: function() { @@ -238,7 +238,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { header.addSpacer(); const buttonsPart = this.__buttonContainer = new qx.ui.toolbar.Part(); - const filesBtn = this.__filesButton = new qx.ui.toolbar.Button(this.tr("Output Files"), "@FontAwesome5Solid/folder-open/14"); + const filesBtn = this.__outFilesButton = new qx.ui.toolbar.Button(this.tr("Output Files"), "@FontAwesome5Solid/folder-open/14"); osparc.utils.Utils.setIdToWidget(filesBtn, "nodeViewFilesBtn"); filesBtn.addListener("execute", () => this.__openNodeDataManager(), this); buttonsPart.add(filesBtn); @@ -334,7 +334,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { retrieveBtn.setEnabled(Boolean(this.getNode().getServiceUrl())); this.__buttonContainer.add(retrieveBtn); } - this.__buttonContainer.add(this.__filesButton); + this.__buttonContainer.add(this.__outFilesButton); this.__header.add(this.__buttonContainer); }, From a3cf3b10541b7753fc25d0dbf00d11c1fb51f53d Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 12:40:39 +0100 Subject: [PATCH 143/200] empty logger added to NodeView --- .../class/osparc/component/node/BaseNodeView.js | 6 +++++- .../class/osparc/component/node/NodeView.js | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js index 0a319c96f56..a5a744a9668 100644 --- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js +++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js @@ -66,6 +66,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { _settingsLayout: null, _mapperLayout: null, _iFrameLayout: null, + _loggerLayout: null, __buttonContainer: null, __outFilesButton: null, @@ -73,11 +74,13 @@ qx.Class.define("osparc.component.node.BaseNodeView", { this.__cleanLayout(); this.getNode().bind("label", this.__title, "value"); - this._addButtons(); this.__addInputPortsUIs(); this.__addOutputPortsUIs(); this._addSettings(); this._addIFrame(); + this._addLogger(); + + this._addButtons(); }, __buildLayout: function() { @@ -193,6 +196,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", { this._settingsLayout = this.self().createSettingsGroupBox(this.tr("Settings")); this._mapperLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)); this._iFrameLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox()); + this._loggerLayout = new qx.ui.container.Composite(new qx.ui.layout.VBox()); return mainView; }, diff --git a/services/web/client/source/class/osparc/component/node/NodeView.js b/services/web/client/source/class/osparc/component/node/NodeView.js index e1e1e20c53e..94b2a79815b 100644 --- a/services/web/client/source/class/osparc/component/node/NodeView.js +++ b/services/web/client/source/class/osparc/component/node/NodeView.js @@ -128,6 +128,21 @@ qx.Class.define("osparc.component.node.NodeView", { }); }, + _addLogger: function() { + this._loggerLayout.removeAll(); + + const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView().set({ + maxHeight: 150 + }); + const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView).set({ + collapsed: true + }); + osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "nodeLoggerTitleLabel"); + this._loggerLayout.add(loggerView); + + this._addToMainView(this._loggerLayout); + }, + _openEditAccessLevel: function() { const settingsEditorLayout = osparc.component.node.BaseNodeView.createSettingsGroupBox(this.tr("Settings")); const propsFormEditor = this.getNode().getPropsFormEditor(); From ac09bb4794aaa3744259d5f61e4928b843f39709 Mon Sep 17 00:00:00 2001 From: odeimaiz Date: Mon, 22 Feb 2021 13:41:30 +0100 Subject: [PATCH 144/200] Logger refactoring --- .../{RemoteTableModel.js => LoggerTable.js} | 35 +++++++++---------- .../component/widget/logger/LoggerView.js | 10 +----- 2 files changed, 17 insertions(+), 28 deletions(-) rename services/web/client/source/class/osparc/component/widget/logger/{RemoteTableModel.js => LoggerTable.js} (82%) diff --git a/services/web/client/source/class/osparc/component/widget/logger/RemoteTableModel.js b/services/web/client/source/class/osparc/component/widget/logger/LoggerTable.js similarity index 82% rename from services/web/client/source/class/osparc/component/widget/logger/RemoteTableModel.js rename to services/web/client/source/class/osparc/component/widget/logger/LoggerTable.js index 881c33b7642..52e84060195 100644 --- a/services/web/client/source/class/osparc/component/widget/logger/RemoteTableModel.js +++ b/services/web/client/source/class/osparc/component/widget/logger/LoggerTable.js @@ -25,7 +25,7 @@ * Here is a little example of how to use the widget. * *
- *   let tableModel = this.__logModel = new osparc.component.widget.logger.RemoteTableModel();
+ *   let tableModel = this.__logModel = new osparc.component.widget.logger.LoggerTable();
  *   tableModel.setColumns(["Origin", "Message"], ["whoRich", "whatRich"]);
  *   let custom = {
  *     tableColumnModel : function(obj) {
@@ -42,14 +42,19 @@
  * @asset(demobrowser/backend/remote_table.php)
  */
 
-qx.Class.define("osparc.component.widget.logger.RemoteTableModel", {
-
+qx.Class.define("osparc.component.widget.logger.LoggerTable", {
   extend : qx.ui.table.model.Remote,
 
   construct : function() {
     this.base(arguments);
 
-    this.setColumns(["Origin", "Message"], ["whoRich", "msgRich"]);
+    this.setColumns([
+      "Origin",
+      "Message"
+    ], [
+      "whoRich",
+      "msgRich"
+    ]);
 
     this.__rawData = [];
   },
@@ -60,15 +65,11 @@ qx.Class.define("osparc.component.widget.logger.RemoteTableModel", {
       check : "Number",
       init: -1
     },
+
     filterString: {
       nullable: true,
       check : "String",
       init: ""
-    },
-    caseSensitive: {
-      nullable: false,
-      check : "Boolean",
-      init: false
     }
   },
 
@@ -85,8 +86,8 @@ qx.Class.define("osparc.component.widget.logger.RemoteTableModel", {
     addRows: function(newRows) {
       for (let i=0; i
Date: Mon, 22 Feb 2021 14:04:40 +0100
Subject: [PATCH 145/200] Refactoring

---
 .../component/widget/logger/LoggerView.js     | 162 ++++++++++--------
 1 file changed, 91 insertions(+), 71 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js b/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js
index 8aeff5063fb..355beadb269 100644
--- a/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js
+++ b/services/web/client/source/class/osparc/component/widget/logger/LoggerView.js
@@ -62,8 +62,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
 
     this._setLayout(new qx.ui.layout.VBox());
 
-    const filterToolbar = this.__createFilterToolbar();
-    this._add(filterToolbar);
+    this.__createFilterToolbar();
 
     const table = this.__createTableLayout();
     this._add(table, {
@@ -71,10 +70,6 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
     });
 
     this.__messengerColors = new Set();
-
-    this.__createInitMsg();
-
-    this.__textFilterField.addListener("changeValue", this.__applyFilters, this);
   },
 
   properties: {
@@ -121,68 +116,99 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
   },
 
   members: {
-    __currentNodeButton: null,
     __textFilterField: null,
-    __logModel: null,
+    __loggerModel: null,
     __logView: null,
     __messengerColors: null,
 
+    _createChildControlImpl: function(id) {
+      let control;
+      switch (id) {
+        case "toolbar":
+          control = new qx.ui.toolbar.ToolBar();
+          this._add(control);
+          break;
+        case "pin-node": {
+          const toolbar = this.getChildControl("toolbar");
+          control = new qx.ui.form.ToggleButton().set({
+            icon: "@FontAwesome5Solid/thumbtack/14",
+            toolTipText: this.tr("Show logs only from current node"),
+            appearance: "toolbar-button"
+          });
+          toolbar.add(control);
+          break;
+        }
+        case "filter-text": {
+          const toolbar = this.getChildControl("toolbar");
+          control = new qx.ui.form.TextField().set({
+            appearance: "toolbar-textfield",
+            liveUpdate: true,
+            placeholder: this.tr("Filter")
+          });
+          osparc.utils.Utils.setIdToWidget(control, "logsFilterField");
+          toolbar.add(control, {
+            flex: 1
+          });
+          break;
+        }
+        case "log-level": {
+          const toolbar = this.getChildControl("toolbar");
+          control = new qx.ui.form.SelectBox().set({
+            appearance: "toolbar-selectbox",
+            maxWidth: 80
+          });
+          let logLevelSet = false;
+          for (let i=0; i {
-        // this.currectNodeClicked(currentNodeButton.getValue());
+      const pinNode = this.getChildControl("pin-node");
+      pinNode.addListener("changeValue", e => {
         this.currectNodeClicked(e.getData());
       }, this);
-      toolbar.add(currentNodeButton);
 
-      toolbar.add(new qx.ui.toolbar.Separator());
-      const textFilterField = this.__textFilterField = new qx.ui.form.TextField().set({
-        appearance: "toolbar-textfield",
-        liveUpdate: true,
-        placeholder: this.tr("Filter")
-      });
-      osparc.utils.Utils.setIdToWidget(textFilterField, "logsFilterField");
-      toolbar.add(textFilterField, {
-        flex: 1
-      });
+      const textFilterField = this.__textFilterField = this.getChildControl("filter-text");
+      textFilterField.addListener("changeValue", this.__applyFilters, this);
 
-      const logLevelSelectBox = new qx.ui.form.SelectBox().set({
-        appearance: "toolbar-selectbox",
-        maxWidth: 80
-      });
-      let logLevelSet = false;
-      for (let i=0; i {
         this.setLogLevel(e.getData().logLevel);
       }, this);
       toolbar.add(logLevelSelectBox);
 
-      const copyToClipboardButton = new qx.ui.form.Button().set({
-        icon: "@FontAwesome5Solid/copy/14",
-        toolTipText: this.tr("Copy logs to clipboard"),
-        appearance: "toolbar-button"
-      });
-      osparc.utils.Utils.setIdToWidget(copyToClipboardButton, "copyLogsToClipboardButton");
+      const copyToClipboardButton = this.getChildControl("copy-to-clipboard");
       copyToClipboardButton.addListener("execute", e => {
         this.__copyLogsToClipboard();
       }, this);
@@ -192,7 +218,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
     },
 
     __createTableLayout: function() {
-      const tableModel = this.__logModel = new osparc.component.widget.logger.LoggerTable();
+      const loggerModel = this.__loggerModel = new osparc.component.widget.logger.LoggerTable();
 
       const custom = {
         tableColumnModel : function(obj) {
@@ -201,7 +227,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
       };
 
       // table
-      const table = this.__logView = new qx.ui.table.Table(tableModel, custom).set({
+      const table = this.__logView = new qx.ui.table.Table(loggerModel, custom).set({
         selectable: true,
         statusBarVisible: false,
         showCellFocusIndicator: false
@@ -222,7 +248,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
     },
 
     __currentNodeIdChanged: function() {
-      this.__currentNodeButton.setValue(false);
+      this.getChildControl("pin-node").setValue(false);
     },
 
     currectNodeClicked: function(checked) {
@@ -246,7 +272,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
 
     __copyLogsToClipboard: function() {
       let logs = "";
-      this.__logModel.getRows().forEach(row => {
+      this.__loggerModel.getRows().forEach(row => {
         logs += `(${row.nodeId}) ${row.label}: ${row.msg} \n`;
       });
       osparc.utils.Utils.copyTextToClipboard(logs);
@@ -285,7 +311,7 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
         label = node.getLabel();
         node.addListener("changeLabel", e => {
           const newLabel = e.getData();
-          this.__logModel.nodeLabelChanged(nodeId, newLabel);
+          this.__loggerModel.nodeLabelChanged(nodeId, newLabel);
           this.__updateTable();
         }, this);
       } else {
@@ -306,14 +332,14 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
         };
         msgLogs.push(msgLog);
       }
-      this.__logModel.addRows(msgLogs);
+      this.__loggerModel.addRows(msgLogs);
 
       this.__updateTable();
     },
 
     __updateTable: function() {
-      this.__logModel.reloadData();
-      const nFilteredRows = this.__logModel.getFilteredRowCount();
+      this.__loggerModel.reloadData();
+      const nFilteredRows = this.__loggerModel.getFilteredRowCount();
       this.__logView.scrollCellVisible(0, nFilteredRows);
     },
 
@@ -329,19 +355,13 @@ qx.Class.define("osparc.component.widget.logger.LoggerView", {
     },
 
     __applyFilters: function() {
-      if (this.__logModel === null) {
+      if (this.__loggerModel === null) {
         return;
       }
 
-      this.__logModel.setFilterString(this.__textFilterField.getValue());
-      this.__logModel.setFilterLogLevel(this.getLogLevel());
-      this.__logModel.reloadData();
-    },
-
-    __createInitMsg: function() {
-      const nodeId = null;
-      const msg = "Logger initialized";
-      this.debug(nodeId, msg);
+      this.__loggerModel.setFilterString(this.__textFilterField.getValue());
+      this.__loggerModel.setFilterLogLevel(this.getLogLevel());
+      this.__loggerModel.reloadData();
     }
   }
 });

From a2cdc441ed70c46388dd33576ff74dd720957f0c Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 14:42:40 +0100
Subject: [PATCH 146/200] getLogger is __getStudyLogger

---
 .../class/osparc/desktop/StudyEditor.js       | 36 +++++++++----------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js
index 230f4af208f..64e85795c34 100644
--- a/services/web/client/source/class/osparc/desktop/StudyEditor.js
+++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js
@@ -170,7 +170,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
           this.__doStartPipeline(runAll);
         })
         .catch(() => {
-          this.getLogger().error(null, "Run failed");
+          this.__getStudyLogger().error(null, "Run failed");
           startStopButtonsWB.setRunning(false);
           startStopButtonsSS.setRunning(false);
         });
@@ -201,15 +201,15 @@ qx.Class.define("osparc.desktop.StudyEditor", {
       const startStopButtonsSS = this.__slideshowView.getStartStopButtons();
       req.addListener("success", this.__onPipelinesubmitted, this);
       req.addListener("error", e => {
-        this.getLogger().error(null, "Error submitting pipeline");
+        this.__getStudyLogger().error(null, "Error submitting pipeline");
         startStopButtonsWB.setRunning(false);
         startStopButtonsSS.setRunning(false);
       }, this);
       req.addListener("fail", e => {
         if (e.getTarget().getStatus() == "403") {
-          this.getLogger().error(null, "Pipeline is already running");
+          this.__getStudyLogger().error(null, "Pipeline is already running");
         } else if (e.getTarget().getStatus() == "422") {
-          this.getLogger().info(null, "The pipeline is up-to-date");
+          this.__getStudyLogger().info(null, "The pipeline is up-to-date");
           const msg = this.tr("The pipeline is up-to-date. Do you want to re-run it?");
           const win = new osparc.ui.window.Confirmation(msg);
           win.center();
@@ -220,7 +220,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
             }
           }, this);
         } else {
-          this.getLogger().error(null, "Failed submitting pipeline");
+          this.__getStudyLogger().error(null, "Failed submitting pipeline");
         }
         startStopButtonsWB.setRunning(false);
         startStopButtonsSS.setRunning(false);
@@ -232,9 +232,9 @@ qx.Class.define("osparc.desktop.StudyEditor", {
       });
       req.send();
       if (selectedNodeIDs.length) {
-        this.getLogger().info(null, "Starting partial pipeline");
+        this.__getStudyLogger().info(null, "Starting partial pipeline");
       } else {
-        this.getLogger().info(null, "Starting pipeline");
+        this.__getStudyLogger().info(null, "Starting pipeline");
       }
 
       return true;
@@ -243,12 +243,12 @@ qx.Class.define("osparc.desktop.StudyEditor", {
     __onPipelinesubmitted: function(e) {
       const resp = e.getTarget().getResponse();
       const pipelineId = resp.data["pipeline_id"];
-      this.getLogger().debug(null, "Pipeline ID " + pipelineId);
+      this.__getStudyLogger().debug(null, "Pipeline ID " + pipelineId);
       const notGood = [null, undefined, -1];
       if (notGood.includes(pipelineId)) {
-        this.getLogger().error(null, "Submission failed");
+        this.__getStudyLogger().error(null, "Submission failed");
       } else {
-        this.getLogger().info(null, "Pipeline started");
+        this.__getStudyLogger().info(null, "Pipeline started");
         /* If no projectStateUpdated comes in 60 seconds, client must
         check state of pipeline and update button accordingly. */
         const timer = setTimeout(() => {
@@ -287,17 +287,17 @@ qx.Class.define("osparc.desktop.StudyEditor", {
       const url = "/computation/pipeline/" + encodeURIComponent(studyId) + ":stop";
       const req = new osparc.io.request.ApiRequest(url, "POST");
       req.addListener("success", e => {
-        this.getLogger().debug(null, "Pipeline aborting");
+        this.__getStudyLogger().debug(null, "Pipeline aborting");
       }, this);
       req.addListener("error", e => {
-        this.getLogger().error(null, "Error stopping pipeline");
+        this.__getStudyLogger().error(null, "Error stopping pipeline");
       }, this);
       req.addListener("fail", e => {
-        this.getLogger().error(null, "Failed stopping pipeline");
+        this.__getStudyLogger().error(null, "Failed stopping pipeline");
       }, this);
       req.send();
 
-      this.getLogger().info(null, "Stopping pipeline");
+      this.__getStudyLogger().info(null, "Stopping pipeline");
       return true;
     },
     // ------------------ START/STOP PIPELINE ------------------
@@ -305,12 +305,12 @@ qx.Class.define("osparc.desktop.StudyEditor", {
     __updatePipelineAndRetrieve: function(node, portKey = null) {
       this.updateStudyDocument(false)
         .then(() => {
-          this.getLogger().debug(null, "Retrieveing inputs");
+          this.__getStudyLogger().debug(null, "Retrieveing inputs");
           if (node) {
             node.retrieveInputs(portKey);
           }
         });
-      this.getLogger().debug(null, "Updating pipeline");
+      this.__getStudyLogger().debug(null, "Updating pipeline");
     },
 
     // overridden
@@ -331,7 +331,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
       this.__slideshowView.nodeSelected(nodeId);
     },
 
-    getLogger: function() {
+    __getStudyLogger: function() {
       return this.__workbenchView.getLogger();
     },
 
@@ -399,7 +399,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
         .catch(error => {
           console.error(error);
           osparc.component.message.FlashMessenger.getInstance().logAs(this.tr("Error saving the study"), "ERROR");
-          this.getLogger().error(null, "Error updating pipeline");
+          this.__getStudyLogger().error(null, "Error updating pipeline");
           // Need to throw the error to be able to handle it later
           throw error;
         });

From 6c75b6e943e8b5d9cc8f0325470e162b0d354fe2 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 15:19:47 +0100
Subject: [PATCH 147/200] iframe or spacer

---
 .../class/osparc/component/node/NodeView.js   | 46 ++++++++++---------
 1 file changed, 25 insertions(+), 21 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/node/NodeView.js b/services/web/client/source/class/osparc/component/node/NodeView.js
index 94b2a79815b..fd97a1928e8 100644
--- a/services/web/client/source/class/osparc/component/node/NodeView.js
+++ b/services/web/client/source/class/osparc/component/node/NodeView.js
@@ -100,28 +100,32 @@ qx.Class.define("osparc.component.node.NodeView", {
 
       const loadingPage = this.getNode().getLoadingPage();
       const iFrame = this.getNode().getIFrame();
-      if (loadingPage === null && iFrame === null) {
-        return;
-      }
-      [
-        loadingPage,
-        iFrame
-      ].forEach(widget => {
-        if (widget) {
-          widget.addListener("maximize", e => {
-            this._maximizeIFrame(true);
-          }, this);
-          widget.addListener("restore", e => {
-            this._maximizeIFrame(false);
-          }, this);
-          this._maximizeIFrame(widget.hasState("maximized"));
-        }
-      });
-      this.__iFrameChanged();
-
-      iFrame.addListener("load", () => {
+      if (loadingPage && iFrame) {
+        [
+          loadingPage,
+          iFrame
+        ].forEach(widget => {
+          if (widget) {
+            widget.addListener("maximize", e => {
+              this._maximizeIFrame(true);
+            }, this);
+            widget.addListener("restore", e => {
+              this._maximizeIFrame(false);
+            }, this);
+            this._maximizeIFrame(widget.hasState("maximized"));
+          }
+        });
         this.__iFrameChanged();
-      });
+
+        iFrame.addListener("load", () => {
+          this.__iFrameChanged();
+        });
+      } else {
+        // This will keep what comes after at the bottom
+        this._iFrameLayout.add(new qx.ui.core.Spacer(), {
+          flex: 1
+        });
+      }
 
       this._addToMainView(this._iFrameLayout, {
         flex: 1

From aaae4367624d05c45a977a53b8b9fd62cd8f60ae Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 15:25:11 +0100
Subject: [PATCH 148/200] Logger in Node and NodeView

---
 .../osparc/component/node/BaseNodeView.js     |  5 +++++
 .../class/osparc/component/node/NodeView.js   |  3 ++-
 .../source/class/osparc/data/model/Node.js    | 20 ++++++++++++++++---
 .../class/osparc/desktop/WorkbenchView.js     | 18 ++++++++++++++++-
 4 files changed, 41 insertions(+), 5 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
index a5a744a9668..893b0f99b6b 100644
--- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js
+++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
@@ -350,6 +350,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
       const othersStatus2 = isSettingsGroupShowable && !maximize ? "visible" : "excluded";
       this._settingsLayout.setVisibility(othersStatus2);
       this._mapperLayout.setVisibility(othersStatus);
+      this._loggerLayout.setVisibility(othersStatus);
       this.__header.setVisibility(othersStatus);
     },
 
@@ -479,6 +480,10 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
       throw new Error("Abstract method called!");
     },
 
+    _addLogger: function() {
+      return;
+    },
+
     /**
       * @abstract
       */
diff --git a/services/web/client/source/class/osparc/component/node/NodeView.js b/services/web/client/source/class/osparc/component/node/NodeView.js
index fd97a1928e8..42b62e47c32 100644
--- a/services/web/client/source/class/osparc/component/node/NodeView.js
+++ b/services/web/client/source/class/osparc/component/node/NodeView.js
@@ -135,9 +135,10 @@ qx.Class.define("osparc.component.node.NodeView", {
     _addLogger: function() {
       this._loggerLayout.removeAll();
 
-      const loggerView = this.__loggerView = new osparc.component.widget.logger.LoggerView().set({
+      const loggerView = this.__loggerView = this.getNode().getLogger().set({
         maxHeight: 150
       });
+      loggerView.getChildControl("pin-node").exclude();
       const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView).set({
         collapsed: true
       });
diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js
index e3982db07d0..a2820be2bd2 100644
--- a/services/web/client/source/class/osparc/data/model/Node.js
+++ b/services/web/client/source/class/osparc/data/model/Node.js
@@ -135,6 +135,12 @@ qx.Class.define("osparc.data.model.Node", {
       event: "changeOutputs"
     },
 
+    status: {
+      check: "osparc.data.model.NodeStatus",
+      nullable: false
+    },
+
+    // GUI elements //
     propsForm: {
       check: "osparc.component.form.renderer.PropForm",
       init: null,
@@ -165,10 +171,12 @@ qx.Class.define("osparc.data.model.Node", {
       nullable: true
     },
 
-    status: {
-      check: "osparc.data.model.NodeStatus",
-      nullable: false
+    logger: {
+      check: "osparc.component.widget.logger.LoggerView",
+      init: null,
+      nullable: true
     }
+    // GUI elements //
   },
 
   events: {
@@ -348,6 +356,8 @@ qx.Class.define("osparc.data.model.Node", {
         this.__outputWidget.populatePortsData();
       }
 
+      this.__initLogger();
+
       if (this.isDynamic()) {
         this.__initLoadingIPage();
         this.__initIFrame();
@@ -754,6 +764,10 @@ qx.Class.define("osparc.data.model.Node", {
       return true;
     },
 
+    __initLogger: function() {
+      this.setLogger(new osparc.component.widget.logger.LoggerView());
+    },
+
     __getLoadingPageHeader: function() {
       const status = this.getStatus().getInteractive();
       const label = this.getLabel();
diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js
index 93534031f58..bc759d00dfa 100644
--- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js
+++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js
@@ -132,6 +132,16 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
       return this.__loggerView;
     },
 
+    __getNodeLogger: function(nodeId) {
+      const nodes = this.getStudy().getWorkbench().getNodes(true);
+      for (const node of Object.values(nodes)) {
+        if (nodeId === node.getNodeId()) {
+          return node.getLogger();
+        }
+      }
+      return null;
+    },
+
     __editSlides: function() {
       const uiData = this.getStudy().getUi();
       const nodesSlidesTree = new osparc.component.widget.NodesSlidesTree(uiData.getSlideshow());
@@ -501,7 +511,13 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
             // Filtering out logs from other studies
             return;
           }
-          this.getLogger().infos(data["Node"], data["Messages"]);
+          const nodeId = data["Node"];
+          const messages = data["Messages"];
+          this.getLogger().infos(nodeId, messages);
+          const nodeLogger = this.__getNodeLogger(nodeId);
+          if (nodeLogger) {
+            nodeLogger.infos(nodeId, messages);
+          }
         }, this);
       }
       socket.emit(slotName);

From edda720392fd3db2c11e92842eb0f4cce2e519c8 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 15:54:35 +0100
Subject: [PATCH 149/200] aesthetics

---
 .../client/source/class/osparc/component/node/NodeView.js  | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/node/NodeView.js b/services/web/client/source/class/osparc/component/node/NodeView.js
index 42b62e47c32..18d0e466cef 100644
--- a/services/web/client/source/class/osparc/component/node/NodeView.js
+++ b/services/web/client/source/class/osparc/component/node/NodeView.js
@@ -136,14 +136,15 @@ qx.Class.define("osparc.component.node.NodeView", {
       this._loggerLayout.removeAll();
 
       const loggerView = this.__loggerView = this.getNode().getLogger().set({
-        maxHeight: 150
+        maxHeight: 250
       });
       loggerView.getChildControl("pin-node").exclude();
       const loggerPanel = new osparc.desktop.PanelView(this.tr("Logger"), loggerView).set({
-        collapsed: true
+        collapsed: true,
+        backgroundColor: "background-main-lighter"
       });
       osparc.utils.Utils.setIdToWidget(loggerPanel.getTitleLabel(), "nodeLoggerTitleLabel");
-      this._loggerLayout.add(loggerView);
+      this._loggerLayout.add(loggerPanel);
 
       this._addToMainView(this._loggerLayout);
     },

From 8bfa667410dc6622cd9cb1a5e9fdbb70b37ba3f5 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 16:54:38 +0100
Subject: [PATCH 150/200] StartStopButtons has Study as prop

---
 .../source/class/osparc/data/model/Study.js   |  3 +-
 .../class/osparc/desktop/StartStopButtons.js  | 74 +++++++++++--------
 .../source/class/osparc/desktop/Toolbar.js    | 20 ++---
 3 files changed, 57 insertions(+), 40 deletions(-)

diff --git a/services/web/client/source/class/osparc/data/model/Study.js b/services/web/client/source/class/osparc/data/model/Study.js
index 292c75e2005..668d2dd1748 100644
--- a/services/web/client/source/class/osparc/data/model/Study.js
+++ b/services/web/client/source/class/osparc/data/model/Study.js
@@ -154,7 +154,8 @@ qx.Class.define("osparc.data.model.Study", {
 
     state: {
       check: "Object",
-      nullable: true
+      nullable: true,
+      event: "changeState"
     },
 
     quality: {
diff --git a/services/web/client/source/class/osparc/desktop/StartStopButtons.js b/services/web/client/source/class/osparc/desktop/StartStopButtons.js
index 78b5f172fc3..37c06cdf81d 100644
--- a/services/web/client/source/class/osparc/desktop/StartStopButtons.js
+++ b/services/web/client/source/class/osparc/desktop/StartStopButtons.js
@@ -41,6 +41,15 @@ qx.Class.define("osparc.desktop.StartStopButtons", {
     this.__initDefault();
   },
 
+  properties: {
+    study: {
+      check: "osparc.data.model.Study",
+      apply: "__applyStudy",
+      init: null,
+      nullable: false
+    }
+  },
+
   events: {
     "startPipeline": "qx.event.type.Event",
     "startPartialPipeline": "qx.event.type.Event",
@@ -84,35 +93,8 @@ qx.Class.define("osparc.desktop.StartStopButtons", {
       });
       this._add(startSplitButton);
 
-      osparc.store.Store.getInstance().addListener("changeCurrentStudy", e => {
-        const study = e.getData();
-        this.__updateRunButtonsStatus(study);
-      });
-    },
-
-    __updateRunButtonsStatus: function(study) {
-      if (study) {
-        const startButtons = [this.__startButton, this.__startSelectionButton.getChildControl("button"), this.__startAllButton];
-        const stopButton = this.__stopButton;
-        if (study.getState() && study.getState().state) {
-          const pipelineState = study.getState().state;
-          switch (pipelineState.value) {
-            case "PENDING":
-            case "PUBLISHED":
-            case "STARTED":
-              startButtons.forEach(startButton => startButton.setFetching(true));
-              stopButton.setEnabled(true);
-              break;
-            case "NOT_STARTED":
-            case "SUCCESS":
-            case "FAILED":
-            default:
-              startButtons.forEach(startButton => startButton.setFetching(false));
-              stopButton.setEnabled(false);
-              break;
-          }
-        }
-      }
+      const stateLabel = this.__dummyLabel = new qx.ui.basic.Label();
+      this._add(stateLabel);
     },
 
     __createStartButton: function() {
@@ -153,6 +135,40 @@ qx.Class.define("osparc.desktop.StartStopButtons", {
         this.fireEvent("stopPipeline");
       }, this);
       return stopButton;
+    },
+
+    __applyStudy: function(study) {
+      this.__checkButtonsVisible();
+      this.__updateRunButtonsStatus();
+    },
+
+    __checkButtonsVisible: function() {
+      this.setVisibility(this.getStudy().isReadOnly() ? "excluded" : "visible");
+    },
+
+    __updateRunButtonsStatus: function(study) {
+      if (study) {
+        const startButtons = [this.__startButton, this.__startSelectionButton.getChildControl("button"), this.__startAllButton];
+        const stopButton = this.__stopButton;
+        if (study.getState() && study.getState().state) {
+          const pipelineState = study.getState().state;
+          switch (pipelineState.value) {
+            case "PENDING":
+            case "PUBLISHED":
+            case "STARTED":
+              startButtons.forEach(startButton => startButton.setFetching(true));
+              stopButton.setEnabled(true);
+              break;
+            case "NOT_STARTED":
+            case "SUCCESS":
+            case "FAILED":
+            default:
+              startButtons.forEach(startButton => startButton.setFetching(false));
+              stopButton.setEnabled(false);
+              break;
+          }
+        }
+      }
     }
   }
 });
diff --git a/services/web/client/source/class/osparc/desktop/Toolbar.js b/services/web/client/source/class/osparc/desktop/Toolbar.js
index f0a6cd70047..ff89c137c62 100644
--- a/services/web/client/source/class/osparc/desktop/Toolbar.js
+++ b/services/web/client/source/class/osparc/desktop/Toolbar.js
@@ -73,15 +73,15 @@ qx.Class.define("osparc.desktop.Toolbar", {
         }
         case "start-stop-btns": {
           control = new osparc.desktop.StartStopButtons();
-          control.addListener("startPipeline", () => {
-            this.fireEvent("startPipeline");
-          }, this);
-          control.addListener("startPartialPipeline", () => {
-            this.fireEvent("startPartialPipeline");
-          }, this);
-          control.addListener("stopPipeline", () => {
-            this.fireEvent("stopPipeline");
-          }, this);
+          [
+            "startPipeline",
+            "startPartialPipeline",
+            "stopPipeline"
+          ].forEach(signalName => {
+            control.addListener(signalName, () => {
+              this.fireEvent(signalName);
+            }, this);
+          });
           this._add(control);
           break;
         }
@@ -94,7 +94,7 @@ qx.Class.define("osparc.desktop.Toolbar", {
         study.getUi().addListener("changeCurrentNodeId", () => {
           this._populateNodesNavigationLayout();
         });
-        this._startStopBtns.setVisibility(study.isReadOnly() ? "excluded" : "visible");
+        this._startStopBtns.setStudy(study);
 
         this._populateNodesNavigationLayout();
       }

From 056cf5303df8321f8eefaf97a901443977cdd7e4 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 17:13:28 +0100
Subject: [PATCH 151/200] Update StartStopButtons.js

---
 .../source/class/osparc/desktop/StartStopButtons.js  | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/StartStopButtons.js b/services/web/client/source/class/osparc/desktop/StartStopButtons.js
index 37c06cdf81d..dd37c204917 100644
--- a/services/web/client/source/class/osparc/desktop/StartStopButtons.js
+++ b/services/web/client/source/class/osparc/desktop/StartStopButtons.js
@@ -139,20 +139,24 @@ qx.Class.define("osparc.desktop.StartStopButtons", {
 
     __applyStudy: function(study) {
       this.__checkButtonsVisible();
-      this.__updateRunButtonsStatus();
+
+      study.addListener("changeState", () => {
+        this.__updateRunButtonsStatus();
+      }, this);
     },
 
     __checkButtonsVisible: function() {
       this.setVisibility(this.getStudy().isReadOnly() ? "excluded" : "visible");
     },
 
-    __updateRunButtonsStatus: function(study) {
+    __updateRunButtonsStatus: function() {
+      const study = this.getStudy();
       if (study) {
         const startButtons = [this.__startButton, this.__startSelectionButton.getChildControl("button"), this.__startAllButton];
         const stopButton = this.__stopButton;
         if (study.getState() && study.getState().state) {
-          const pipelineState = study.getState().state;
-          switch (pipelineState.value) {
+          const pipelineState = study.getState().state.value;
+          switch (pipelineState) {
             case "PENDING":
             case "PUBLISHED":
             case "STARTED":

From 63a9ee46b817026886a28f0d080515918362986d Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Mon, 22 Feb 2021 17:16:37 +0100
Subject: [PATCH 152/200] cleanup

---
 .../web/client/source/class/osparc/desktop/StartStopButtons.js | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/StartStopButtons.js b/services/web/client/source/class/osparc/desktop/StartStopButtons.js
index dd37c204917..ac1da0d49dc 100644
--- a/services/web/client/source/class/osparc/desktop/StartStopButtons.js
+++ b/services/web/client/source/class/osparc/desktop/StartStopButtons.js
@@ -92,9 +92,6 @@ qx.Class.define("osparc.desktop.StartStopButtons", {
         visibility: "excluded"
       });
       this._add(startSplitButton);
-
-      const stateLabel = this.__dummyLabel = new qx.ui.basic.Label();
-      this._add(stateLabel);
     },
 
     __createStartButton: function() {

From a83fe8e1952442a23b44172e40925915b86aefa9 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 10:03:27 +0100
Subject: [PATCH 153/200] node-level logger out. @mguidon, blame @KZzizzle

---
 .../client/source/class/osparc/component/node/BaseNodeView.js   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
index 893b0f99b6b..524668c3ae8 100644
--- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js
+++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
@@ -78,7 +78,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
       this.__addOutputPortsUIs();
       this._addSettings();
       this._addIFrame();
-      this._addLogger();
+      // this._addLogger();
 
       this._addButtons();
     },

From e9ccc244d80e75b6f378cbdb3f6872d1a4a95680 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 10:10:08 +0100
Subject: [PATCH 154/200] minor fix

---
 services/web/client/source/class/osparc/data/model/Workbench.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/services/web/client/source/class/osparc/data/model/Workbench.js b/services/web/client/source/class/osparc/data/model/Workbench.js
index a46bb75e63b..1de5580ee52 100644
--- a/services/web/client/source/class/osparc/data/model/Workbench.js
+++ b/services/web/client/source/class/osparc/data/model/Workbench.js
@@ -91,7 +91,7 @@ qx.Class.define("osparc.data.model.Workbench", {
 
     getNodes: function(recursive = false) {
       let nodes = Object.assign({}, this.__rootNodes);
-      if (recursive) {
+      if (recursive && this.__rootNodes) {
         let topLevelNodes = Object.values(this.__rootNodes);
         for (const topLevelNode of topLevelNodes) {
           let innerNodes = topLevelNode.getInnerNodes(true);

From 9c5827052228d49661774db11bbbceec878270e5 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 10:10:47 +0100
Subject: [PATCH 155/200] added annotations

---
 .../tests/unit/with_dbs/slow/test_projects.py | 23 +++++++++++--------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/services/web/server/tests/unit/with_dbs/slow/test_projects.py b/services/web/server/tests/unit/with_dbs/slow/test_projects.py
index 734f8fa19d9..6e8882b2031 100644
--- a/services/web/server/tests/unit/with_dbs/slow/test_projects.py
+++ b/services/web/server/tests/unit/with_dbs/slow/test_projects.py
@@ -6,11 +6,12 @@
 import time
 import unittest.mock as mock
 import uuid as uuidlib
-from asyncio import Future, sleep
+from asyncio import Future
 from copy import deepcopy
-from typing import Callable, Dict, List, Optional, Tuple, Union
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 from unittest.mock import call
 
+import aiohttp
 import pytest
 import socketio
 from _helpers import ExpectedResponse, HTTPLocked, standard_role_response
@@ -209,7 +210,9 @@ async def project_db_cleaner(client):
 
 
 @pytest.fixture
-async def catalog_subsystem_mock(monkeypatch):
+async def catalog_subsystem_mock(
+    monkeypatch,
+) -> Callable[[Optional[Union[List[Dict], Dict]]], None]:
     services_in_project = []
 
     def creator(projects: Optional[Union[List[Dict], Dict]] = None) -> None:
@@ -520,13 +523,13 @@ async def _delete_project(client, project: Dict, expected: web.Response) -> None
     ],
 )
 async def test_list_projects(
-    client,
-    logged_user,
-    user_project,
-    template_project,
-    expected,
-    catalog_subsystem_mock,
-    director_v2_service_mock,
+    client: aiohttp.test_utils.TestClient,
+    logged_user: Dict[str, Any],
+    user_project: Dict[str, Any],
+    template_project: Dict[str, Any],
+    expected: aiohttp.web.HTTPException,
+    catalog_subsystem_mock: Callable[[Optional[Union[List[Dict], Dict]]], None],
+    director_v2_service_mock: aioresponses,
 ):
     catalog_subsystem_mock([user_project, template_project])
     data = await _list_projects(client, expected)

From 1d6c2e2badf8c3f0263f5cdd417ef9a22ddf6b62 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 10:17:23 +0100
Subject: [PATCH 156/200] minor

---
 .../web/client/source/class/osparc/data/model/Node.js     | 8 ++------
 .../web/client/source/class/osparc/viewer/NodeViewer.js   | 4 ++--
 2 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js
index 0cc73f148d4..75b7965363f 100644
--- a/services/web/client/source/class/osparc/data/model/Node.js
+++ b/services/web/client/source/class/osparc/data/model/Node.js
@@ -341,15 +341,11 @@ qx.Class.define("osparc.data.model.Node", {
         if (nodeData.label) {
           this.setLabel(nodeData.label);
         }
-
         this.populateInputOutputData(nodeData);
-
         if ("progress" in nodeData) {
           this.getStatus().setProgress(nodeData.progress);
         }
-
         this.populateStates(nodeData);
-
         if (nodeData.thumbnail) {
           this.setThumbnail(nodeData.thumbnail);
         }
@@ -363,9 +359,7 @@ qx.Class.define("osparc.data.model.Node", {
       }
 
       this.__initLogger();
-
       if (this.isDynamic()) {
-        this.__initLoadingIPage();
         this.__initIFrame();
       }
     },
@@ -819,6 +813,8 @@ qx.Class.define("osparc.data.model.Node", {
     },
 
     __initIFrame: function() {
+      this.__initLoadingIPage();
+
       const iframe = new osparc.component.widget.PersistentIframe();
       osparc.utils.Utils.setIdToWidget(iframe, "PersistentIframe");
       iframe.addListener("restart", () => {
diff --git a/services/web/client/source/class/osparc/viewer/NodeViewer.js b/services/web/client/source/class/osparc/viewer/NodeViewer.js
index 134f0548abf..288cade9258 100644
--- a/services/web/client/source/class/osparc/viewer/NodeViewer.js
+++ b/services/web/client/source/class/osparc/viewer/NodeViewer.js
@@ -23,9 +23,7 @@ qx.Class.define("osparc.viewer.NodeViewer", {
 
     this._setLayout(new qx.ui.layout.VBox());
 
-    this.__initLoadingPage();
     this.__initIFrame();
-
     this.__iFrameChanged();
 
     this.set({
@@ -85,6 +83,8 @@ qx.Class.define("osparc.viewer.NodeViewer", {
     },
 
     __initIFrame: function() {
+      this.__initLoadingPage();
+
       const iframe = new osparc.component.widget.PersistentIframe().set({
         showActionButton: false,
         showRestartButton: false

From 2b02912d8821de1229bdea06348592097cdd468c Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 10:34:48 +0100
Subject: [PATCH 157/200] refactoring BreadcrumbNavigation

---
 .../class/osparc/desktop/SlideShowToolbar.js  | 12 +++
 .../source/class/osparc/desktop/Toolbar.js    | 12 ---
 .../class/osparc/desktop/WorkbenchToolbar.js  | 12 +++
 .../osparc/navigation/BreadcrumbNavigation.js | 87 +++----------------
 .../osparc/navigation/BreadcrumbsSlideShow.js | 72 +++++++++++++++
 .../osparc/navigation/BreadcrumbsWorkbench.js | 55 ++++++++++++
 6 files changed, 165 insertions(+), 85 deletions(-)
 create mode 100644 services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
 create mode 100644 services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js

diff --git a/services/web/client/source/class/osparc/desktop/SlideShowToolbar.js b/services/web/client/source/class/osparc/desktop/SlideShowToolbar.js
index a6563b194b3..56ec8b41d50 100644
--- a/services/web/client/source/class/osparc/desktop/SlideShowToolbar.js
+++ b/services/web/client/source/class/osparc/desktop/SlideShowToolbar.js
@@ -24,6 +24,18 @@ qx.Class.define("osparc.desktop.SlideShowToolbar", {
     _createChildControlImpl: function(id) {
       let control;
       switch (id) {
+        case "breadcrumb-navigation": {
+          const breadcrumbNavigation = this._navNodes = new osparc.navigation.BreadcrumbsSlideShow();
+          breadcrumbNavigation.addListener("nodeSelected", e => {
+            this.fireDataEvent("nodeSelected", e.getData());
+          }, this);
+          control = new qx.ui.container.Scroll();
+          control.add(breadcrumbNavigation);
+          this._add(control, {
+            flex: 1
+          });
+          break;
+        }
         case "prev-next-btns": {
           control = new osparc.navigation.PrevNextButtons();
           control.addListener("nodeSelected", e => {
diff --git a/services/web/client/source/class/osparc/desktop/Toolbar.js b/services/web/client/source/class/osparc/desktop/Toolbar.js
index ff89c137c62..a17db525221 100644
--- a/services/web/client/source/class/osparc/desktop/Toolbar.js
+++ b/services/web/client/source/class/osparc/desktop/Toolbar.js
@@ -59,18 +59,6 @@ qx.Class.define("osparc.desktop.Toolbar", {
     _createChildControlImpl: function(id) {
       let control;
       switch (id) {
-        case "breadcrumb-navigation": {
-          const breadcrumbNavigation = this._navNodes = new osparc.navigation.BreadcrumbNavigation();
-          breadcrumbNavigation.addListener("nodeSelected", e => {
-            this.fireDataEvent("nodeSelected", e.getData());
-          }, this);
-          control = new qx.ui.container.Scroll();
-          control.add(breadcrumbNavigation);
-          this._add(control, {
-            flex: 1
-          });
-          break;
-        }
         case "start-stop-btns": {
           control = new osparc.desktop.StartStopButtons();
           [
diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js b/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js
index 337b001ec79..1209c1913ba 100644
--- a/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js
+++ b/services/web/client/source/class/osparc/desktop/WorkbenchToolbar.js
@@ -32,6 +32,18 @@ qx.Class.define("osparc.desktop.WorkbenchToolbar", {
     _createChildControlImpl: function(id) {
       let control;
       switch (id) {
+        case "breadcrumb-navigation": {
+          const breadcrumbNavigation = this._navNodes = new osparc.navigation.BreadcrumbsWorkbench();
+          breadcrumbNavigation.addListener("nodeSelected", e => {
+            this.fireDataEvent("nodeSelected", e.getData());
+          }, this);
+          control = new qx.ui.container.Scroll();
+          control.add(breadcrumbNavigation);
+          this._add(control, {
+            flex: 1
+          });
+          break;
+        }
         case "sweeper-btn": {
           control = new qx.ui.form.Button(this.tr("Sweeper"), "@FontAwesome5Solid/paw/14").set({
             toolTipText: this.tr("Sweeper"),
diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js b/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js
index 299d6f808c6..e352c4b3952 100644
--- a/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbNavigation.js
@@ -37,33 +37,21 @@ qx.Class.define("osparc.navigation.BreadcrumbNavigation", {
   },
 
   members: {
-    populateButtons: function(nodesIds = [], shape = "slash") {
-      const btns = [];
-      if (shape === "slash") {
-        for (let i=0; i (pos+1).toString() + "- " + val
-        });
-        node.bind("label", btn, "toolTipText");
-
-        const nsUI = new osparc.ui.basic.NodeStatusUI(node);
-        const nsUIIcon = nsUI.getChildControl("icon");
-        // Hacky, aber schön
-        // eslint-disable-next-line no-underscore-dangle
-        btn._add(nsUIIcon);
-        const nsUILabel = nsUI.getChildControl("label");
-        nsUILabel.addListener("changeValue", e => {
-          const statusLabel = e.getData();
-          if (statusLabel) {
-            btn.setToolTipText(`${node.getLabel()} - ${statusLabel}`);
-          }
-        }, this);
-        if (nsUILabel.getValue()) {
-          btn.setToolTipText(`${node.getLabel()} - ${nsUILabel.getValue()}`);
-        }
-      }
-      return btn;
-    },
-
-    __buttonsToBreadcrumb: function(btns, shape = "slash") {
+    _buttonsToBreadcrumb: function(btns, shape = "slash") {
       this._removeAll();
       for (let i=0; i (pos+1).toString() + "- " + val
+        });
+        node.bind("label", btn, "toolTipText");
+
+        const nsUI = new osparc.ui.basic.NodeStatusUI(node);
+        const nsUIIcon = nsUI.getChildControl("icon");
+        // Hacky, aber schön
+        // eslint-disable-next-line no-underscore-dangle
+        btn._add(nsUIIcon);
+        const nsUILabel = nsUI.getChildControl("label");
+        nsUILabel.addListener("changeValue", e => {
+          const statusLabel = e.getData();
+          if (statusLabel) {
+            btn.setToolTipText(`${node.getLabel()} - ${statusLabel}`);
+          }
+        }, this);
+        if (nsUILabel.getValue()) {
+          btn.setToolTipText(`${node.getLabel()} - ${nsUILabel.getValue()}`);
+        }
+      }
+      return btn;
+    }
+  }
+});
diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js
new file mode 100644
index 00000000000..918d6623c1a
--- /dev/null
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsWorkbench.js
@@ -0,0 +1,55 @@
+/* ************************************************************************
+
+   osparc - the simcore frontend
+
+   https://osparc.io
+
+   Copyright:
+     2021 IT'IS Foundation, https://itis.swiss
+
+   License:
+     MIT: https://opensource.org/licenses/MIT
+
+   Authors:
+     * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+/**
+ *
+ */
+
+qx.Class.define("osparc.navigation.BreadcrumbsWorkbench", {
+  extend: osparc.navigation.BreadcrumbNavigation,
+
+  members: {
+    populateButtons: function(nodesIds = []) {
+      const btns = [];
+      for (let i=0; i
Date: Tue, 23 Feb 2021 11:07:54 +0100
Subject: [PATCH 158/200] suffix star if modified

---
 .../osparc/navigation/BreadcrumbsSlideShow.js | 35 ++++++++++++-------
 1 file changed, 23 insertions(+), 12 deletions(-)

diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
index 316b12068c4..b1be458adc2 100644
--- a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
@@ -46,24 +46,35 @@ qx.Class.define("osparc.navigation.BreadcrumbsSlideShow", {
       if (node && nodeId in slideShow) {
         const pos = slideShow[nodeId].position;
         node.bind("label", btn, "label", {
-          converter: val => (pos+1).toString() + "- " + val
+          converter: val => `${pos+1}- ${val}`
+        });
+        node.getStatus().bind("modified", btn, "label", {
+          converter: modified => {
+            const label = btn.getLabel();
+            const lastCharacter = label.slice(-1);
+            if (modified === true && lastCharacter !== "*") {
+              return label + "*"; // add star suffix
+            } else if (modified === false && lastCharacter === "*") {
+              return label.slice(0, -1); // remove star suffix
+            }
+            return label;
+          }
         });
         node.bind("label", btn, "toolTipText");
 
-        const nsUI = new osparc.ui.basic.NodeStatusUI(node);
-        const nsUIIcon = nsUI.getChildControl("icon");
-        // Hacky, aber schön
+        const statusUI = new osparc.ui.basic.NodeStatusUI(node);
+        const statusIcon = statusUI.getChildControl("icon");
         // eslint-disable-next-line no-underscore-dangle
-        btn._add(nsUIIcon);
-        const nsUILabel = nsUI.getChildControl("label");
-        nsUILabel.addListener("changeValue", e => {
-          const statusLabel = e.getData();
-          if (statusLabel) {
-            btn.setToolTipText(`${node.getLabel()} - ${statusLabel}`);
+        btn._add(statusIcon);
+        const statusLabel = statusUI.getChildControl("label");
+        statusLabel.addListener("changeValue", e => {
+          const newStatusLabel = e.getData();
+          if (newStatusLabel) {
+            btn.setToolTipText(`${node.getLabel()} - ${newStatusLabel}`);
           }
         }, this);
-        if (nsUILabel.getValue()) {
-          btn.setToolTipText(`${node.getLabel()} - ${nsUILabel.getValue()}`);
+        if (statusLabel.getValue()) {
+          btn.setToolTipText(`${node.getLabel()} - ${statusLabel.getValue()}`);
         }
       }
       return btn;

From 0c43b204e576085eae6c1f8b80c276fa416391d5 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 11:13:42 +0100
Subject: [PATCH 159/200] dependencies -> enabled

---
 .../class/osparc/navigation/BreadcrumbsSlideShow.js      | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
index b1be458adc2..b614cc6b2bd 100644
--- a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
@@ -48,6 +48,15 @@ qx.Class.define("osparc.navigation.BreadcrumbsSlideShow", {
         node.bind("label", btn, "label", {
           converter: val => `${pos+1}- ${val}`
         });
+        node.getStatus().bind("dependencies", btn, "enabled", {
+          converter: dependencies => {
+            if (dependencies !== null) {
+              const waiting = Boolean(dependencies.length);
+              return !waiting;
+            }
+            return true;
+          }
+        });
         node.getStatus().bind("modified", btn, "label", {
           converter: modified => {
             const label = btn.getLabel();

From 43234f45ca4447a049ccad6c2ea7c60ace004808 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 12:27:02 +0100
Subject: [PATCH 160/200] return the states of all computational nodes

---
 .../src/models_library/projects_pipeline.py   |  5 +-
 .../api/routes/computations.py                |  8 ++-
 .../simcore_service_director_v2/utils/dags.py |  6 +-
 .../tests/integration/test_computation_api.py | 59 +++++++++++++++++--
 4 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/packages/models-library/src/models_library/projects_pipeline.py b/packages/models-library/src/models_library/projects_pipeline.py
index a15a1d8a5dd..907e21da7dd 100644
--- a/packages/models-library/src/models_library/projects_pipeline.py
+++ b/packages/models-library/src/models_library/projects_pipeline.py
@@ -9,10 +9,11 @@
 
 class PipelineDetails(BaseModel):
     adjacency_list: Dict[NodeID, List[NodeID]] = Field(
-        ..., description="The adjacency list in terms of {NodeID: [successor NodeID]}"
+        ...,
+        description="The adjacency list of the current pipeline in terms of {NodeID: [successor NodeID]}",
     )
     node_states: Dict[NodeID, NodeState] = Field(
-        ..., description="The states of each of the pipeline node"
+        ..., description="The states of each of the computational nodes in the pipeline"
     )
 
 
diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
index 07d8eea55d5..56da626a3eb 100644
--- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
+++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
@@ -207,13 +207,15 @@ async def get_computation(
         )
 
         # get the project task states
-        tasks: List[CompTaskAtDB] = await computation_tasks.get_all_tasks(project_id)
+        all_comp_tasks: List[CompTaskAtDB] = await computation_tasks.get_all_tasks(
+            project_id
+        )
         # create the complete DAG graph
-        complete_dag = create_complete_dag_from_tasks(tasks)
+        complete_dag = create_complete_dag_from_tasks(all_comp_tasks)
 
         # filter the tasks by the effective pipeline
         filtered_tasks = [
-            t for t in tasks if str(t.node_id) in list(pipeline_dag.nodes())
+            t for t in all_comp_tasks if str(t.node_id) in list(pipeline_dag.nodes())
         ]
         pipeline_state = get_pipeline_state_from_task_states(
             filtered_tasks, celery_client.settings.publication_timeout
diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
index 95b037f5c05..7ede77d9e92 100644
--- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py
+++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
@@ -176,11 +176,11 @@ async def compute_pipeline_details(
         adjacency_list=nx.to_dict_of_lists(pipeline_dag),
         node_states={
             node_id: NodeState(
-                modified=node_data.get(kNODE_MODIFIED_STATE),
-                dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE),
+                modified=node_data.get(kNODE_MODIFIED_STATE, False),
+                dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE, set()),
             )
             for node_id, node_data in complete_dag.nodes.data()
-            if node_id in pipeline_dag.nodes
+            if _is_node_computational(node_data["key"])
         },
     )
 
diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py
index d30208cb398..125b4a8c7f9 100644
--- a/services/director-v2/tests/integration/test_computation_api.py
+++ b/services/director-v2/tests/integration/test_computation_api.py
@@ -332,12 +332,12 @@ def test_start_empty_computation(
 
 PartialComputationParams = namedtuple(
     "PartialComputationParams",
-    "subgraph_elements, exp_pipeline_adj_list, exp_node_states",
+    "subgraph_elements, exp_pipeline_adj_list, exp_node_states, exp_node_states_after_run",
 )
 
 
 @pytest.mark.parametrize(
-    "subgraph_elements,exp_pipeline_adj_list, exp_node_states",
+    "subgraph_elements,exp_pipeline_adj_list, exp_node_states, exp_node_states_after_run",
     [
         pytest.param(
             *PartialComputationParams(
@@ -347,7 +347,37 @@ def test_start_empty_computation(
                     1: {
                         "modified": True,
                         "dependencies": [],
-                    }
+                    },
+                    2: {
+                        "modified": True,
+                        "dependencies": [1],
+                    },
+                    3: {
+                        "modified": True,
+                        "dependencies": [],
+                    },
+                    4: {
+                        "modified": True,
+                        "dependencies": [2, 3],
+                    },
+                },
+                exp_node_states_after_run={
+                    1: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    2: {
+                        "modified": True,
+                        "dependencies": [],
+                    },
+                    3: {
+                        "modified": True,
+                        "dependencies": [],
+                    },
+                    4: {
+                        "modified": True,
+                        "dependencies": [2, 3],
+                    },
                 },
             ),
             id="element 0,1",
@@ -374,6 +404,24 @@ def test_start_empty_computation(
                         "dependencies": [2, 3],
                     },
                 },
+                exp_node_states_after_run={
+                    1: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    2: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    3: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    4: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                },
             ),
             id="element 1,2,4",
         ),
@@ -388,6 +436,7 @@ def test_run_partial_computation(
     subgraph_elements: List[int],
     exp_pipeline_adj_list: Dict[int, List[str]],
     exp_node_states: Dict[int, Dict[str, Any]],
+    exp_node_states_after_run: Dict[int, Dict[str, Any]],
 ):
     sleepers_project: ProjectAtDB = project(workbench=fake_workbench_without_outputs)
 
@@ -448,9 +497,7 @@ def _convert_to_pipeline_details(
         client, task_out.url, user_id, sleepers_project.uuid
     )
     expected_pipeline_details = _convert_to_pipeline_details(
-        sleepers_project,
-        exp_pipeline_adj_list,
-        {n: {"modified": False, "dependencies": []} for n in exp_node_states},
+        sleepers_project, exp_pipeline_adj_list, exp_node_states_after_run
     )
     _assert_computation_task_out_obj(
         client,

From 212c56d9d49d96e36477f13d942bee8707b4da00 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 12:39:58 +0100
Subject: [PATCH 161/200] grayout text, but button still enabled

---
 .../class/osparc/navigation/BreadcrumbsSlideShow.js    | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
index b614cc6b2bd..e51146f84af 100644
--- a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
@@ -48,13 +48,13 @@ qx.Class.define("osparc.navigation.BreadcrumbsSlideShow", {
         node.bind("label", btn, "label", {
           converter: val => `${pos+1}- ${val}`
         });
-        node.getStatus().bind("dependencies", btn, "enabled", {
+        node.getStatus().bind("dependencies", btn.getChildControl("label"), "textColor", {
           converter: dependencies => {
-            if (dependencies !== null) {
-              const waiting = Boolean(dependencies.length);
-              return !waiting;
+            let textColor = "material-button-text";
+            if (dependencies && dependencies.length) {
+              textColor = "material-button-text-disabled";
             }
-            return true;
+            return textColor;
           }
         });
         node.getStatus().bind("modified", btn, "label", {

From 28ed27c798708f92080572b3597b034a70e6f8a4 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 13:25:38 +0100
Subject: [PATCH 162/200] improved tooltip

---
 .../osparc/navigation/BreadcrumbsSlideShow.js    | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
index e51146f84af..90e96df9275 100644
--- a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
@@ -69,22 +69,16 @@ qx.Class.define("osparc.navigation.BreadcrumbsSlideShow", {
             return label;
           }
         });
-        node.bind("label", btn, "toolTipText");
 
         const statusUI = new osparc.ui.basic.NodeStatusUI(node);
+        const statusLabel = statusUI.getChildControl("label");
         const statusIcon = statusUI.getChildControl("icon");
         // eslint-disable-next-line no-underscore-dangle
         btn._add(statusIcon);
-        const statusLabel = statusUI.getChildControl("label");
-        statusLabel.addListener("changeValue", e => {
-          const newStatusLabel = e.getData();
-          if (newStatusLabel) {
-            btn.setToolTipText(`${node.getLabel()} - ${newStatusLabel}`);
-          }
-        }, this);
-        if (statusLabel.getValue()) {
-          btn.setToolTipText(`${node.getLabel()} - ${statusLabel.getValue()}`);
-        }
+
+        statusLabel.bind("value", btn, "toolTipText", {
+          converter: status => `${node.getLabel()} - ${status}`
+        });
       }
       return btn;
     }

From d745b9e94bf06516fb0b9a580969a968d394698b Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:20:54 +0100
Subject: [PATCH 163/200] add function to get nodes depending on another

---
 .../projects/projects_utils.py                | 23 ++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_utils.py b/services/web/server/src/simcore_service_webserver/projects/projects_utils.py
index 12833721d6d..d84a32a8f13 100644
--- a/services/web/server/src/simcore_service_webserver/projects/projects_utils.py
+++ b/services/web/server/src/simcore_service_webserver/projects/projects_utils.py
@@ -2,7 +2,7 @@
 import re
 import uuid as uuidlib
 from copy import deepcopy
-from typing import AnyStr, Dict, List, Match, Optional, Set, Tuple
+from typing import Any, AnyStr, Dict, List, Match, Optional, Set, Tuple
 
 from servicelib.decorators import safe_return
 
@@ -118,7 +118,9 @@ def _get_param_input_match(name, value, access) -> Optional[Match[AnyStr]]:
     return project
 
 
-def is_graph_equal(lhs_workbench: Dict, rhs_workbench: Dict) -> bool:
+def is_graph_equal(
+    lhs_workbench: Dict[str, Any], rhs_workbench: Dict[str, Any]
+) -> bool:
     """Checks whether both workbench contain the same graph
 
     Two graphs are the same when the same topology (i.e. nodes and edges)
@@ -152,7 +154,7 @@ def is_graph_equal(lhs_workbench: Dict, rhs_workbench: Dict) -> bool:
 
 
 async def project_uses_available_services(
-    project: Dict, available_services: List[Dict]
+    project: Dict[str, Any], available_services: List[Dict[str, Any]]
 ) -> bool:
     if not project["workbench"]:
         # empty project
@@ -168,3 +170,18 @@ async def project_uses_available_services(
     }
 
     return needed_services.issubset(available_services)
+
+
+async def project_get_depending_nodes(
+    project: Dict[str, Any], node_uuid: str
+) -> Set[str]:
+    depending_node_uuids = set()
+    for dep_node_uuid, dep_node_data in project.get("workbench", {}).items():
+        for dep_node_inputs_key_data in dep_node_data.get("inputs", {}).values():
+            if (
+                isinstance(dep_node_inputs_key_data, dict)
+                and dep_node_inputs_key_data.get("nodeUuid") == node_uuid
+            ):
+                depending_node_uuids.add(dep_node_uuid)
+
+    return depending_node_uuids

From 6f3d953d9cbdb18f8bcecedcb5667a43fe756fdc Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:26:26 +0100
Subject: [PATCH 164/200] create test for checking new function

---
 .../unit/isolated/test_projects_utils.py      | 44 ++++++++++++++++---
 1 file changed, 39 insertions(+), 5 deletions(-)

diff --git a/services/web/server/tests/unit/isolated/test_projects_utils.py b/services/web/server/tests/unit/isolated/test_projects_utils.py
index 6037949fc67..5d9dc73f1ff 100644
--- a/services/web/server/tests/unit/isolated/test_projects_utils.py
+++ b/services/web/server/tests/unit/isolated/test_projects_utils.py
@@ -4,16 +4,20 @@
 
 import json
 from copy import deepcopy
+from pathlib import Path
+from typing import Any, Dict, Set
 
 import jsonschema
 import pytest
 from jsonschema import ValidationError
-
-from simcore_service_webserver.projects.projects_utils import clone_project_document
+from simcore_service_webserver.projects.projects_utils import (
+    clone_project_document,
+    project_get_dependent_nodes,
+)
 from simcore_service_webserver.resources import resources
 
 
-def load_template_projects():
+def load_template_projects() -> Dict[str, Any]:
     projects = []
     projects_names = [
         name for name in resources.listdir("data") if "template-projects" in name
@@ -25,7 +29,7 @@ def load_template_projects():
 
 
 @pytest.fixture
-def project_schema(project_schema_file):
+def project_schema(project_schema_file: Path) -> Dict[str, Any]:
     with open(project_schema_file) as fh:
         schema = json.load(fh)
     return schema
@@ -34,7 +38,9 @@ def project_schema(project_schema_file):
 @pytest.mark.parametrize(
     "name,project", [(p["name"], p) for p in load_template_projects()]
 )
-def test_clone_project_document(name, project, project_schema):
+def test_clone_project_document(
+    name: str, project: Dict[str, Any], project_schema: Dict[str, Any]
+):
 
     source = deepcopy(project)
     clone, _ = clone_project_document(source)
@@ -53,3 +59,31 @@ def test_clone_project_document(name, project, project_schema):
         jsonschema.validate(instance=clone, schema=project_schema)
     except ValidationError as err:
         pytest.fail(f"Invalid clone of '{name}': {err.message}")
+
+
+@pytest.fixture(scope="session")
+def fake_project_data(fake_data_dir: Path) -> Dict[str, Any]:
+    with (fake_data_dir / "fake-project.json").open() as fp:
+        return json.load(fp)
+
+
+@pytest.mark.parametrize(
+    "node_uuid, expected_dependencies",
+    [
+        (
+            "b4b20476-e7c0-47c2-8cc4-f66ac21a13bf",
+            {
+                "5739e377-17f7-4f09-a6ad-62659fb7fdec",
+            },
+        ),
+        ("5739e377-17f7-4f09-a6ad-62659fb7fdec", set()),
+        ("351fd505-1ee3-466d-ad6c-ea2915ffd364", set()),
+    ],
+)
+async def test_project_get_depending_nodes(
+    fake_project_data: Dict[str, Any], node_uuid: str, expected_dependencies: Set[str]
+):
+    set_of_depending_nodes = await project_get_dependent_nodes(
+        fake_project_data, node_uuid
+    )
+    assert set_of_depending_nodes == expected_dependencies

From 154b5938b1f2feba5d8df0a44681c4e485542813 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:26:46 +0100
Subject: [PATCH 165/200] notification of depending nodes

---
 .../computation_comp_tasks_listening_task.py       | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
index c711881d849..ee554ed363b 100644
--- a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
+++ b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
@@ -17,9 +17,14 @@
 from pydantic.types import PositiveInt
 from servicelib.application_keys import APP_DB_ENGINE_KEY
 from servicelib.logging_utils import log_decorator
+from servicelib.utils import logged_gather
 from simcore_postgres_database.webserver_models import DB_CHANNEL_NAME, projects
 from sqlalchemy.sql import select
 
+from services.web.server.src.simcore_service_webserver.projects.projects_utils import (
+    project_get_depending_nodes,
+)
+
 from .computation_api import convert_state_from_db
 from .projects import projects_api, projects_exceptions
 
@@ -73,6 +78,15 @@ async def _update_project_outputs(
     )
 
     await projects_api.notify_project_node_update(app, project, node_uuid)
+    # get depending node and notify for these ones as well
+    depending_node_uuids = await project_get_depending_nodes(project, node_uuid)
+    await logged_gather(
+        *[
+            projects_api.notify_project_node_update(app, project, n)
+            for n in depending_node_uuids
+        ]
+    )
+    # notifiy
     await projects_api.post_trigger_connected_service_retrieve(
         app=app, project=project, updated_node_uuid=node_uuid, changed_keys=changed_keys
     )

From 69ee30336ce9cbf31607254df82a70899ea809da Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 14:46:54 +0100
Subject: [PATCH 166/200] check if node is ready

---
 .../class/osparc/desktop/SlideShowView.js     | 31 +++++++++++++++++--
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/SlideShowView.js b/services/web/client/source/class/osparc/desktop/SlideShowView.js
index 8b53c0eeb17..6cba6d96fda 100644
--- a/services/web/client/source/class/osparc/desktop/SlideShowView.js
+++ b/services/web/client/source/class/osparc/desktop/SlideShowView.js
@@ -52,12 +52,33 @@ qx.Class.define("osparc.desktop.SlideShowView", {
       return [this.__currentNodeId];
     },
 
-    nodeSelected: function(nodeId) {
-      this.__currentNodeId = nodeId;
-      this.getStudy().getUi().setCurrentNodeId(nodeId);
+    __isNodeReady: function(node, oldCurrentNodeId) {
+      const dependencies = node.getStatus().getDependencies();
+      if (dependencies && dependencies.length) {
+        console.log(dependencies);
+        const msg = this.tr("Do you want to run the required steps?");
+        const win = new osparc.ui.window.Confirmation(msg);
+        win.center();
+        win.open();
+        win.addListener("close", () => {
+          if (win.getConfirmed()) {
+            console.log("Run this", dependencies);
+          } else {
+            this.nodeSelected(oldCurrentNodeId);
+          }
+        }, this);
+        return false;
+      }
+      return true;
+    },
 
+    nodeSelected: function(nodeId) {
       const node = this.getStudy().getWorkbench().getNode(nodeId);
       if (node) {
+        const oldCurrentNodeId = this.__currentNodeId;
+        this.__currentNodeId = nodeId;
+        this.getStudy().getUi().setCurrentNodeId(nodeId);
+
         let view;
         if (node.isContainer()) {
           view = new osparc.component.node.GroupNodeView();
@@ -79,6 +100,10 @@ qx.Class.define("osparc.desktop.SlideShowView", {
           });
           this.__lastView = view;
         }
+        // check if upstream has to be run
+        if (!this.__isNodeReady(node, oldCurrentNodeId)) {
+          return;
+        }
       }
       this.getStudy().getUi().setCurrentNodeId(nodeId);
 

From a1124f0ef61321c6897febac8ee7873440ea4cd9 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:49:24 +0100
Subject: [PATCH 167/200] also return the current state of the node as was
 designed

---
 .../api/routes/computations.py                         | 10 +++++++---
 .../src/simcore_service_director_v2/utils/dags.py      |  7 ++++++-
 .../simcore_service_webserver/projects/projects_api.py |  4 +---
 3 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
index 56da626a3eb..ea0fdae7f22 100644
--- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
+++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
@@ -161,7 +161,7 @@ async def create_computation(
             if job.start_pipeline
             else RunningState.NOT_STARTED,
             pipeline_details=await compute_pipeline_details(
-                complete_dag, computational_dag
+                complete_dag, computational_dag, comp_tasks
             ),
             url=f"{request.url}/{job.project_id}",
             stop_url=f"{request.url}/{job.project_id}:stop"
@@ -231,7 +231,9 @@ async def get_computation(
         task_out = ComputationTaskOut(
             id=project_id,
             state=pipeline_state,
-            pipeline_details=await compute_pipeline_details(complete_dag, pipeline_dag),
+            pipeline_details=await compute_pipeline_details(
+                complete_dag, pipeline_dag, all_comp_tasks
+            ),
             url=f"{request.url.remove_query_params('user_id')}",
             stop_url=f"{request.url.remove_query_params('user_id')}:stop"
             if is_pipeline_running(pipeline_state)
@@ -308,7 +310,9 @@ async def stop_computation_project(
         return ComputationTaskOut(
             id=project_id,
             state=pipeline_state,
-            pipeline_details=await compute_pipeline_details(complete_dag, pipeline_dag),
+            pipeline_details=await compute_pipeline_details(
+                complete_dag, pipeline_dag, tasks
+            ),
             url=f"{str(request.url).rstrip(':stop')}",
         )
 
diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
index 7ede77d9e92..0f114ab74c8 100644
--- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py
+++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
@@ -6,6 +6,7 @@
 from models_library.projects_nodes import NodeID, NodeState
 from models_library.projects_nodes_io import PortLink
 from models_library.projects_pipeline import PipelineDetails
+from models_library.projects_state import RunningState
 from models_library.utils.nodes import compute_node_hash
 from simcore_service_director_v2.models.domains.comp_tasks import CompTaskAtDB
 
@@ -167,7 +168,7 @@ async def create_minimal_computational_graph_based_on_selection(
 
 @log_decorator(logger=logger)
 async def compute_pipeline_details(
-    complete_dag: nx.DiGraph, pipeline_dag: nx.DiGraph
+    complete_dag: nx.DiGraph, pipeline_dag: nx.DiGraph, comp_tasks: List[CompTaskAtDB]
 ) -> PipelineDetails:
 
     # first pass, traversing in topological order to correctly get the dependencies, set the nodes states
@@ -178,6 +179,10 @@ async def compute_pipeline_details(
             node_id: NodeState(
                 modified=node_data.get(kNODE_MODIFIED_STATE, False),
                 dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE, set()),
+                currentStatus=next(
+                    (task.state for task in comp_tasks if str(task.node_id) == node_id),
+                    RunningState.UNKNOWN,
+                ),
             )
             for node_id, node_data in complete_dag.nodes.data()
             if _is_node_computational(node_data["key"])
diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py
index 593a51ee4a0..865b77de992 100644
--- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py
+++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py
@@ -529,9 +529,7 @@ async def add_project_states_for_user(
                 if prj_node is None:
                     continue
                 node_state_dict = json.loads(
-                    node_state.json(
-                        by_alias=True, exclude_unset=True, exclude={"current_status"}
-                    )
+                    node_state.json(by_alias=True, exclude_unset=True)
                 )
                 prj_node.setdefault("state", {}).update(node_state_dict)
 

From 8ed7510df8a3e936a67ca44f504e8510c2101540 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:59:18 +0100
Subject: [PATCH 168/200] fix import

---
 .../computation_comp_tasks_listening_task.py                 | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
index ee554ed363b..aa14c3f7f43 100644
--- a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
+++ b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
@@ -21,12 +21,9 @@
 from simcore_postgres_database.webserver_models import DB_CHANNEL_NAME, projects
 from sqlalchemy.sql import select
 
-from services.web.server.src.simcore_service_webserver.projects.projects_utils import (
-    project_get_depending_nodes,
-)
-
 from .computation_api import convert_state_from_db
 from .projects import projects_api, projects_exceptions
+from .projects.projects_utils import project_get_depending_nodes
 
 log = logging.getLogger(__name__)
 

From c182ad41fcfac4fbf6ed4f3a09f221cee27718c6 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 15:04:45 +0100
Subject: [PATCH 169/200] fix import

---
 .../web/server/tests/unit/isolated/test_projects_utils.py     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/services/web/server/tests/unit/isolated/test_projects_utils.py b/services/web/server/tests/unit/isolated/test_projects_utils.py
index 5d9dc73f1ff..b69a645aedb 100644
--- a/services/web/server/tests/unit/isolated/test_projects_utils.py
+++ b/services/web/server/tests/unit/isolated/test_projects_utils.py
@@ -12,7 +12,7 @@
 from jsonschema import ValidationError
 from simcore_service_webserver.projects.projects_utils import (
     clone_project_document,
-    project_get_dependent_nodes,
+    project_get_depending_nodes,
 )
 from simcore_service_webserver.resources import resources
 
@@ -83,7 +83,7 @@ def fake_project_data(fake_data_dir: Path) -> Dict[str, Any]:
 async def test_project_get_depending_nodes(
     fake_project_data: Dict[str, Any], node_uuid: str, expected_dependencies: Set[str]
 ):
-    set_of_depending_nodes = await project_get_dependent_nodes(
+    set_of_depending_nodes = await project_get_depending_nodes(
         fake_project_data, node_uuid
     )
     assert set_of_depending_nodes == expected_dependencies

From a28c4c45a2ae7e0ca2e0a118bc7d31f26ff93939 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 15:28:04 +0100
Subject: [PATCH 170/200] startPipeline expects the partialPipeline arg

---
 .../class/osparc/desktop/SlideShowView.js     |  8 +++-
 .../class/osparc/desktop/StudyEditor.js       | 37 ++++++++++---------
 2 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/SlideShowView.js b/services/web/client/source/class/osparc/desktop/SlideShowView.js
index 6cba6d96fda..50404558d91 100644
--- a/services/web/client/source/class/osparc/desktop/SlideShowView.js
+++ b/services/web/client/source/class/osparc/desktop/SlideShowView.js
@@ -31,6 +31,10 @@ qx.Class.define("osparc.desktop.SlideShowView", {
     this._add(slideShowToolbar);
   },
 
+  events: {
+    "startPartialPipeline": "qx.event.type.Data"
+  },
+
   properties: {
     study: {
       check: "osparc.data.model.Study",
@@ -63,9 +67,9 @@ qx.Class.define("osparc.desktop.SlideShowView", {
         win.addListener("close", () => {
           if (win.getConfirmed()) {
             console.log("Run this", dependencies);
-          } else {
-            this.nodeSelected(oldCurrentNodeId);
+            this.fireDataEvent("startPartialPipeline", dependencies);
           }
+          this.nodeSelected(oldCurrentNodeId);
         }, this);
         return false;
       }
diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js
index 64e85795c34..dffced80200 100644
--- a/services/web/client/source/class/osparc/desktop/StudyEditor.js
+++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js
@@ -34,12 +34,22 @@ qx.Class.define("osparc.desktop.StudyEditor", {
     const slideshowView = this.__slideshowView = new osparc.desktop.SlideShowView();
     viewsStack.add(slideshowView);
 
+    slideshowView.addListener("startPartialPipeline", e => {
+      const partialPipeline = e.getData();
+      this.__startPipeline(partialPipeline);
+    }, this);
+
     [
       workbenchView.getStartStopButtons(),
       slideshowView.getStartStopButtons()
     ].forEach(startStopButtons => {
-      startStopButtons.addListener("startPipeline", this.__startPipeline, this);
-      startStopButtons.addListener("startPartialPipeline", () => this.__startPipeline(false), this);
+      startStopButtons.addListener("startPipeline", () => {
+        this.__startPipeline([]);
+      }, this);
+      startStopButtons.addListener("startPartialPipeline", () => {
+        const partialPipeline = this.getPageContext() === "workbench" ? this.__workbenchView.getSelectedNodeIDs() : this.__slideshowView.getSelectedNodeIDs();
+        this.__startPipeline(partialPipeline);
+      }, this);
       startStopButtons.addListener("stopPipeline", this.__stopPipeline, this);
     });
 
@@ -156,7 +166,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
 
 
     // ------------------ START/STOP PIPELINE ------------------
-    __startPipeline: function(runAll = true) {
+    __startPipeline: function(partialPipeline = []) {
       if (!osparc.data.Permissions.getInstance().canDo("study.start", true)) {
         return;
       }
@@ -167,7 +177,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
       startStopButtonsSS.setRunning(true);
       this.updateStudyDocument(true)
         .then(() => {
-          this.__doStartPipeline(runAll);
+          this.__doStartPipeline(partialPipeline);
         })
         .catch(() => {
           this.__getStudyLogger().error(null, "Run failed");
@@ -176,25 +186,18 @@ qx.Class.define("osparc.desktop.StudyEditor", {
         });
     },
 
-    __doStartPipeline: function(runAll) {
+    __doStartPipeline: function(partialPipeline) {
       if (this.getStudy().getSweeper().hasSecondaryStudies()) {
         const secondaryStudyIds = this.getStudy().getSweeper().getSecondaryStudyIds();
         secondaryStudyIds.forEach(secondaryStudyId => {
           this.__requestStartPipeline(secondaryStudyId);
         });
-      } else if (runAll) {
-        this.__requestStartPipeline(this.getStudy().getUuid());
       } else {
-        const selectedNodeIDs = this.getPageContext() === "workbench" ? this.__workbenchView.getSelectedNodeIDs() : this.__slideshowView.getSelectedNodeIDs();
-        if (selectedNodeIDs === null || selectedNodeIDs.length === 0) {
-          this.__requestStartPipeline(this.getStudy().getUuid());
-        } else {
-          this.__requestStartPipeline(this.getStudy().getUuid(), selectedNodeIDs);
-        }
+        this.__requestStartPipeline(this.getStudy().getUuid(), partialPipeline);
       }
     },
 
-    __requestStartPipeline: function(studyId, selectedNodeIDs = [], forceRestart = false) {
+    __requestStartPipeline: function(studyId, partialPipeline = [], forceRestart = false) {
       const url = "/computation/pipeline/" + encodeURIComponent(studyId) + ":start";
       const req = new osparc.io.request.ApiRequest(url, "POST");
       const startStopButtonsWB = this.__workbenchView.getStartStopButtons();
@@ -216,7 +219,7 @@ qx.Class.define("osparc.desktop.StudyEditor", {
           win.open();
           win.addListener("close", () => {
             if (win.getConfirmed()) {
-              this.__requestStartPipeline(studyId, selectedNodeIDs, true);
+              this.__requestStartPipeline(studyId, partialPipeline, true);
             }
           }, this);
         } else {
@@ -227,11 +230,11 @@ qx.Class.define("osparc.desktop.StudyEditor", {
       }, this);
 
       req.setRequestData({
-        "subgraph": selectedNodeIDs,
+        "subgraph": partialPipeline,
         "force_restart": forceRestart
       });
       req.send();
-      if (selectedNodeIDs.length) {
+      if (partialPipeline.length) {
         this.__getStudyLogger().info(null, "Starting partial pipeline");
       } else {
         this.__getStudyLogger().info(null, "Starting pipeline");

From b11a1f53aaa578815440e945b014ec44a9b78aab Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 15:32:56 +0100
Subject: [PATCH 171/200] Update SlideShowView.js

---
 .../client/source/class/osparc/desktop/SlideShowView.js   | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/SlideShowView.js b/services/web/client/source/class/osparc/desktop/SlideShowView.js
index 50404558d91..81aebe11a2e 100644
--- a/services/web/client/source/class/osparc/desktop/SlideShowView.js
+++ b/services/web/client/source/class/osparc/desktop/SlideShowView.js
@@ -66,10 +66,14 @@ qx.Class.define("osparc.desktop.SlideShowView", {
         win.open();
         win.addListener("close", () => {
           if (win.getConfirmed()) {
-            console.log("Run this", dependencies);
             this.fireDataEvent("startPartialPipeline", dependencies);
           }
-          this.nodeSelected(oldCurrentNodeId);
+          // bring the user back to the old node or to the first dependency
+          if (oldCurrentNodeId === this.__currentNodeId) {
+            this.nodeSelected(dependencies[0]);
+          } else {
+            this.nodeSelected(oldCurrentNodeId);
+          }
         }, this);
         return false;
       }

From 1136f644065bd7f1269d1f2d2bfdedbd7d62e6d8 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 15:37:04 +0100
Subject: [PATCH 172/200] Update SlideShowView.js

---
 services/web/client/source/class/osparc/desktop/SlideShowView.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/services/web/client/source/class/osparc/desktop/SlideShowView.js b/services/web/client/source/class/osparc/desktop/SlideShowView.js
index 81aebe11a2e..a64a1fc7994 100644
--- a/services/web/client/source/class/osparc/desktop/SlideShowView.js
+++ b/services/web/client/source/class/osparc/desktop/SlideShowView.js
@@ -59,7 +59,6 @@ qx.Class.define("osparc.desktop.SlideShowView", {
     __isNodeReady: function(node, oldCurrentNodeId) {
       const dependencies = node.getStatus().getDependencies();
       if (dependencies && dependencies.length) {
-        console.log(dependencies);
         const msg = this.tr("Do you want to run the required steps?");
         const win = new osparc.ui.window.Confirmation(msg);
         win.center();

From 240d136488101a34677016f8c66a91ebbe8efa29 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 15:42:40 +0100
Subject: [PATCH 173/200] Save every 3 seconds

---
 .../web/client/source/class/osparc/desktop/StudyEditor.js     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/services/web/client/source/class/osparc/desktop/StudyEditor.js b/services/web/client/source/class/osparc/desktop/StudyEditor.js
index dffced80200..1cc6ade2ce5 100644
--- a/services/web/client/source/class/osparc/desktop/StudyEditor.js
+++ b/services/web/client/source/class/osparc/desktop/StudyEditor.js
@@ -353,8 +353,8 @@ qx.Class.define("osparc.desktop.StudyEditor", {
 
     __startAutoSaveTimer: function() {
       let diffPatcher = osparc.wrapper.JsonDiffPatch.getInstance();
-      // Save every 5 seconds
-      const interval = 5000;
+      // Save every 3 seconds
+      const interval = 3000;
       let timer = this.__autoSaveTimer = new qx.event.Timer(interval);
       timer.addListener("interval", () => {
         const newObj = this.getStudy().serialize();

From b2954e9e175bf1756eee7819a591b13ba60d66d2 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 16:03:20 +0100
Subject: [PATCH 174/200] minor

---
 .../class/osparc/component/widget/CollapsibleView.js   | 10 +++++-----
 .../source/class/osparc/component/widget/NodePorts.js  |  3 +++
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/widget/CollapsibleView.js b/services/web/client/source/class/osparc/component/widget/CollapsibleView.js
index c95e7347af7..72916a137d3 100644
--- a/services/web/client/source/class/osparc/component/widget/CollapsibleView.js
+++ b/services/web/client/source/class/osparc/component/widget/CollapsibleView.js
@@ -100,7 +100,7 @@ qx.Class.define("osparc.component.widget.CollapsibleView", {
         }
         case "title": {
           const header = this.getChildControl("header");
-          control = new qx.ui.basic.Atom(this.getTitle());
+          control = new qx.ui.basic.Label(this.getTitle());
           header.addAt(control, 1);
           // Attach handler
           this.__attachToggler(control);
@@ -187,14 +187,14 @@ qx.Class.define("osparc.component.widget.CollapsibleView", {
       }
     },
 
-    _applyTitle: function(title) {
-      this.getChildControl("title").setLabel(title);
-    },
-
     _applyCaretSize: function(size) {
       this.getChildControl("caret").setSource(this.__getCaretId(this.getCollapsed()));
     },
 
+    _applyTitle: function(title) {
+      this.getChildControl("title").setValue(title);
+    },
+
     __getCaretId: function(collapsed) {
       const caretSize = this.getCaretSize();
       const moreCaret = this.self().COLLAPSED_CARET;
diff --git a/services/web/client/source/class/osparc/component/widget/NodePorts.js b/services/web/client/source/class/osparc/component/widget/NodePorts.js
index 7fba70ed21e..beae05b1625 100644
--- a/services/web/client/source/class/osparc/component/widget/NodePorts.js
+++ b/services/web/client/source/class/osparc/component/widget/NodePorts.js
@@ -44,6 +44,9 @@ qx.Class.define("osparc.component.widget.NodePorts", {
 
     this.base(arguments, node.getLabel());
 
+    this.getTitleBar().set({
+      height: 30
+    });
     node.bind("label", this, "title");
   },
 

From 1a3e85f1c12d677bb9ed1777c83dd1b523cedfe4 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 16:24:54 +0100
Subject: [PATCH 175/200] added icon with color to NodePorts in NodeView

---
 .../osparc/component/widget/CollapsibleView.js | 10 ++++++++++
 .../class/osparc/component/widget/NodePorts.js | 18 ++++++++++++++++++
 .../source/class/osparc/utils/StatusUI.js      | 12 ++++++++++++
 3 files changed, 40 insertions(+)

diff --git a/services/web/client/source/class/osparc/component/widget/CollapsibleView.js b/services/web/client/source/class/osparc/component/widget/CollapsibleView.js
index 72916a137d3..b31323a7ab2 100644
--- a/services/web/client/source/class/osparc/component/widget/CollapsibleView.js
+++ b/services/web/client/source/class/osparc/component/widget/CollapsibleView.js
@@ -106,6 +106,12 @@ qx.Class.define("osparc.component.widget.CollapsibleView", {
           this.__attachToggler(control);
           break;
         }
+        case "icon": {
+          const header = this.getChildControl("header");
+          control = new qx.ui.basic.Image();
+          header.addAt(control, 2);
+          break;
+        }
       }
       return control || this.base(arguments, id);
     },
@@ -195,6 +201,10 @@ qx.Class.define("osparc.component.widget.CollapsibleView", {
       this.getChildControl("title").setValue(title);
     },
 
+    _applyIcon: function(icon) {
+      this.getChildControl("icon").setSource(icon);
+    },
+
     __getCaretId: function(collapsed) {
       const caretSize = this.getCaretSize();
       const moreCaret = this.self().COLLAPSED_CARET;
diff --git a/services/web/client/source/class/osparc/component/widget/NodePorts.js b/services/web/client/source/class/osparc/component/widget/NodePorts.js
index beae05b1625..f5deef9246f 100644
--- a/services/web/client/source/class/osparc/component/widget/NodePorts.js
+++ b/services/web/client/source/class/osparc/component/widget/NodePorts.js
@@ -48,6 +48,24 @@ qx.Class.define("osparc.component.widget.NodePorts", {
       height: 30
     });
     node.bind("label", this, "title");
+
+    node.getStatus().bind("modified", this.getChildControl("icon"), "source", {
+      converter: modified => {
+        if (modified === true) {
+          return osparc.utils.StatusUI.getIconSource("modified");
+        }
+        return osparc.utils.StatusUI.getIconSource("up-to-date");
+      },
+      onUpdate: (source, target) => {
+        const modified = source.getModified();
+        if (modified === true) {
+          console.log("modified");
+          target.setTextColor(osparc.utils.StatusUI.getColor("modified"));
+        }
+        console.log("up-to-date");
+        target.setTextColor(osparc.utils.StatusUI.getColor("up-to-date"));
+      }
+    }, this);
   },
 
   properties: {
diff --git a/services/web/client/source/class/osparc/utils/StatusUI.js b/services/web/client/source/class/osparc/utils/StatusUI.js
index 0d8934ef7b2..a7d47e7f6ea 100644
--- a/services/web/client/source/class/osparc/utils/StatusUI.js
+++ b/services/web/client/source/class/osparc/utils/StatusUI.js
@@ -52,6 +52,12 @@ qx.Class.define("osparc.utils.StatusUI", {
         case "connecting":
           return "@FontAwesome5Solid/circle-notch/12";
 
+        // ports
+        case "modified":
+          return "@FontAwesome5Solid/exclamation-circle/12";
+        case "up-to-date":
+          return "@FontAwesome5Solid/check/12";
+
         default:
           return "";
       }
@@ -111,6 +117,12 @@ qx.Class.define("osparc.utils.StatusUI", {
         case "connecting":
           return "busy-orange";
 
+        // ports
+        case "modified":
+          return "busy-orange";
+        case "up-to-date":
+          return "ready-green";
+
         default:
           return "text";
       }

From 477020859666490d600caa344f95aeb03e428f71 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 16:29:11 +0100
Subject: [PATCH 176/200] Update NodePorts.js

---
 .../osparc/component/widget/NodePorts.js      | 19 ++++---------------
 1 file changed, 4 insertions(+), 15 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/widget/NodePorts.js b/services/web/client/source/class/osparc/component/widget/NodePorts.js
index f5deef9246f..5d66d497514 100644
--- a/services/web/client/source/class/osparc/component/widget/NodePorts.js
+++ b/services/web/client/source/class/osparc/component/widget/NodePorts.js
@@ -50,21 +50,10 @@ qx.Class.define("osparc.component.widget.NodePorts", {
     node.bind("label", this, "title");
 
     node.getStatus().bind("modified", this.getChildControl("icon"), "source", {
-      converter: modified => {
-        if (modified === true) {
-          return osparc.utils.StatusUI.getIconSource("modified");
-        }
-        return osparc.utils.StatusUI.getIconSource("up-to-date");
-      },
-      onUpdate: (source, target) => {
-        const modified = source.getModified();
-        if (modified === true) {
-          console.log("modified");
-          target.setTextColor(osparc.utils.StatusUI.getColor("modified"));
-        }
-        console.log("up-to-date");
-        target.setTextColor(osparc.utils.StatusUI.getColor("up-to-date"));
-      }
+      converter: modified => osparc.utils.StatusUI.getIconSource(modified === true ? "modified" : "up-to-date")
+    }, this);
+    node.getStatus().bind("modified", this.getChildControl("icon"), "textColor", {
+      converter: modified => osparc.utils.StatusUI.getColor(modified === true ? "modified" : "up-to-date")
     }, this);
   },
 

From 6e2523662195f067d13f1a67533df73c69a8a5ee Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 16:30:58 +0100
Subject: [PATCH 177/200] Update NodePorts.js

---
 .../client/source/class/osparc/component/widget/NodePorts.js   | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/services/web/client/source/class/osparc/component/widget/NodePorts.js b/services/web/client/source/class/osparc/component/widget/NodePorts.js
index 5d66d497514..1afcd43de72 100644
--- a/services/web/client/source/class/osparc/component/widget/NodePorts.js
+++ b/services/web/client/source/class/osparc/component/widget/NodePorts.js
@@ -55,6 +55,9 @@ qx.Class.define("osparc.component.widget.NodePorts", {
     node.getStatus().bind("modified", this.getChildControl("icon"), "textColor", {
       converter: modified => osparc.utils.StatusUI.getColor(modified === true ? "modified" : "up-to-date")
     }, this);
+    node.getStatus().bind("modified", this.getChildControl("icon"), "toolTipText", {
+      converter: modified => modified === true ? this.tr("Out of date") : ""
+    }, this);
   },
 
   properties: {

From 543e2d0f8dc2f4a7a8468c4f868c0377fc063415 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 16:47:29 +0100
Subject: [PATCH 178/200] NodeStatusUI in NodeView toolbar

---
 .../class/osparc/component/node/BaseNodeView.js  | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
index 524668c3ae8..113e51b6c30 100644
--- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js
+++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
@@ -58,6 +58,7 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
     __pane2: null,
     __title: null,
     __serviceInfoLayout: null,
+    __nodeStatusUI: null,
     __header: null,
     _mainView: null,
     __inputsView: null,
@@ -241,6 +242,12 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
 
       header.addSpacer();
 
+      // just a placeholder until the node is set
+      const nodeStatusUI = this.__nodeStatusUI = new qx.ui.core.Widget();
+      header.add(nodeStatusUI);
+
+      header.addSpacer();
+
       const buttonsPart = this.__buttonContainer = new qx.ui.toolbar.Part();
       const filesBtn = this.__outFilesButton = new qx.ui.toolbar.Button(this.tr("Output Files"), "@FontAwesome5Solid/folder-open/14");
       osparc.utils.Utils.setIdToWidget(filesBtn, "nodeViewFilesBtn");
@@ -507,6 +514,15 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
         const infoButton = this.__getInfoButton();
         this.__serviceInfoLayout.add(infoButton);
       }
+
+      const nsUIidx = this.__header.indexOf(this.__nodeStatusUI);
+      if (nsUIidx > -1) {
+        this.__header.remove(this.__nodeStatusUI);
+      }
+      this.__nodeStatusUI = new osparc.ui.basic.NodeStatusUI(node).set({
+        backgroundColor: "material-button-background"
+      });
+      this.__header.addAt(this.__nodeStatusUI, nsUIidx);
     }
   }
 });

From 6bd00cec446136e60fe362a5870a03b9b89955ec Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 16:54:00 +0100
Subject: [PATCH 179/200] Update BaseNodeView.js

---
 .../osparc/component/node/BaseNodeView.js     | 20 +++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/node/BaseNodeView.js b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
index 113e51b6c30..c7b658f6508 100644
--- a/services/web/client/source/class/osparc/component/node/BaseNodeView.js
+++ b/services/web/client/source/class/osparc/component/node/BaseNodeView.js
@@ -498,6 +498,16 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
       throw new Error("Abstract method called!");
     },
 
+    __createNodeStatusUI: function(node) {
+      const nodeStatusUI = new osparc.ui.basic.NodeStatusUI(node).set({
+        backgroundColor: "material-button-background"
+      });
+      nodeStatusUI.getChildControl("label").set({
+        font: "text-16"
+      });
+      return nodeStatusUI;
+    },
+
     /**
       * @param node {osparc.data.model.Node} node
       */
@@ -515,14 +525,12 @@ qx.Class.define("osparc.component.node.BaseNodeView", {
         this.__serviceInfoLayout.add(infoButton);
       }
 
-      const nsUIidx = this.__header.indexOf(this.__nodeStatusUI);
-      if (nsUIidx > -1) {
+      const idx = this.__header.indexOf(this.__nodeStatusUI);
+      if (idx > -1) {
         this.__header.remove(this.__nodeStatusUI);
       }
-      this.__nodeStatusUI = new osparc.ui.basic.NodeStatusUI(node).set({
-        backgroundColor: "material-button-background"
-      });
-      this.__header.addAt(this.__nodeStatusUI, nsUIidx);
+      this.__nodeStatusUI = this.__createNodeStatusUI(node);
+      this.__header.addAt(this.__nodeStatusUI, idx);
     }
   }
 });

From 479683febbd75456ec4406ed84ffc116ef509430 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Tue, 23 Feb 2021 17:11:29 +0100
Subject: [PATCH 180/200] fix

---
 .../web/client/source/class/osparc/desktop/WorkbenchView.js  | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/services/web/client/source/class/osparc/desktop/WorkbenchView.js b/services/web/client/source/class/osparc/desktop/WorkbenchView.js
index bc759d00dfa..f32b5f5ccf6 100644
--- a/services/web/client/source/class/osparc/desktop/WorkbenchView.js
+++ b/services/web/client/source/class/osparc/desktop/WorkbenchView.js
@@ -85,7 +85,10 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
     },
 
     getSelectedNodeIDs: function() {
-      return this.__workbenchUI.getSelectedNodeIDs();
+      if (this.__mainPanel.getMainView() === this.__workbenchUI) {
+        return this.__workbenchUI.getSelectedNodeIDs();
+      }
+      return [this.__currentNodeId];
     },
 
     nodeSelected: function(nodeId) {

From 83b6d8d2a1639249cd9929ea5d023724920a2038 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 10:10:47 +0100
Subject: [PATCH 181/200] added annotations

---
 .../tests/unit/with_dbs/slow/test_projects.py | 23 +++++++++++--------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/services/web/server/tests/unit/with_dbs/slow/test_projects.py b/services/web/server/tests/unit/with_dbs/slow/test_projects.py
index 734f8fa19d9..6e8882b2031 100644
--- a/services/web/server/tests/unit/with_dbs/slow/test_projects.py
+++ b/services/web/server/tests/unit/with_dbs/slow/test_projects.py
@@ -6,11 +6,12 @@
 import time
 import unittest.mock as mock
 import uuid as uuidlib
-from asyncio import Future, sleep
+from asyncio import Future
 from copy import deepcopy
-from typing import Callable, Dict, List, Optional, Tuple, Union
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
 from unittest.mock import call
 
+import aiohttp
 import pytest
 import socketio
 from _helpers import ExpectedResponse, HTTPLocked, standard_role_response
@@ -209,7 +210,9 @@ async def project_db_cleaner(client):
 
 
 @pytest.fixture
-async def catalog_subsystem_mock(monkeypatch):
+async def catalog_subsystem_mock(
+    monkeypatch,
+) -> Callable[[Optional[Union[List[Dict], Dict]]], None]:
     services_in_project = []
 
     def creator(projects: Optional[Union[List[Dict], Dict]] = None) -> None:
@@ -520,13 +523,13 @@ async def _delete_project(client, project: Dict, expected: web.Response) -> None
     ],
 )
 async def test_list_projects(
-    client,
-    logged_user,
-    user_project,
-    template_project,
-    expected,
-    catalog_subsystem_mock,
-    director_v2_service_mock,
+    client: aiohttp.test_utils.TestClient,
+    logged_user: Dict[str, Any],
+    user_project: Dict[str, Any],
+    template_project: Dict[str, Any],
+    expected: aiohttp.web.HTTPException,
+    catalog_subsystem_mock: Callable[[Optional[Union[List[Dict], Dict]]], None],
+    director_v2_service_mock: aioresponses,
 ):
     catalog_subsystem_mock([user_project, template_project])
     data = await _list_projects(client, expected)

From 40d4544bf4a2b5bd8cfb0e3f87e646ce4943f911 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 12:27:02 +0100
Subject: [PATCH 182/200] return the states of all computational nodes

---
 .../src/models_library/projects_pipeline.py   |  5 +-
 .../api/routes/computations.py                |  8 ++-
 .../simcore_service_director_v2/utils/dags.py |  6 +-
 .../tests/integration/test_computation_api.py | 59 +++++++++++++++++--
 4 files changed, 64 insertions(+), 14 deletions(-)

diff --git a/packages/models-library/src/models_library/projects_pipeline.py b/packages/models-library/src/models_library/projects_pipeline.py
index a15a1d8a5dd..907e21da7dd 100644
--- a/packages/models-library/src/models_library/projects_pipeline.py
+++ b/packages/models-library/src/models_library/projects_pipeline.py
@@ -9,10 +9,11 @@
 
 class PipelineDetails(BaseModel):
     adjacency_list: Dict[NodeID, List[NodeID]] = Field(
-        ..., description="The adjacency list in terms of {NodeID: [successor NodeID]}"
+        ...,
+        description="The adjacency list of the current pipeline in terms of {NodeID: [successor NodeID]}",
     )
     node_states: Dict[NodeID, NodeState] = Field(
-        ..., description="The states of each of the pipeline node"
+        ..., description="The states of each of the computational nodes in the pipeline"
     )
 
 
diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
index 07d8eea55d5..56da626a3eb 100644
--- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
+++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
@@ -207,13 +207,15 @@ async def get_computation(
         )
 
         # get the project task states
-        tasks: List[CompTaskAtDB] = await computation_tasks.get_all_tasks(project_id)
+        all_comp_tasks: List[CompTaskAtDB] = await computation_tasks.get_all_tasks(
+            project_id
+        )
         # create the complete DAG graph
-        complete_dag = create_complete_dag_from_tasks(tasks)
+        complete_dag = create_complete_dag_from_tasks(all_comp_tasks)
 
         # filter the tasks by the effective pipeline
         filtered_tasks = [
-            t for t in tasks if str(t.node_id) in list(pipeline_dag.nodes())
+            t for t in all_comp_tasks if str(t.node_id) in list(pipeline_dag.nodes())
         ]
         pipeline_state = get_pipeline_state_from_task_states(
             filtered_tasks, celery_client.settings.publication_timeout
diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
index 95b037f5c05..7ede77d9e92 100644
--- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py
+++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
@@ -176,11 +176,11 @@ async def compute_pipeline_details(
         adjacency_list=nx.to_dict_of_lists(pipeline_dag),
         node_states={
             node_id: NodeState(
-                modified=node_data.get(kNODE_MODIFIED_STATE),
-                dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE),
+                modified=node_data.get(kNODE_MODIFIED_STATE, False),
+                dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE, set()),
             )
             for node_id, node_data in complete_dag.nodes.data()
-            if node_id in pipeline_dag.nodes
+            if _is_node_computational(node_data["key"])
         },
     )
 
diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py
index d30208cb398..125b4a8c7f9 100644
--- a/services/director-v2/tests/integration/test_computation_api.py
+++ b/services/director-v2/tests/integration/test_computation_api.py
@@ -332,12 +332,12 @@ def test_start_empty_computation(
 
 PartialComputationParams = namedtuple(
     "PartialComputationParams",
-    "subgraph_elements, exp_pipeline_adj_list, exp_node_states",
+    "subgraph_elements, exp_pipeline_adj_list, exp_node_states, exp_node_states_after_run",
 )
 
 
 @pytest.mark.parametrize(
-    "subgraph_elements,exp_pipeline_adj_list, exp_node_states",
+    "subgraph_elements,exp_pipeline_adj_list, exp_node_states, exp_node_states_after_run",
     [
         pytest.param(
             *PartialComputationParams(
@@ -347,7 +347,37 @@ def test_start_empty_computation(
                     1: {
                         "modified": True,
                         "dependencies": [],
-                    }
+                    },
+                    2: {
+                        "modified": True,
+                        "dependencies": [1],
+                    },
+                    3: {
+                        "modified": True,
+                        "dependencies": [],
+                    },
+                    4: {
+                        "modified": True,
+                        "dependencies": [2, 3],
+                    },
+                },
+                exp_node_states_after_run={
+                    1: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    2: {
+                        "modified": True,
+                        "dependencies": [],
+                    },
+                    3: {
+                        "modified": True,
+                        "dependencies": [],
+                    },
+                    4: {
+                        "modified": True,
+                        "dependencies": [2, 3],
+                    },
                 },
             ),
             id="element 0,1",
@@ -374,6 +404,24 @@ def test_start_empty_computation(
                         "dependencies": [2, 3],
                     },
                 },
+                exp_node_states_after_run={
+                    1: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    2: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    3: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                    4: {
+                        "modified": False,
+                        "dependencies": [],
+                    },
+                },
             ),
             id="element 1,2,4",
         ),
@@ -388,6 +436,7 @@ def test_run_partial_computation(
     subgraph_elements: List[int],
     exp_pipeline_adj_list: Dict[int, List[str]],
     exp_node_states: Dict[int, Dict[str, Any]],
+    exp_node_states_after_run: Dict[int, Dict[str, Any]],
 ):
     sleepers_project: ProjectAtDB = project(workbench=fake_workbench_without_outputs)
 
@@ -448,9 +497,7 @@ def _convert_to_pipeline_details(
         client, task_out.url, user_id, sleepers_project.uuid
     )
     expected_pipeline_details = _convert_to_pipeline_details(
-        sleepers_project,
-        exp_pipeline_adj_list,
-        {n: {"modified": False, "dependencies": []} for n in exp_node_states},
+        sleepers_project, exp_pipeline_adj_list, exp_node_states_after_run
     )
     _assert_computation_task_out_obj(
         client,

From b8a86f898d8a879efc9a967cea11c48702f167bd Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:20:54 +0100
Subject: [PATCH 183/200] add function to get nodes depending on another

---
 .../projects/projects_utils.py                | 23 ++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_utils.py b/services/web/server/src/simcore_service_webserver/projects/projects_utils.py
index 12833721d6d..d84a32a8f13 100644
--- a/services/web/server/src/simcore_service_webserver/projects/projects_utils.py
+++ b/services/web/server/src/simcore_service_webserver/projects/projects_utils.py
@@ -2,7 +2,7 @@
 import re
 import uuid as uuidlib
 from copy import deepcopy
-from typing import AnyStr, Dict, List, Match, Optional, Set, Tuple
+from typing import Any, AnyStr, Dict, List, Match, Optional, Set, Tuple
 
 from servicelib.decorators import safe_return
 
@@ -118,7 +118,9 @@ def _get_param_input_match(name, value, access) -> Optional[Match[AnyStr]]:
     return project
 
 
-def is_graph_equal(lhs_workbench: Dict, rhs_workbench: Dict) -> bool:
+def is_graph_equal(
+    lhs_workbench: Dict[str, Any], rhs_workbench: Dict[str, Any]
+) -> bool:
     """Checks whether both workbench contain the same graph
 
     Two graphs are the same when the same topology (i.e. nodes and edges)
@@ -152,7 +154,7 @@ def is_graph_equal(lhs_workbench: Dict, rhs_workbench: Dict) -> bool:
 
 
 async def project_uses_available_services(
-    project: Dict, available_services: List[Dict]
+    project: Dict[str, Any], available_services: List[Dict[str, Any]]
 ) -> bool:
     if not project["workbench"]:
         # empty project
@@ -168,3 +170,18 @@ async def project_uses_available_services(
     }
 
     return needed_services.issubset(available_services)
+
+
+async def project_get_depending_nodes(
+    project: Dict[str, Any], node_uuid: str
+) -> Set[str]:
+    depending_node_uuids = set()
+    for dep_node_uuid, dep_node_data in project.get("workbench", {}).items():
+        for dep_node_inputs_key_data in dep_node_data.get("inputs", {}).values():
+            if (
+                isinstance(dep_node_inputs_key_data, dict)
+                and dep_node_inputs_key_data.get("nodeUuid") == node_uuid
+            ):
+                depending_node_uuids.add(dep_node_uuid)
+
+    return depending_node_uuids

From 0e74941e051ea6a782f3650d36cc45f4269da802 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:26:26 +0100
Subject: [PATCH 184/200] create test for checking new function

---
 .../unit/isolated/test_projects_utils.py      | 44 ++++++++++++++++---
 1 file changed, 39 insertions(+), 5 deletions(-)

diff --git a/services/web/server/tests/unit/isolated/test_projects_utils.py b/services/web/server/tests/unit/isolated/test_projects_utils.py
index 6037949fc67..5d9dc73f1ff 100644
--- a/services/web/server/tests/unit/isolated/test_projects_utils.py
+++ b/services/web/server/tests/unit/isolated/test_projects_utils.py
@@ -4,16 +4,20 @@
 
 import json
 from copy import deepcopy
+from pathlib import Path
+from typing import Any, Dict, Set
 
 import jsonschema
 import pytest
 from jsonschema import ValidationError
-
-from simcore_service_webserver.projects.projects_utils import clone_project_document
+from simcore_service_webserver.projects.projects_utils import (
+    clone_project_document,
+    project_get_dependent_nodes,
+)
 from simcore_service_webserver.resources import resources
 
 
-def load_template_projects():
+def load_template_projects() -> Dict[str, Any]:
     projects = []
     projects_names = [
         name for name in resources.listdir("data") if "template-projects" in name
@@ -25,7 +29,7 @@ def load_template_projects():
 
 
 @pytest.fixture
-def project_schema(project_schema_file):
+def project_schema(project_schema_file: Path) -> Dict[str, Any]:
     with open(project_schema_file) as fh:
         schema = json.load(fh)
     return schema
@@ -34,7 +38,9 @@ def project_schema(project_schema_file):
 @pytest.mark.parametrize(
     "name,project", [(p["name"], p) for p in load_template_projects()]
 )
-def test_clone_project_document(name, project, project_schema):
+def test_clone_project_document(
+    name: str, project: Dict[str, Any], project_schema: Dict[str, Any]
+):
 
     source = deepcopy(project)
     clone, _ = clone_project_document(source)
@@ -53,3 +59,31 @@ def test_clone_project_document(name, project, project_schema):
         jsonschema.validate(instance=clone, schema=project_schema)
     except ValidationError as err:
         pytest.fail(f"Invalid clone of '{name}': {err.message}")
+
+
+@pytest.fixture(scope="session")
+def fake_project_data(fake_data_dir: Path) -> Dict[str, Any]:
+    with (fake_data_dir / "fake-project.json").open() as fp:
+        return json.load(fp)
+
+
+@pytest.mark.parametrize(
+    "node_uuid, expected_dependencies",
+    [
+        (
+            "b4b20476-e7c0-47c2-8cc4-f66ac21a13bf",
+            {
+                "5739e377-17f7-4f09-a6ad-62659fb7fdec",
+            },
+        ),
+        ("5739e377-17f7-4f09-a6ad-62659fb7fdec", set()),
+        ("351fd505-1ee3-466d-ad6c-ea2915ffd364", set()),
+    ],
+)
+async def test_project_get_depending_nodes(
+    fake_project_data: Dict[str, Any], node_uuid: str, expected_dependencies: Set[str]
+):
+    set_of_depending_nodes = await project_get_dependent_nodes(
+        fake_project_data, node_uuid
+    )
+    assert set_of_depending_nodes == expected_dependencies

From 64bc2ef48deeac0e61019a1122fe191597e2640b Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:26:46 +0100
Subject: [PATCH 185/200] notification of depending nodes

---
 .../computation_comp_tasks_listening_task.py       | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
index c711881d849..ee554ed363b 100644
--- a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
+++ b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
@@ -17,9 +17,14 @@
 from pydantic.types import PositiveInt
 from servicelib.application_keys import APP_DB_ENGINE_KEY
 from servicelib.logging_utils import log_decorator
+from servicelib.utils import logged_gather
 from simcore_postgres_database.webserver_models import DB_CHANNEL_NAME, projects
 from sqlalchemy.sql import select
 
+from services.web.server.src.simcore_service_webserver.projects.projects_utils import (
+    project_get_depending_nodes,
+)
+
 from .computation_api import convert_state_from_db
 from .projects import projects_api, projects_exceptions
 
@@ -73,6 +78,15 @@ async def _update_project_outputs(
     )
 
     await projects_api.notify_project_node_update(app, project, node_uuid)
+    # get depending node and notify for these ones as well
+    depending_node_uuids = await project_get_depending_nodes(project, node_uuid)
+    await logged_gather(
+        *[
+            projects_api.notify_project_node_update(app, project, n)
+            for n in depending_node_uuids
+        ]
+    )
+    # notifiy
     await projects_api.post_trigger_connected_service_retrieve(
         app=app, project=project, updated_node_uuid=node_uuid, changed_keys=changed_keys
     )

From 12ba46c6c02ee7fdf259bc5f277e98958a7afbfd Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:49:24 +0100
Subject: [PATCH 186/200] also return the current state of the node as was
 designed

---
 .../api/routes/computations.py                         | 10 +++++++---
 .../src/simcore_service_director_v2/utils/dags.py      |  7 ++++++-
 .../simcore_service_webserver/projects/projects_api.py |  4 +---
 3 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
index 56da626a3eb..ea0fdae7f22 100644
--- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
+++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
@@ -161,7 +161,7 @@ async def create_computation(
             if job.start_pipeline
             else RunningState.NOT_STARTED,
             pipeline_details=await compute_pipeline_details(
-                complete_dag, computational_dag
+                complete_dag, computational_dag, comp_tasks
             ),
             url=f"{request.url}/{job.project_id}",
             stop_url=f"{request.url}/{job.project_id}:stop"
@@ -231,7 +231,9 @@ async def get_computation(
         task_out = ComputationTaskOut(
             id=project_id,
             state=pipeline_state,
-            pipeline_details=await compute_pipeline_details(complete_dag, pipeline_dag),
+            pipeline_details=await compute_pipeline_details(
+                complete_dag, pipeline_dag, all_comp_tasks
+            ),
             url=f"{request.url.remove_query_params('user_id')}",
             stop_url=f"{request.url.remove_query_params('user_id')}:stop"
             if is_pipeline_running(pipeline_state)
@@ -308,7 +310,9 @@ async def stop_computation_project(
         return ComputationTaskOut(
             id=project_id,
             state=pipeline_state,
-            pipeline_details=await compute_pipeline_details(complete_dag, pipeline_dag),
+            pipeline_details=await compute_pipeline_details(
+                complete_dag, pipeline_dag, tasks
+            ),
             url=f"{str(request.url).rstrip(':stop')}",
         )
 
diff --git a/services/director-v2/src/simcore_service_director_v2/utils/dags.py b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
index 7ede77d9e92..0f114ab74c8 100644
--- a/services/director-v2/src/simcore_service_director_v2/utils/dags.py
+++ b/services/director-v2/src/simcore_service_director_v2/utils/dags.py
@@ -6,6 +6,7 @@
 from models_library.projects_nodes import NodeID, NodeState
 from models_library.projects_nodes_io import PortLink
 from models_library.projects_pipeline import PipelineDetails
+from models_library.projects_state import RunningState
 from models_library.utils.nodes import compute_node_hash
 from simcore_service_director_v2.models.domains.comp_tasks import CompTaskAtDB
 
@@ -167,7 +168,7 @@ async def create_minimal_computational_graph_based_on_selection(
 
 @log_decorator(logger=logger)
 async def compute_pipeline_details(
-    complete_dag: nx.DiGraph, pipeline_dag: nx.DiGraph
+    complete_dag: nx.DiGraph, pipeline_dag: nx.DiGraph, comp_tasks: List[CompTaskAtDB]
 ) -> PipelineDetails:
 
     # first pass, traversing in topological order to correctly get the dependencies, set the nodes states
@@ -178,6 +179,10 @@ async def compute_pipeline_details(
             node_id: NodeState(
                 modified=node_data.get(kNODE_MODIFIED_STATE, False),
                 dependencies=node_data.get(kNODE_DEPENDENCIES_TO_COMPUTE, set()),
+                currentStatus=next(
+                    (task.state for task in comp_tasks if str(task.node_id) == node_id),
+                    RunningState.UNKNOWN,
+                ),
             )
             for node_id, node_data in complete_dag.nodes.data()
             if _is_node_computational(node_data["key"])
diff --git a/services/web/server/src/simcore_service_webserver/projects/projects_api.py b/services/web/server/src/simcore_service_webserver/projects/projects_api.py
index 593a51ee4a0..865b77de992 100644
--- a/services/web/server/src/simcore_service_webserver/projects/projects_api.py
+++ b/services/web/server/src/simcore_service_webserver/projects/projects_api.py
@@ -529,9 +529,7 @@ async def add_project_states_for_user(
                 if prj_node is None:
                     continue
                 node_state_dict = json.loads(
-                    node_state.json(
-                        by_alias=True, exclude_unset=True, exclude={"current_status"}
-                    )
+                    node_state.json(by_alias=True, exclude_unset=True)
                 )
                 prj_node.setdefault("state", {}).update(node_state_dict)
 

From 2d6af7b62e59280ad08bba32f9a15682f49519f1 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 14:59:18 +0100
Subject: [PATCH 187/200] fix import

---
 .../computation_comp_tasks_listening_task.py                 | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
index ee554ed363b..aa14c3f7f43 100644
--- a/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
+++ b/services/web/server/src/simcore_service_webserver/computation_comp_tasks_listening_task.py
@@ -21,12 +21,9 @@
 from simcore_postgres_database.webserver_models import DB_CHANNEL_NAME, projects
 from sqlalchemy.sql import select
 
-from services.web.server.src.simcore_service_webserver.projects.projects_utils import (
-    project_get_depending_nodes,
-)
-
 from .computation_api import convert_state_from_db
 from .projects import projects_api, projects_exceptions
+from .projects.projects_utils import project_get_depending_nodes
 
 log = logging.getLogger(__name__)
 

From 528a6f1d11a38dda3d709f502f571dbbe8447868 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 15:04:45 +0100
Subject: [PATCH 188/200] fix import

---
 .../web/server/tests/unit/isolated/test_projects_utils.py     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/services/web/server/tests/unit/isolated/test_projects_utils.py b/services/web/server/tests/unit/isolated/test_projects_utils.py
index 5d9dc73f1ff..b69a645aedb 100644
--- a/services/web/server/tests/unit/isolated/test_projects_utils.py
+++ b/services/web/server/tests/unit/isolated/test_projects_utils.py
@@ -12,7 +12,7 @@
 from jsonschema import ValidationError
 from simcore_service_webserver.projects.projects_utils import (
     clone_project_document,
-    project_get_dependent_nodes,
+    project_get_depending_nodes,
 )
 from simcore_service_webserver.resources import resources
 
@@ -83,7 +83,7 @@ def fake_project_data(fake_data_dir: Path) -> Dict[str, Any]:
 async def test_project_get_depending_nodes(
     fake_project_data: Dict[str, Any], node_uuid: str, expected_dependencies: Set[str]
 ):
-    set_of_depending_nodes = await project_get_dependent_nodes(
+    set_of_depending_nodes = await project_get_depending_nodes(
         fake_project_data, node_uuid
     )
     assert set_of_depending_nodes == expected_dependencies

From fc7686f28fae48091fe94d878cb09dbd1856cbd7 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 17:21:18 +0100
Subject: [PATCH 189/200] use the correct comp_tasks when giving back states

---
 .../api/routes/computations.py                |  4 ++--
 .../modules/db/repositories/comp_tasks.py     | 19 +++++++++++++------
 .../tests/integration/test_computation_api.py | 11 +++++++++++
 3 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
index ea0fdae7f22..bb3f9841770 100644
--- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
+++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
@@ -132,7 +132,7 @@ async def create_computation(
         await computation_pipelines.upsert_pipeline(
             project.uuid, computational_dag, job.start_pipeline
         )
-        await computation_tasks.upsert_tasks_from_project(
+        inserted_comp_tasks = await computation_tasks.upsert_tasks_from_project(
             project,
             director_client,
             list(computational_dag.nodes()) if job.start_pipeline else [],
@@ -161,7 +161,7 @@ async def create_computation(
             if job.start_pipeline
             else RunningState.NOT_STARTED,
             pipeline_details=await compute_pipeline_details(
-                complete_dag, computational_dag, comp_tasks
+                complete_dag, computational_dag, inserted_comp_tasks
             ),
             url=f"{request.url}/{job.project_id}",
             stop_url=f"{request.url}/{job.project_id}:stop"
diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
index 4ab0366833a..494c81fb414 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
@@ -14,6 +14,7 @@
     ServiceKeyVersion,
     ServiceType,
 )
+from sqlalchemy import literal_column
 from sqlalchemy.dialects.postgresql import insert
 
 from ....models.domains.comp_tasks import CompTaskAtDB, Image, NodeSchema
@@ -160,10 +161,12 @@ async def _sequentially_upsert_tasks_from_project(
         director_client: DirectorV0Client,
         published_nodes: List[NodeID],
         str_project_uuid: str,
-    ) -> None:
+    ) -> List[CompTaskAtDB]:
 
         # NOTE: really do an upsert here because of issue https://github.com/ITISFoundation/osparc-simcore/issues/2125
-        list_of_comp_tasks_in_project = await _generate_tasks_list_from_project(
+        list_of_comp_tasks_in_project: List[
+            CompTaskAtDB
+        ] = await _generate_tasks_list_from_project(
             project, director_client, published_nodes
         )
         # get current tasks
@@ -189,6 +192,7 @@ async def _sequentially_upsert_tasks_from_project(
         # insert or update the remaining tasks
         # NOTE: comp_tasks DB only trigger a notification to the webserver if an UPDATE on comp_tasks.outputs or comp_tasks.state is done
         # NOTE: an exception to this is when a frontend service changes its output since there is no node_ports, the UPDATE must be done here.
+        inserted_comp_tasks_db: List[CompTaskAtDB] = []
         for comp_task_db in list_of_comp_tasks_in_project:
 
             insert_stmt = insert(comp_tasks).values(
@@ -203,8 +207,11 @@ async def _sequentially_upsert_tasks_from_project(
                 set_=comp_task_db.dict(
                     by_alias=True, exclude_unset=True, exclude=exclusion_rule
                 ),
-            )
-            await self.connection.execute(on_update_stmt)
+            ).returning(literal_column("*"))
+            result = await self.connection.execute(on_update_stmt)
+            row: RowProxy = await result.fetchone()
+            inserted_comp_tasks_db.append(CompTaskAtDB.from_orm(row))
+        return inserted_comp_tasks_db
 
     @log_decorator(logger=logger)
     async def upsert_tasks_from_project(
@@ -212,7 +219,7 @@ async def upsert_tasks_from_project(
         project: ProjectAtDB,
         director_client: DirectorV0Client,
         published_nodes: List[NodeID],
-    ) -> None:
+    ) -> List[CompTaskAtDB]:
 
         # only used by the decorator on the "_sequentially_upsert_tasks_from_project"
         str_project_uuid: str = str(project.uuid)
@@ -223,7 +230,7 @@ async def upsert_tasks_from_project(
         # If we need to scale this service or the same comp_task entry is used in
         # a different service an implementation of "therun_sequentially_in_context"
         # based on redis queues needs to be put in place.
-        await self._sequentially_upsert_tasks_from_project(
+        return await self._sequentially_upsert_tasks_from_project(
             project=project,
             director_client=director_client,
             published_nodes=published_nodes,
diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py
index 125b4a8c7f9..57b024a9e2a 100644
--- a/services/director-v2/tests/integration/test_computation_api.py
+++ b/services/director-v2/tests/integration/test_computation_api.py
@@ -347,6 +347,7 @@ def test_start_empty_computation(
                     1: {
                         "modified": True,
                         "dependencies": [],
+                        "currentStatus": RunningState.PUBLISHED,
                     },
                     2: {
                         "modified": True,
@@ -365,6 +366,7 @@ def test_start_empty_computation(
                     1: {
                         "modified": False,
                         "dependencies": [],
+                        "currentStatus": RunningState.SUCCESS,
                     },
                     2: {
                         "modified": True,
@@ -390,36 +392,44 @@ def test_start_empty_computation(
                     1: {
                         "modified": True,
                         "dependencies": [],
+                        "currentStatus": RunningState.PUBLISHED,
                     },
                     2: {
                         "modified": True,
                         "dependencies": [1],
+                        "currentStatus": RunningState.PUBLISHED,
                     },
                     3: {
                         "modified": True,
                         "dependencies": [],
+                        "currentStatus": RunningState.PUBLISHED,
                     },
                     4: {
                         "modified": True,
                         "dependencies": [2, 3],
+                        "currentStatus": RunningState.PUBLISHED,
                     },
                 },
                 exp_node_states_after_run={
                     1: {
                         "modified": False,
                         "dependencies": [],
+                        "currentStatus": RunningState.SUCCESS,
                     },
                     2: {
                         "modified": False,
                         "dependencies": [],
+                        "currentStatus": RunningState.SUCCESS,
                     },
                     3: {
                         "modified": False,
                         "dependencies": [],
+                        "currentStatus": RunningState.SUCCESS,
                     },
                     4: {
                         "modified": False,
                         "dependencies": [],
+                        "currentStatus": RunningState.SUCCESS,
                     },
                 },
             ),
@@ -457,6 +467,7 @@ def _convert_to_pipeline_details(
                 dependencies={
                     workbench_node_uuids[dep_n] for dep_n in s["dependencies"]
                 },
+                currentStatus=s.get("currentStatus", RunningState.NOT_STARTED),
             )
             for n, s in exp_node_states.items()
         }

From 664dfc3f669adcde5e317b7c10c211622ecc24f2 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 17:30:38 +0100
Subject: [PATCH 190/200] fix remaining director-v2 tests

---
 .../tests/integration/test_computation_api.py        |  1 +
 .../fake_workbench_computational_node_states.json    | 12 ++++++++----
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py
index 57b024a9e2a..35fb9da5b43 100644
--- a/services/director-v2/tests/integration/test_computation_api.py
+++ b/services/director-v2/tests/integration/test_computation_api.py
@@ -280,6 +280,7 @@ def fake_workbench_computational_pipeline_details_completed(
     for node_state in completed_pipeline_details.node_states.values():
         node_state.modified = False
         node_state.dependencies = set()
+        node_state.current_status = RunningState.SUCCESS
     return completed_pipeline_details
 
 
diff --git a/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json b/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json
index 65e147c8d41..d8ed46e44f7 100644
--- a/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json
+++ b/services/director-v2/tests/mocks/fake_workbench_computational_node_states.json
@@ -1,23 +1,27 @@
 {
   "3a710d8b-565c-5f46-870b-b45ebe195fc7": {
     "modified": true,
-    "dependencies": []
+    "dependencies": [],
+    "currentStatus": "PUBLISHED"
   },
   "e1e2ea96-ce8f-5abc-8712-b8ed312a782c": {
     "modified": true,
-    "dependencies": []
+    "dependencies": [],
+    "currentStatus": "PUBLISHED"
   },
   "415fefd1-d08b-53c1-adb0-16bed3a687ef": {
     "modified": true,
     "dependencies": [
       "3a710d8b-565c-5f46-870b-b45ebe195fc7"
-    ]
+    ],
+    "currentStatus": "PUBLISHED"
   },
   "6ede1209-b459-5735-91fc-761aa584808d": {
     "modified": true,
     "dependencies": [
       "e1e2ea96-ce8f-5abc-8712-b8ed312a782c",
       "415fefd1-d08b-53c1-adb0-16bed3a687ef"
-    ]
+    ],
+    "currentStatus": "PUBLISHED"
   }
 }

From 29f9c04cfbd50525442fa64f33b37de05c73c218 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 18:20:13 +0100
Subject: [PATCH 191/200] ensure state is set to published when a node is
 published for execution

---
 .../modules/db/repositories/comp_tasks.py                     | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
index 494c81fb414..8cb1823ee52 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
@@ -199,7 +199,9 @@ async def _sequentially_upsert_tasks_from_project(
                 **comp_task_db.dict(by_alias=True, exclude_unset=True)
             )
 
-            exclusion_rule = {"state"}
+            exclusion_rule = (
+                {"state"} if str(comp_task_db.node_id) not in published_nodes else set()
+            )
             if to_node_class(comp_task_db.image.name) != NodeClass.FRONTEND:
                 exclusion_rule.add("outputs")
             on_update_stmt = insert_stmt.on_conflict_do_update(

From af3f6a9f92631622ace65a70da966a9ab2b2bbd4 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 18:21:02 +0100
Subject: [PATCH 192/200] correctly check when a pipeline is not yet started

---
 .../tests/integration/test_computation_api.py  | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py
index 35fb9da5b43..7589e73f15a 100644
--- a/services/director-v2/tests/integration/test_computation_api.py
+++ b/services/director-v2/tests/integration/test_computation_api.py
@@ -284,6 +284,17 @@ def fake_workbench_computational_pipeline_details_completed(
     return completed_pipeline_details
 
 
+@pytest.fixture(scope="session")
+def fake_workbench_computational_pipeline_details_not_started(
+    fake_workbench_computational_pipeline_details: PipelineDetails,
+) -> PipelineDetails:
+    completed_pipeline_details = deepcopy(fake_workbench_computational_pipeline_details)
+    for node_state in completed_pipeline_details.node_states.values():
+        node_state.modified = True
+        node_state.current_status = RunningState.NOT_STARTED
+    return completed_pipeline_details
+
+
 # TESTS ---------------------------------------
 
 
@@ -723,6 +734,7 @@ def test_update_and_delete_computation(
     user_id: PositiveInt,
     project: Callable,
     fake_workbench_without_outputs: Dict[str, Any],
+    fake_workbench_computational_pipeline_details_not_started: PipelineDetails,
     fake_workbench_computational_pipeline_details: PipelineDetails,
 ):
     sleepers_project = project(workbench=fake_workbench_without_outputs)
@@ -742,7 +754,7 @@ def test_update_and_delete_computation(
         task_out,
         project=sleepers_project,
         exp_task_state=RunningState.NOT_STARTED,
-        exp_pipeline_details=fake_workbench_computational_pipeline_details,
+        exp_pipeline_details=fake_workbench_computational_pipeline_details_not_started,
     )
 
     # update the pipeline
@@ -761,7 +773,7 @@ def test_update_and_delete_computation(
         task_out,
         project=sleepers_project,
         exp_task_state=RunningState.NOT_STARTED,
-        exp_pipeline_details=fake_workbench_computational_pipeline_details,
+        exp_pipeline_details=fake_workbench_computational_pipeline_details_not_started,
     )
 
     # update the pipeline
@@ -780,7 +792,7 @@ def test_update_and_delete_computation(
         task_out,
         project=sleepers_project,
         exp_task_state=RunningState.NOT_STARTED,
-        exp_pipeline_details=fake_workbench_computational_pipeline_details,
+        exp_pipeline_details=fake_workbench_computational_pipeline_details_not_started,
     )
 
     # start it now

From ed34535521c8caa7b020ff1d00cdab81bfe6a635 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Tue, 23 Feb 2021 18:51:10 +0100
Subject: [PATCH 193/200] test expected states when running normally or forcing

---
 .../tests/integration/test_computation_api.py | 24 +++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/services/director-v2/tests/integration/test_computation_api.py b/services/director-v2/tests/integration/test_computation_api.py
index 7589e73f15a..1c13ec0b6d2 100644
--- a/services/director-v2/tests/integration/test_computation_api.py
+++ b/services/director-v2/tests/integration/test_computation_api.py
@@ -519,7 +519,7 @@ def _convert_to_pipeline_details(
     task_out = _assert_pipeline_status(
         client, task_out.url, user_id, sleepers_project.uuid
     )
-    expected_pipeline_details = _convert_to_pipeline_details(
+    expected_pipeline_details_after_run = _convert_to_pipeline_details(
         sleepers_project, exp_pipeline_adj_list, exp_node_states_after_run
     )
     _assert_computation_task_out_obj(
@@ -527,7 +527,7 @@ def _convert_to_pipeline_details(
         task_out,
         project=sleepers_project,
         exp_task_state=RunningState.SUCCESS,
-        exp_pipeline_details=expected_pipeline_details,
+        exp_pipeline_details=expected_pipeline_details_after_run,
     )
 
     # run it a second time. the tasks are all up-to-date, nothing should be run
@@ -548,6 +548,12 @@ def _convert_to_pipeline_details(
     )
 
     # force run it this time.
+    # the task are up-to-date but we force run them
+    expected_pipeline_details_forced = deepcopy(expected_pipeline_details_after_run)
+    for node_id, node_data in expected_pipeline_details_forced.node_states.items():
+        node_data.current_status = expected_pipeline_details.node_states[
+            node_id
+        ].current_status
     response = _create_pipeline(
         client,
         project=sleepers_project,
@@ -568,7 +574,7 @@ def _convert_to_pipeline_details(
         task_out,
         project=sleepers_project,
         exp_task_state=RunningState.PUBLISHED,
-        exp_pipeline_details=expected_pipeline_details,
+        exp_pipeline_details=expected_pipeline_details_forced,
     )
 
     # now wait for the computation to finish
@@ -631,6 +637,16 @@ def test_run_computation(
     )
 
     # now force run again
+    # the task are up-to-date but we force run them
+    expected_pipeline_details_forced = deepcopy(
+        fake_workbench_computational_pipeline_details_completed
+    )
+    for node_id, node_data in expected_pipeline_details_forced.node_states.items():
+        node_data.current_status = (
+            fake_workbench_computational_pipeline_details.node_states[
+                node_id
+            ].current_status
+        )
     response = _create_pipeline(
         client,
         project=sleepers_project,
@@ -646,7 +662,7 @@ def test_run_computation(
         task_out,
         project=sleepers_project,
         exp_task_state=RunningState.PUBLISHED,
-        exp_pipeline_details=fake_workbench_computational_pipeline_details_completed,  # NOTE: here the pipeline already ran so its states are different
+        exp_pipeline_details=expected_pipeline_details_forced,  # NOTE: here the pipeline already ran so its states are different
     )
 
     # wait for the computation to finish

From 6e200664343ab116f97a696a378d79b4694fd9c5 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Wed, 24 Feb 2021 08:48:06 +0100
Subject: [PATCH 194/200] use project state to initialize comp_tasks

---
 .../modules/db/repositories/comp_tasks.py              | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
index 8cb1823ee52..8173cd8a303 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
@@ -100,6 +100,10 @@ async def _generate_tasks_list_from_project(
             requires_mpi=requires_mpi,
         )
 
+        task_state = node.state.current_status
+        if node_id in published_nodes and node_class == NodeClass.COMPUTATIONAL:
+            task_state = RunningState.PUBLISHED
+
         task_db = CompTaskAtDB(
             project_id=project.uuid,
             node_id=node_id,
@@ -108,11 +112,7 @@ async def _generate_tasks_list_from_project(
             outputs=node.outputs,
             image=image,
             submit=datetime.utcnow(),
-            state=(
-                RunningState.PUBLISHED
-                if node_id in published_nodes and node_class == NodeClass.COMPUTATIONAL
-                else RunningState.NOT_STARTED
-            ),
+            state=task_state,
             internal_id=internal_id,
             node_class=node_class,
         )

From a96aa05b588f5d548a23da81d5a030bb9e368ba7 Mon Sep 17 00:00:00 2001
From: sanderegg <35365065+sanderegg@users.noreply.github.com>
Date: Wed, 24 Feb 2021 10:19:16 +0100
Subject: [PATCH 195/200] correctly convert to DB type of state type

---
 .../models/domains/comp_tasks.py                 | 16 ++++++++++++++--
 .../modules/db/repositories/comp_tasks.py        |  8 ++------
 .../src/simcore_service_director_v2/utils/db.py  |  2 ++
 3 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/services/director-v2/src/simcore_service_director_v2/models/domains/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/models/domains/comp_tasks.py
index f88273d4848..4c47fea3c84 100644
--- a/services/director-v2/src/simcore_service_director_v2/models/domains/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/models/domains/comp_tasks.py
@@ -1,5 +1,5 @@
 from datetime import datetime
-from typing import Optional
+from typing import Any, Dict, Optional
 
 from models_library.basic_regex import VERSION_RE
 from models_library.projects import ProjectID
@@ -10,7 +10,7 @@
 from pydantic.types import PositiveInt
 from simcore_postgres_database.models.comp_tasks import NodeClass, StateType
 
-from ...utils.db import DB_TO_RUNNING_STATE
+from ...utils.db import DB_TO_RUNNING_STATE, RUNNING_STATE_TO_DB
 
 
 class Image(BaseModel):
@@ -54,8 +54,20 @@ class CompTaskAtDB(BaseModel):
     def convert_state_if_needed(cls, v):
         if isinstance(v, StateType):
             return RunningState(DB_TO_RUNNING_STATE[StateType(v)])
+        if isinstance(v, str):
+            try:
+                state_type = StateType(v)
+                return RunningState(DB_TO_RUNNING_STATE[state_type])
+            except ValueError:
+                pass
         return v
 
+    def to_db_model(self, **exclusion_rules) -> Dict[str, Any]:
+        comp_task_dict = self.dict(by_alias=True, exclude_unset=True, **exclusion_rules)
+        if "state" in comp_task_dict:
+            comp_task_dict["state"] = RUNNING_STATE_TO_DB[comp_task_dict["state"]].value
+        return comp_task_dict
+
     class Config:
         extra = Extra.forbid
         orm_mode = True
diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
index 8173cd8a303..ca036dece0d 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
@@ -195,9 +195,7 @@ async def _sequentially_upsert_tasks_from_project(
         inserted_comp_tasks_db: List[CompTaskAtDB] = []
         for comp_task_db in list_of_comp_tasks_in_project:
 
-            insert_stmt = insert(comp_tasks).values(
-                **comp_task_db.dict(by_alias=True, exclude_unset=True)
-            )
+            insert_stmt = insert(comp_tasks).values(**comp_task_db.to_db_model())
 
             exclusion_rule = (
                 {"state"} if str(comp_task_db.node_id) not in published_nodes else set()
@@ -206,9 +204,7 @@ async def _sequentially_upsert_tasks_from_project(
                 exclusion_rule.add("outputs")
             on_update_stmt = insert_stmt.on_conflict_do_update(
                 index_elements=[comp_tasks.c.project_id, comp_tasks.c.node_id],
-                set_=comp_task_db.dict(
-                    by_alias=True, exclude_unset=True, exclude=exclusion_rule
-                ),
+                set_=comp_task_db.to_db_model(exclude=exclusion_rule),
             ).returning(literal_column("*"))
             result = await self.connection.execute(on_update_stmt)
             row: RowProxy = await result.fetchone()
diff --git a/services/director-v2/src/simcore_service_director_v2/utils/db.py b/services/director-v2/src/simcore_service_director_v2/utils/db.py
index a72501fa377..eb0f1aaf139 100644
--- a/services/director-v2/src/simcore_service_director_v2/utils/db.py
+++ b/services/director-v2/src/simcore_service_director_v2/utils/db.py
@@ -10,3 +10,5 @@
     StateType.RUNNING: RunningState.STARTED,
     StateType.ABORTED: RunningState.ABORTED,
 }
+
+RUNNING_STATE_TO_DB = {v: k for k, v in DB_TO_RUNNING_STATE.items()}

From c006a64c0ea92417198a43a7432ac2d2761709fb Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Wed, 24 Feb 2021 12:33:50 +0100
Subject: [PATCH 196/200] modified can be null

---
 .../source/class/osparc/component/workbench/NodeUI.js       | 6 +++---
 services/web/client/source/class/osparc/data/model/Node.js  | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js
index 5174502a61c..78d29c6cab6 100644
--- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js
+++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js
@@ -230,10 +230,10 @@ qx.Class.define("osparc.component.workbench.NodeUI", {
       } else {
         this.getNode().getStatus().bind("modified", portLabel, "textColor", {
           converter: modified => {
-            if (modified !== null) {
-              return osparc.utils.StatusUI.getColor(modified ? "failed" : "ready");
+            if (modified === null) {
+              return osparc.utils.StatusUI.getColor(null);
             }
-            return osparc.utils.StatusUI.getColor(null);
+            return osparc.utils.StatusUI.getColor(modified ? "failed" : "ready");
           }
         });
       }
diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js
index 75b7965363f..da20e37fde6 100644
--- a/services/web/client/source/class/osparc/data/model/Node.js
+++ b/services/web/client/source/class/osparc/data/model/Node.js
@@ -387,7 +387,7 @@ qx.Class.define("osparc.data.model.Node", {
           this.getStatus().setRunning(nodeData.state.currentStatus);
         }
         if ("modified" in nodeData.state) {
-          this.getStatus().setModified(nodeData.state.modified || this.getStatus().hasDependencies());
+          this.getStatus().setModified(nodeData.state.modified);
         }
       }
     },

From 0a66f0a8a6fcfc1105b7caac96688b1914af73c7 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Wed, 24 Feb 2021 13:44:34 +0100
Subject: [PATCH 197/200] modified can be null

---
 .../class/osparc/component/widget/NodePorts.js     | 14 ++++++++++++--
 .../class/osparc/component/workbench/NodeUI.js     |  4 ++--
 .../osparc/navigation/BreadcrumbsSlideShow.js      |  2 +-
 3 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/services/web/client/source/class/osparc/component/widget/NodePorts.js b/services/web/client/source/class/osparc/component/widget/NodePorts.js
index 1afcd43de72..3518e512f87 100644
--- a/services/web/client/source/class/osparc/component/widget/NodePorts.js
+++ b/services/web/client/source/class/osparc/component/widget/NodePorts.js
@@ -50,10 +50,20 @@ qx.Class.define("osparc.component.widget.NodePorts", {
     node.bind("label", this, "title");
 
     node.getStatus().bind("modified", this.getChildControl("icon"), "source", {
-      converter: modified => osparc.utils.StatusUI.getIconSource(modified === true ? "modified" : "up-to-date")
+      converter: modified => {
+        if (modified === null) {
+          return osparc.utils.StatusUI.getIconSource();
+        }
+        return osparc.utils.StatusUI.getIconSource(modified === true ? "modified" : "up-to-date");
+      }
     }, this);
     node.getStatus().bind("modified", this.getChildControl("icon"), "textColor", {
-      converter: modified => osparc.utils.StatusUI.getColor(modified === true ? "modified" : "up-to-date")
+      converter: modified => {
+        if (modified === null) {
+          return osparc.utils.StatusUI.getColor();
+        }
+        return osparc.utils.StatusUI.getColor(modified === true ? "modified" : "up-to-date");
+      }
     }, this);
     node.getStatus().bind("modified", this.getChildControl("icon"), "toolTipText", {
       converter: modified => modified === true ? this.tr("Out of date") : ""
diff --git a/services/web/client/source/class/osparc/component/workbench/NodeUI.js b/services/web/client/source/class/osparc/component/workbench/NodeUI.js
index 78d29c6cab6..69323220986 100644
--- a/services/web/client/source/class/osparc/component/workbench/NodeUI.js
+++ b/services/web/client/source/class/osparc/component/workbench/NodeUI.js
@@ -224,14 +224,14 @@ qx.Class.define("osparc.component.workbench.NodeUI", {
             if (dependencies !== null) {
               return osparc.utils.StatusUI.getColor(dependencies.length ? "failed" : "ready");
             }
-            return osparc.utils.StatusUI.getColor(null);
+            return osparc.utils.StatusUI.getColor();
           }
         });
       } else {
         this.getNode().getStatus().bind("modified", portLabel, "textColor", {
           converter: modified => {
             if (modified === null) {
-              return osparc.utils.StatusUI.getColor(null);
+              return osparc.utils.StatusUI.getColor();
             }
             return osparc.utils.StatusUI.getColor(modified ? "failed" : "ready");
           }
diff --git a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
index 90e96df9275..a4f00886c57 100644
--- a/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
+++ b/services/web/client/source/class/osparc/navigation/BreadcrumbsSlideShow.js
@@ -63,7 +63,7 @@ qx.Class.define("osparc.navigation.BreadcrumbsSlideShow", {
             const lastCharacter = label.slice(-1);
             if (modified === true && lastCharacter !== "*") {
               return label + "*"; // add star suffix
-            } else if (modified === false && lastCharacter === "*") {
+            } else if ((modified === false || modified === null) && lastCharacter === "*") {
               return label.slice(0, -1); // remove star suffix
             }
             return label;

From cf65a42d5ce4a05045feb93e9770595f37e7438b Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Wed, 24 Feb 2021 13:58:30 +0100
Subject: [PATCH 198/200] hasOutputs prop added to NodeStatus

---
 services/web/client/source/class/osparc/data/model/Node.js  | 4 +++-
 .../web/client/source/class/osparc/data/model/NodeStatus.js | 6 ++++++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js
index da20e37fde6..0f81c87ced6 100644
--- a/services/web/client/source/class/osparc/data/model/Node.js
+++ b/services/web/client/source/class/osparc/data/model/Node.js
@@ -387,7 +387,7 @@ qx.Class.define("osparc.data.model.Node", {
           this.getStatus().setRunning(nodeData.state.currentStatus);
         }
         if ("modified" in nodeData.state) {
-          this.getStatus().setModified(nodeData.state.modified);
+          this.getStatus().setModified((nodeData.state.modified || this.getStatus().hasDependencies()) && this.getNodeStatus().getHasOutputs());
         }
       }
     },
@@ -649,6 +649,8 @@ qx.Class.define("osparc.data.model.Node", {
             this.getOutputs()[outputKey]["value"] = "";
           }
         }
+        this.getNodeStatus().setHasOutputs(true);
+
         this.fireDataEvent("changeOutputs", this.getOutputs());
       }
     },
diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js
index 944e737f793..356a89786bc 100644
--- a/services/web/client/source/class/osparc/data/model/NodeStatus.js
+++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js
@@ -62,6 +62,12 @@ qx.Class.define("osparc.data.model.NodeStatus", {
       init: null,
       event: "changeModified",
       apply: "__applyModified"
+    },
+
+    hasOutputs: {
+      check: "Boolean",
+      init: false,
+      apply: "__applyModified"
     }
   },
 

From d93f5f97caf0df9bbd084b8e37ae1dd2b5f0157e Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Wed, 24 Feb 2021 13:59:54 +0100
Subject: [PATCH 199/200] Update Node.js

---
 services/web/client/source/class/osparc/data/model/Node.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js
index 0f81c87ced6..ef694c2c633 100644
--- a/services/web/client/source/class/osparc/data/model/Node.js
+++ b/services/web/client/source/class/osparc/data/model/Node.js
@@ -387,7 +387,7 @@ qx.Class.define("osparc.data.model.Node", {
           this.getStatus().setRunning(nodeData.state.currentStatus);
         }
         if ("modified" in nodeData.state) {
-          this.getStatus().setModified((nodeData.state.modified || this.getStatus().hasDependencies()) && this.getNodeStatus().getHasOutputs());
+          this.getStatus().setModified((nodeData.state.modified || this.getNodeStatus().hasDependencies()) && this.getNodeStatus().getHasOutputs());
         }
       }
     },

From 579d024351c9881680de59b2c441d73dfb631388 Mon Sep 17 00:00:00 2001
From: odeimaiz 
Date: Wed, 24 Feb 2021 14:10:03 +0100
Subject: [PATCH 200/200] modified null if there are no outputs

---
 .../web/client/source/class/osparc/data/model/Node.js     | 8 ++++++--
 .../client/source/class/osparc/data/model/NodeStatus.js   | 8 +++++---
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/services/web/client/source/class/osparc/data/model/Node.js b/services/web/client/source/class/osparc/data/model/Node.js
index ef694c2c633..e122ac4e53f 100644
--- a/services/web/client/source/class/osparc/data/model/Node.js
+++ b/services/web/client/source/class/osparc/data/model/Node.js
@@ -387,7 +387,11 @@ qx.Class.define("osparc.data.model.Node", {
           this.getStatus().setRunning(nodeData.state.currentStatus);
         }
         if ("modified" in nodeData.state) {
-          this.getStatus().setModified((nodeData.state.modified || this.getNodeStatus().hasDependencies()) && this.getNodeStatus().getHasOutputs());
+          if (this.getStatus().getHasOutputs()) {
+            this.getStatus().setModified(nodeData.state.modified || this.getStatus().hasDependencies());
+          } else {
+            this.getStatus().setModified(null);
+          }
         }
       }
     },
@@ -649,7 +653,7 @@ qx.Class.define("osparc.data.model.Node", {
             this.getOutputs()[outputKey]["value"] = "";
           }
         }
-        this.getNodeStatus().setHasOutputs(true);
+        this.getStatus().setHasOutputs(true);
 
         this.fireDataEvent("changeOutputs", this.getOutputs());
       }
diff --git a/services/web/client/source/class/osparc/data/model/NodeStatus.js b/services/web/client/source/class/osparc/data/model/NodeStatus.js
index 356a89786bc..8beb877109d 100644
--- a/services/web/client/source/class/osparc/data/model/NodeStatus.js
+++ b/services/web/client/source/class/osparc/data/model/NodeStatus.js
@@ -81,12 +81,14 @@ qx.Class.define("osparc.data.model.NodeStatus", {
     },
 
     __applyDependencies: function() {
-      this.setModified(this.hasDependencies());
+      this.__applyModified(this.hasDependencies());
     },
 
     __applyModified: function(modified) {
-      if (modified === false) {
-        this.setModified(this.hasDependencies());
+      if (this.getHasOutputs()) {
+        this.setModified(modified || this.hasDependencies());
+      } else {
+        this.setModified(null);
       }
     }
   }