From f8d121cb07ef561580c6aeda0ee15ffc4c4fc1bb Mon Sep 17 00:00:00 2001 From: Allan Hvam Date: Thu, 30 May 2024 11:17:19 +0200 Subject: [PATCH] feat: remove samples from workspace --- .../app/analysis/data-table/data-table.tsx | 8 ++- app/src/app/workspaces/workspace.tsx | 70 ++++++++++++++----- .../workspaces/workspaces-query-configs.ts | 17 +++++ app/src/sap-client/apis/WorkspacesApi.ts | 55 +++++++++++++++ openapi_specs/SOFI/SOFI.yaml | 24 +++++++ web/openapi_specs/SOFI/SOFI.yaml | 24 +++++++ .../controllers/workspaces_controller.py | 14 ++++ web/src/SAP/generated/openapi/openapi.yaml | 27 +++++++ .../test/test_workspaces_controller.py | 15 ++++ .../src/controllers/WorkspacesController.py | 5 ++ web/src/SAP/src/repositories/workspaces.py | 10 +++ 11 files changed, 250 insertions(+), 19 deletions(-) diff --git a/app/src/app/analysis/data-table/data-table.tsx b/app/src/app/analysis/data-table/data-table.tsx index 2047b4c3..99a092f2 100644 --- a/app/src/app/analysis/data-table/data-table.tsx +++ b/app/src/app/analysis/data-table/data-table.tsx @@ -69,7 +69,9 @@ type DataTableProps = { renderCellControl: ( rowId: string, columnId: string, - value: string + value: string, + columnIndex: number, + original: T ) => JSX.Element; }; @@ -494,7 +496,9 @@ function DataTable(props: DataTableProps) { {renderCellControl( rowId, columnId, - rows[rowIndex - 1].original[columnId] + rows[rowIndex - 1].original[columnId], + columnIndex, + rows[rowIndex - 1].original )} diff --git a/app/src/app/workspaces/workspace.tsx b/app/src/app/workspaces/workspace.tsx index 48528fe1..bbc83efa 100644 --- a/app/src/app/workspaces/workspace.tsx +++ b/app/src/app/workspaces/workspace.tsx @@ -1,9 +1,12 @@ -import React from "react"; -import { Box, Heading, Spinner } from "@chakra-ui/react"; +import React, { useCallback } from "react"; +import { Box, Heading, IconButton, Spinner } from "@chakra-ui/react"; import { useTranslation } from "react-i18next"; import HalfHolyGrailLayout from "layouts/half-holy-grail"; -import { useRequest } from "redux-query-react"; -import { getWorkspace } from "./workspaces-query-configs"; +import { useMutation, useRequest } from "redux-query-react"; +import { + getWorkspace, + removeWorkspaceSample, +} from "./workspaces-query-configs"; import { useSelector } from "react-redux"; import { RootState } from "app/root-reducer"; import { @@ -20,6 +23,7 @@ import { Column } from "react-table"; import { UserDefinedViewInternal } from "models/user-defined-view-internal"; import { Loading } from "loading"; import { AnalysisViewSelector } from "app/analysis/view-selector/analysis-view-selector"; +import { DeleteIcon } from "@chakra-ui/icons"; type Props = { id: string; @@ -64,22 +68,40 @@ export function Workspace(props: Props) { [columnConfigs, t] ); + const deleteWorkspaceSampleCallback = useMutation((sampleId: string) => + removeWorkspaceSample({ sampleId, workspaceId: id }) + )[1]; + + const onRemoveSample = useCallback( + (sampleId) => { + const ok = confirm( + t("Are you sure you want to remove the sequence from the workspace?") + ); + if (ok) { + deleteWorkspaceSampleCallback(sampleId); + } + }, + [deleteWorkspaceSampleCallback, t] + ); + const renderCellControl = React.useCallback( - (rowId: string, columnId: string, value) => { + ( + rowId: string, + columnId: string, + value, + columnIndex: number, + original: AnalysisResult + ) => { if ( - value !== 0 && - value !== false && - !value && - !columnConfigs[columnId].editable + value === undefined || + value === null || + String(value) === "Invalid Date" ) { - return
; + value = ""; } let v = `${value}`; - if (v === "Invalid Date") { - return
; - } - // any other dates - else if (value instanceof Date) { + + if (value instanceof Date) { // Fancy libraries could be used, but this will do the trick just fine v = value.toISOString().split("T")[0]; } else if ( @@ -119,9 +141,23 @@ export function Workspace(props: Props) { } } - return
{`${v}`}
; + return ( +
+ {columnIndex === 0 ? ( + onRemoveSample(original.id)} + aria-label="Remove" + icon={} + ml="1" + /> + ) : null} + {`${v}`} +
+ ); }, - [columnConfigs] + [onRemoveSample] ); if (!columnLoadState.isFinished) { diff --git a/app/src/app/workspaces/workspaces-query-configs.ts b/app/src/app/workspaces/workspaces-query-configs.ts index a7fa4cba..8992a131 100644 --- a/app/src/app/workspaces/workspaces-query-configs.ts +++ b/app/src/app/workspaces/workspaces-query-configs.ts @@ -5,8 +5,10 @@ import { createWorkspace as createWorkspaceApi, getWorkspace as getWorkspaceApi, postWorkspace as postWorkspaceApi, + deleteWorkspaceSample as deleteWorkspaceSampleApi, DeleteWorkspaceRequest, PostWorkspaceRequest, + DeleteWorkspaceSampleRequest, } from "sap-client"; import { CreateWorkspace, WorkspaceInfo } from "sap-client/models"; import { getUrl } from "service"; @@ -66,6 +68,21 @@ export const deleteWorkspace = (params: DeleteWorkspaceRequest) => { return base; }; +export const removeWorkspaceSample = (params: DeleteWorkspaceSampleRequest) => { + const base = deleteWorkspaceSampleApi(params); + base.url = getUrl(base.url); + base.update = { + workspace: (oldValue) => { + return { + ...oldValue, + samples: oldValue.samples.filter((s) => s.id !== params.sampleId), + }; + }, + }; + base.force = true; + return base; +}; + export const createWorkspace = (params: CreateWorkspace) => { const base = createWorkspaceApi({ createWorkspace: params }); base.url = getUrl(base.url); diff --git a/app/src/sap-client/apis/WorkspacesApi.ts b/app/src/sap-client/apis/WorkspacesApi.ts index 65415eb9..6274cd45 100644 --- a/app/src/sap-client/apis/WorkspacesApi.ts +++ b/app/src/sap-client/apis/WorkspacesApi.ts @@ -37,6 +37,11 @@ export interface DeleteWorkspaceRequest { workspaceId: string; } +export interface DeleteWorkspaceSampleRequest { + workspaceId: string; + sampleId: string; +} + export interface GetWorkspaceRequest { workspaceId: string; } @@ -135,6 +140,56 @@ export function deleteWorkspace(requestParameters: DeleteWorkspaceRequest, re return deleteWorkspaceRaw(requestParameters, requestConfig); } +/** + * Delete sample from workspace + */ +function deleteWorkspaceSampleRaw(requestParameters: DeleteWorkspaceSampleRequest, requestConfig: runtime.TypedQueryConfig = {}): QueryConfig { + if (requestParameters.workspaceId === null || requestParameters.workspaceId === undefined) { + throw new runtime.RequiredError('workspaceId','Required parameter requestParameters.workspaceId was null or undefined when calling deleteWorkspaceSample.'); + } + + if (requestParameters.sampleId === null || requestParameters.sampleId === undefined) { + throw new runtime.RequiredError('sampleId','Required parameter requestParameters.sampleId was null or undefined when calling deleteWorkspaceSample.'); + } + + let queryParameters = null; + + + const headerParameters : runtime.HttpHeaders = {}; + + + const { meta = {} } = requestConfig; + + meta.authType = ['bearer']; + const config: QueryConfig = { + url: `${runtime.Configuration.basePath}/workspace/{workspace_id}/{sample_id}`.replace(`{${"workspace_id"}}`, encodeURIComponent(String(requestParameters.workspaceId))).replace(`{${"sample_id"}}`, encodeURIComponent(String(requestParameters.sampleId))), + meta, + update: requestConfig.update, + queryKey: requestConfig.queryKey, + optimisticUpdate: requestConfig.optimisticUpdate, + force: requestConfig.force, + rollback: requestConfig.rollback, + options: { + method: 'DELETE', + headers: headerParameters, + }, + body: queryParameters, + }; + + const { transform: requestTransform } = requestConfig; + if (requestTransform) { + } + + return config; +} + +/** +* Delete sample from workspace +*/ +export function deleteWorkspaceSample(requestParameters: DeleteWorkspaceSampleRequest, requestConfig?: runtime.TypedQueryConfig): QueryConfig { + return deleteWorkspaceSampleRaw(requestParameters, requestConfig); +} + /** * Get an existing workspace */ diff --git a/openapi_specs/SOFI/SOFI.yaml b/openapi_specs/SOFI/SOFI.yaml index 55f74161..21e5e842 100644 --- a/openapi_specs/SOFI/SOFI.yaml +++ b/openapi_specs/SOFI/SOFI.yaml @@ -43,6 +43,30 @@ paths: tags: - workspaces x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller + /workspace/{workspace_id}/{sample_id}: + delete: + description: Delete sample from workspace + operationId: delete_workspace_sample + tags: + - workspaces + x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller + parameters: + - name: workspace_id + in: path + required: true + schema: + type: string + description: Id of workspace to modify + - name: sample_id + in: path + required: true + schema: + type: string + description: Id of the sample to remove + responses: + '204': + description: Sample removed from workspace successfully. + /workspaces/{workspace_id}: get: description: Get an existing workspace diff --git a/web/openapi_specs/SOFI/SOFI.yaml b/web/openapi_specs/SOFI/SOFI.yaml index 55f74161..21e5e842 100644 --- a/web/openapi_specs/SOFI/SOFI.yaml +++ b/web/openapi_specs/SOFI/SOFI.yaml @@ -43,6 +43,30 @@ paths: tags: - workspaces x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller + /workspace/{workspace_id}/{sample_id}: + delete: + description: Delete sample from workspace + operationId: delete_workspace_sample + tags: + - workspaces + x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller + parameters: + - name: workspace_id + in: path + required: true + schema: + type: string + description: Id of workspace to modify + - name: sample_id + in: path + required: true + schema: + type: string + description: Id of the sample to remove + responses: + '204': + description: Sample removed from workspace successfully. + /workspaces/{workspace_id}: get: description: Get an existing workspace diff --git a/web/src/SAP/generated/controllers/workspaces_controller.py b/web/src/SAP/generated/controllers/workspaces_controller.py index cfd870ea..e6d1c8cc 100644 --- a/web/src/SAP/generated/controllers/workspaces_controller.py +++ b/web/src/SAP/generated/controllers/workspaces_controller.py @@ -31,6 +31,20 @@ def delete_workspace(user, token_info, workspace_id): # noqa: E501 """ return WorkspacesController.delete_workspace(user, token_info, workspace_id) +def delete_workspace_sample(user, token_info, workspace_id, sample_id): # noqa: E501 + """delete_workspace_sample + + Delete sample from workspace # noqa: E501 + + :param workspace_id: Id of workspace to modify + :type workspace_id: str + :param sample_id: Id of the sample to remove + :type sample_id: str + + :rtype: None + """ + return WorkspacesController.delete_workspace_sample(user, token_info, workspace_id, sample_id) + def get_workspace(user, token_info, workspace_id): # noqa: E501 """get_workspace diff --git a/web/src/SAP/generated/openapi/openapi.yaml b/web/src/SAP/generated/openapi/openapi.yaml index 0bc9204d..c577e493 100644 --- a/web/src/SAP/generated/openapi/openapi.yaml +++ b/web/src/SAP/generated/openapi/openapi.yaml @@ -531,6 +531,33 @@ paths: tags: - user x-openapi-router-controller: web.src.SAP.generated.controllers.user_controller + /workspace/{workspace_id}/{sample_id}: + delete: + description: Delete sample from workspace + operationId: delete_workspace_sample + parameters: + - description: Id of workspace to modify + explode: false + in: path + name: workspace_id + required: true + schema: + type: string + style: simple + - description: Id of the sample to remove + explode: false + in: path + name: sample_id + required: true + schema: + type: string + style: simple + responses: + "204": + description: Sample removed from workspace successfully. + tags: + - workspaces + x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller /workspaces: get: description: Gets workspaces diff --git a/web/src/SAP/generated/test/test_workspaces_controller.py b/web/src/SAP/generated/test/test_workspaces_controller.py index aedbb4db..1dee4054 100644 --- a/web/src/SAP/generated/test/test_workspaces_controller.py +++ b/web/src/SAP/generated/test/test_workspaces_controller.py @@ -51,6 +51,21 @@ def test_delete_workspace(self): self.assert200(response, 'Response body is : ' + response.data.decode('utf-8')) + def test_delete_workspace_sample(self): + """Test case for delete_workspace_sample + + + """ + headers = { + 'Authorization': 'Bearer special-key', + } + response = self.client.open( + '/api/workspace/{workspace_id}/{sample_id}'.format(workspace_id='workspace_id_example')sample_id='sample_id_example'), + method='DELETE', + headers=headers) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + def test_get_workspace(self): """Test case for get_workspace diff --git a/web/src/SAP/src/controllers/WorkspacesController.py b/web/src/SAP/src/controllers/WorkspacesController.py index 19c938b4..0a2facbc 100644 --- a/web/src/SAP/src/controllers/WorkspacesController.py +++ b/web/src/SAP/src/controllers/WorkspacesController.py @@ -3,6 +3,7 @@ from flask.json import jsonify from ..repositories.workspaces import get_workspaces as get_workspaces_db from ..repositories.workspaces import delete_workspace as delete_workspace_db +from ..repositories.workspaces import delete_workspace_sample as delete_workspace_sample_db from ..repositories.workspaces import create_workspace as create_workspace_db from ..repositories.workspaces import update_workspace as update_workspace_db from ..repositories.workspaces import get_workspace as get_workspace_db @@ -29,3 +30,7 @@ def post_workspace(user, token_info, workspace_id: str, body): def get_workspace(user, token_info, workspace_id: str): return jsonify(get_workspace_db(user, workspace_id)) + +def delete_workspace_sample(user, token_info, workspace_id, sample_id): + res = delete_workspace_sample_db(user, workspace_id, sample_id) + return None if res.modified_count > 0 else abort(404) diff --git a/web/src/SAP/src/repositories/workspaces.py b/web/src/SAP/src/repositories/workspaces.py index eff3f18f..14f77e8a 100644 --- a/web/src/SAP/src/repositories/workspaces.py +++ b/web/src/SAP/src/repositories/workspaces.py @@ -41,6 +41,16 @@ def delete_workspace(user: str, workspace_id: str): {"_id": ObjectId(workspace_id), "created_by": user} ) +def delete_workspace_sample(user: str, workspace_id: str, sample_id: str): + conn = get_connection() + mydb = conn[DB_NAME] + workspaces = mydb[WORKSPACES_COL_NAME] + + filter = {'created_by': user, '_id': ObjectId(workspace_id)} + update = {"$pull": {"samples": sample_id}} + return workspaces.update_one(filter, update, upsert=True) + + def create_workspace(user: str, workspace: CreateWorkspace): conn = get_connection() db = conn[DB_NAME]