diff --git a/app/src/app/workspaces/clone-workspace.tsx b/app/src/app/workspaces/clone-workspace.tsx
new file mode 100644
index 00000000..1196b030
--- /dev/null
+++ b/app/src/app/workspaces/clone-workspace.tsx
@@ -0,0 +1,57 @@
+import React, { useCallback, useEffect, useState } from "react";
+import { useToast } from "@chakra-ui/react";
+import { useMutation } from "redux-query-react";
+import { useTranslation } from "react-i18next";
+import { cloneWorkspace } from "./workspaces-query-configs";
+import { IconButton } from "@chakra-ui/react";
+import { CopyIcon } from "@chakra-ui/icons";
+
+type Props = {
+ id: string;
+ samples: string[];
+};
+
+export function CloneWorkspace(props: Props) {
+ const { t } = useTranslation();
+ const toast = useToast();
+
+ const [
+ cloneWorkspaceQueryState,
+ cloneWorkspaceMutation,
+ ] = useMutation((name: string, id: string) => cloneWorkspace({ name, id, samples: props.samples }));
+
+ const [needsNotify, setNeedsNotify] = useState(true);
+
+ const cloneWorkspaceCallback = useCallback(() => {
+ const name = prompt("Workspace name");
+ if (name) {
+ setNeedsNotify(true);
+ cloneWorkspaceMutation(name, props.id);
+ }
+ }, [cloneWorkspaceMutation, setNeedsNotify, props.id]);
+
+ useEffect(() => {
+ if (
+ needsNotify &&
+ cloneWorkspaceQueryState.status >= 200 &&
+ cloneWorkspaceQueryState.status < 300 &&
+ !cloneWorkspaceQueryState.isPending
+ ) {
+ toast({
+ title: t("Workspace cloned"),
+ status: "info",
+ duration: 3000,
+ isClosable: true,
+ });
+ setNeedsNotify(false);
+ }
+ }, [t, cloneWorkspaceQueryState, toast, needsNotify, setNeedsNotify]);
+
+ return (
+ }
+ aria-label={`${t("Clone workspace")}`}
+ onClick={cloneWorkspaceCallback}
+ />
+ );
+}
diff --git a/app/src/app/workspaces/workspaces-query-configs.ts b/app/src/app/workspaces/workspaces-query-configs.ts
index 8992a131..218f6464 100644
--- a/app/src/app/workspaces/workspaces-query-configs.ts
+++ b/app/src/app/workspaces/workspaces-query-configs.ts
@@ -9,8 +9,9 @@ import {
DeleteWorkspaceRequest,
PostWorkspaceRequest,
DeleteWorkspaceSampleRequest,
+ cloneWorkspace as cloneWorkspaceApi,
} from "sap-client";
-import { CreateWorkspace, WorkspaceInfo } from "sap-client/models";
+import { CreateWorkspace, WorkspaceInfo, CloneWorkspace } from "sap-client/models";
import { getUrl } from "service";
export type WorkspacesSlice = {
@@ -106,6 +107,30 @@ export const createWorkspace = (params: CreateWorkspace) => {
return base;
};
+export const cloneWorkspace = (params: CloneWorkspace & { samples: string[] }) => {
+ const base = cloneWorkspaceApi({ cloneWorkspace: params });
+ base.url = getUrl(base.url);
+
+ base.transform = (response) => {
+ if (!response.id) {
+ return undefined;
+ }
+ return { workspaces: [{ id: response.id, name: params.name, samples: params.samples }] };
+ };
+
+
+ base.update = {
+ workspaces: (oldValue, newValue) => {
+ if (!newValue) {
+ return oldValue;
+ }
+ return [].concat(...oldValue, ...newValue);
+ },
+ };
+ base.force = true;
+ return base;
+};
+
export const updateWorkspace = (params: PostWorkspaceRequest) => {
const base = postWorkspaceApi(params);
base.url = getUrl(base.url);
diff --git a/app/src/app/workspaces/workspaces.tsx b/app/src/app/workspaces/workspaces.tsx
index 4c30a90a..9284b1a8 100644
--- a/app/src/app/workspaces/workspaces.tsx
+++ b/app/src/app/workspaces/workspaces.tsx
@@ -21,6 +21,7 @@ import type { Workspace } from "sap-client";
import { DeleteWorkspace } from "./delete-workspace";
import { CreateWorkspace } from "./create-workspace";
import { ViewWorkspace } from "./view-workspace";
+import { CloneWorkspace } from "./clone-workspace";
export function Workspaces() {
const [workspacesQueryState] = useRequest(fetchWorkspaces());
@@ -63,6 +64,7 @@ export function Workspaces() {
+
|
diff --git a/app/src/sap-client/apis/WorkspacesApi.ts b/app/src/sap-client/apis/WorkspacesApi.ts
index 6274cd45..7eef771d 100644
--- a/app/src/sap-client/apis/WorkspacesApi.ts
+++ b/app/src/sap-client/apis/WorkspacesApi.ts
@@ -15,6 +15,9 @@
import { HttpMethods, QueryConfig, ResponseBody, ResponseText } from 'redux-query';
import * as runtime from '../runtime';
import {
+ CloneWorkspace,
+ CloneWorkspaceFromJSON,
+ CloneWorkspaceToJSON,
CreateWorkspace,
CreateWorkspaceFromJSON,
CreateWorkspaceToJSON,
@@ -29,6 +32,10 @@ import {
WorkspaceInfoToJSON,
} from '../models';
+export interface CloneWorkspaceRequest {
+ cloneWorkspace?: CloneWorkspace;
+}
+
export interface CreateWorkspaceRequest {
createWorkspace?: CreateWorkspace;
}
@@ -52,6 +59,48 @@ export interface PostWorkspaceRequest {
}
+/**
+ */
+function cloneWorkspaceRaw(requestParameters: CloneWorkspaceRequest, requestConfig: runtime.TypedQueryConfig = {}): QueryConfig {
+ let queryParameters = null;
+
+
+ const headerParameters : runtime.HttpHeaders = {};
+
+ headerParameters['Content-Type'] = 'application/json';
+
+
+ const { meta = {} } = requestConfig;
+
+ meta.authType = ['bearer'];
+ const config: QueryConfig = {
+ url: `${runtime.Configuration.basePath}/workspace/clone`,
+ meta,
+ update: requestConfig.update,
+ queryKey: requestConfig.queryKey,
+ optimisticUpdate: requestConfig.optimisticUpdate,
+ force: requestConfig.force,
+ rollback: requestConfig.rollback,
+ options: {
+ method: 'POST',
+ headers: headerParameters,
+ },
+ body: queryParameters || CloneWorkspaceToJSON(requestParameters.cloneWorkspace),
+ };
+
+ const { transform: requestTransform } = requestConfig;
+ if (requestTransform) {
+ }
+
+ return config;
+}
+
+/**
+*/
+export function cloneWorkspace(requestParameters: CloneWorkspaceRequest, requestConfig?: runtime.TypedQueryConfig): QueryConfig {
+ return cloneWorkspaceRaw(requestParameters, requestConfig);
+}
+
/**
*/
function createWorkspaceRaw(requestParameters: CreateWorkspaceRequest, requestConfig: runtime.TypedQueryConfig = {}): QueryConfig {
diff --git a/app/src/sap-client/models/CloneWorkspace.ts b/app/src/sap-client/models/CloneWorkspace.ts
new file mode 100644
index 00000000..7a23472f
--- /dev/null
+++ b/app/src/sap-client/models/CloneWorkspace.ts
@@ -0,0 +1,52 @@
+// tslint:disable
+/**
+ * SOFI
+ * SOFI Sekvensanalyseplatform
+ *
+ * The version of the OpenAPI document: 0.1.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { exists, mapValues } from '../runtime';
+/**
+ *
+ * @export
+ * @interface CloneWorkspace
+ */
+export interface CloneWorkspace {
+ /**
+ *
+ * @type {string}
+ * @memberof CloneWorkspace
+ */
+ name: string;
+ /**
+ *
+ * @type {string}
+ * @memberof CloneWorkspace
+ */
+ id: string;
+}
+
+export function CloneWorkspaceFromJSON(json: any): CloneWorkspace {
+ return {
+ 'name': json['name'],
+ 'id': json['id'],
+ };
+}
+
+export function CloneWorkspaceToJSON(value?: CloneWorkspace): any {
+ if (value === undefined) {
+ return undefined;
+ }
+ return {
+ 'name': value.name,
+ 'id': value.id,
+ };
+}
+
+
diff --git a/app/src/sap-client/models/index.ts b/app/src/sap-client/models/index.ts
index 72307a3e..5352676b 100644
--- a/app/src/sap-client/models/index.ts
+++ b/app/src/sap-client/models/index.ts
@@ -9,6 +9,7 @@ export * from './ApprovalRequest';
export * from './ApprovalStatus';
export * from './BaseMetadata';
export * from './BioApiStatus';
+export * from './CloneWorkspace';
export * from './Column';
export * from './CreateWorkspace';
export * from './DataClearance';
diff --git a/openapi_specs/SOFI/SOFI.yaml b/openapi_specs/SOFI/SOFI.yaml
index 972a2d4d..9365b153 100644
--- a/openapi_specs/SOFI/SOFI.yaml
+++ b/openapi_specs/SOFI/SOFI.yaml
@@ -43,6 +43,20 @@ paths:
tags:
- workspaces
x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller
+ /workspace/clone:
+ post:
+ operationId: clone_workspace
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CloneWorkspace"
+ responses:
+ '204':
+ description: "Workspace cloned."
+ tags:
+ - workspaces
+ x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller
/workspace/{workspace_id}/{sample_id}:
delete:
description: Delete sample from workspace
@@ -1550,6 +1564,19 @@ components:
required:
- name
+ CloneWorkspace:
+ properties:
+ name:
+ type: string
+ title: Name
+ id:
+ type: string
+ title: Id
+ type: object
+ required:
+ - name
+ - id
+
UpdateWorkspace:
properties:
samples:
diff --git a/web/openapi_specs/SOFI/SOFI.yaml b/web/openapi_specs/SOFI/SOFI.yaml
index 972a2d4d..9365b153 100644
--- a/web/openapi_specs/SOFI/SOFI.yaml
+++ b/web/openapi_specs/SOFI/SOFI.yaml
@@ -43,6 +43,20 @@ paths:
tags:
- workspaces
x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller
+ /workspace/clone:
+ post:
+ operationId: clone_workspace
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CloneWorkspace"
+ responses:
+ '204':
+ description: "Workspace cloned."
+ tags:
+ - workspaces
+ x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller
/workspace/{workspace_id}/{sample_id}:
delete:
description: Delete sample from workspace
@@ -1550,6 +1564,19 @@ components:
required:
- name
+ CloneWorkspace:
+ properties:
+ name:
+ type: string
+ title: Name
+ id:
+ type: string
+ title: Id
+ type: object
+ required:
+ - name
+ - id
+
UpdateWorkspace:
properties:
samples:
diff --git a/web/src/SAP/generated/controllers/workspaces_controller.py b/web/src/SAP/generated/controllers/workspaces_controller.py
index e6d1c8cc..407d1b58 100644
--- a/web/src/SAP/generated/controllers/workspaces_controller.py
+++ b/web/src/SAP/generated/controllers/workspaces_controller.py
@@ -4,6 +4,21 @@
from .. import util
from ...src.controllers import WorkspacesController
+def clone_workspace(user, token_info, clone_workspace=None): # noqa: E501
+ """clone_workspace
+
+ # noqa: E501
+
+ :param clone_workspace:
+ :type clone_workspace: dict | bytes
+
+ :rtype: None
+ """
+ if connexion.request.is_json:
+ from ..models import CloneWorkspace
+ clone_workspace = CloneWorkspace.from_dict(connexion.request.get_json()) # noqa: E501
+ return WorkspacesController.clone_workspace(user, token_info, clone_workspace)
+
def create_workspace(user, token_info, create_workspace=None): # noqa: E501
"""create_workspace
diff --git a/web/src/SAP/generated/models/__init__.py b/web/src/SAP/generated/models/__init__.py
index ff71dcd7..b1040f27 100644
--- a/web/src/SAP/generated/models/__init__.py
+++ b/web/src/SAP/generated/models/__init__.py
@@ -15,6 +15,7 @@
from .base_metadata import BaseMetadata
from .bio_api_status import BioApiStatus
from .bulk_upload_request import BulkUploadRequest
+from .clone_workspace import CloneWorkspace
from .column import Column
from .create_workspace import CreateWorkspace
from .data_clearance import DataClearance
diff --git a/web/src/SAP/generated/models/clone_workspace.py b/web/src/SAP/generated/models/clone_workspace.py
new file mode 100644
index 00000000..e19120ca
--- /dev/null
+++ b/web/src/SAP/generated/models/clone_workspace.py
@@ -0,0 +1,96 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+from datetime import date, datetime # noqa: F401
+
+from typing import List, Dict # noqa: F401
+
+from web.src.SAP.generated.models.base_model_ import Model
+from web.src.SAP.generated import util
+
+
+class CloneWorkspace(Model):
+
+
+
+ """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ Do not edit the class manually.
+ """
+
+ def __init__(self, name=None, id=None): # noqa: E501
+ """CloneWorkspace - a model defined in OpenAPI
+
+ :param name: The name of this CloneWorkspace. # noqa: E501
+ :type name: str
+ :param id: The id of this CloneWorkspace. # noqa: E501
+ :type id: str
+ """
+ self.openapi_types = {
+ 'name': str,
+ 'id': str,
+ }
+
+ self.attribute_map = {
+ 'name': 'name',
+ 'id': 'id',
+ }
+
+ self._name = name
+ self._id = id
+
+ @classmethod
+ def from_dict(cls, dikt):
+ """Returns the dict as a model
+
+ :param dikt: A dict.
+ :type: dict
+ :return: The CloneWorkspace of this CloneWorkspace. # noqa: E501
+ :rtype: CloneWorkspace
+ """
+ return util.deserialize_model(dikt, cls)
+
+ @property
+ def name(self):
+ """Gets the name of this CloneWorkspace.
+
+
+ :return: The name of this CloneWorkspace.
+ :rtype: str
+ """
+ return self._name
+
+ @name.setter
+ def name(self, name):
+ """Sets the name of this CloneWorkspace.
+
+
+ :param name: The name of this CloneWorkspace.
+ :type name: str
+ """
+ if name is None:
+ raise ValueError("Invalid value for `name`, must not be `None`") # noqa: E501
+
+ self._name = name
+
+ @property
+ def id(self):
+ """Gets the id of this CloneWorkspace.
+
+
+ :return: The id of this CloneWorkspace.
+ :rtype: str
+ """
+ return self._id
+
+ @id.setter
+ def id(self, id):
+ """Sets the id of this CloneWorkspace.
+
+
+ :param id: The id of this CloneWorkspace.
+ :type id: str
+ """
+ if id is None:
+ raise ValueError("Invalid value for `id`, must not be `None`") # noqa: E501
+
+ self._id = id
diff --git a/web/src/SAP/generated/openapi/openapi.yaml b/web/src/SAP/generated/openapi/openapi.yaml
index 035b5e01..dd48188b 100644
--- a/web/src/SAP/generated/openapi/openapi.yaml
+++ b/web/src/SAP/generated/openapi/openapi.yaml
@@ -575,6 +575,20 @@ paths:
tags:
- user
x-openapi-router-controller: web.src.SAP.generated.controllers.user_controller
+ /workspace/clone:
+ post:
+ operationId: clone_workspace
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CloneWorkspace'
+ responses:
+ "204":
+ description: Workspace cloned.
+ tags:
+ - workspaces
+ x-openapi-router-controller: web.src.SAP.generated.controllers.workspaces_controller
/workspace/{workspace_id}/{sample_id}:
delete:
description: Delete sample from workspace
@@ -1555,6 +1569,21 @@ components:
required:
- name
type: object
+ CloneWorkspace:
+ example:
+ name: name
+ id: id
+ properties:
+ name:
+ title: Name
+ type: string
+ id:
+ title: Id
+ type: string
+ required:
+ - id
+ - name
+ type: object
UpdateWorkspace:
example:
samples:
diff --git a/web/src/SAP/generated/test/test_workspaces_controller.py b/web/src/SAP/generated/test/test_workspaces_controller.py
index b9e23d02..29a73fbe 100644
--- a/web/src/SAP/generated/test/test_workspaces_controller.py
+++ b/web/src/SAP/generated/test/test_workspaces_controller.py
@@ -5,6 +5,7 @@
from flask import json
from six import BytesIO
+from web.src.SAP.generated.models.clone_workspace import CloneWorkspace # noqa: E501
from web.src.SAP.generated.models.create_workspace import CreateWorkspace # noqa: E501
from web.src.SAP.generated.models.update_workspace import UpdateWorkspace # noqa: E501
from web.src.SAP.generated.models.workspace import Workspace # noqa: E501
@@ -15,6 +16,28 @@
class TestWorkspacesController(BaseTestCase):
"""WorkspacesController integration test stubs"""
+ def test_clone_workspace(self):
+ """Test case for clone_workspace
+
+
+ """
+ clone_workspace = {
+ "name" : "name",
+ "id" : "id"
+}
+ headers = {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer special-key',
+ }
+ response = self.client.open(
+ '/api/workspace/clone',
+ method='POST',
+ headers=headers,
+ data=json.dumps(clone_workspace),
+ content_type='application/json')
+ self.assert200(response,
+ 'Response body is : ' + response.data.decode('utf-8'))
+
def test_create_workspace(self):
"""Test case for create_workspace
diff --git a/web/src/SAP/src/controllers/WorkspacesController.py b/web/src/SAP/src/controllers/WorkspacesController.py
index d3672eb1..b479eeac 100644
--- a/web/src/SAP/src/controllers/WorkspacesController.py
+++ b/web/src/SAP/src/controllers/WorkspacesController.py
@@ -4,6 +4,7 @@
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 clone_workspace as clone_workspace_db
from ..repositories.workspaces import update_workspace as update_workspace_db
from ..repositories.workspaces import get_workspace as get_workspace_db
@@ -22,6 +23,14 @@ def create_workspace(user, token_info, body):
return jsonify(body)
+def clone_workspace(user, token_info, body):
+ res = clone_workspace_db(user, body)
+
+ if not res.inserted_id:
+ return None
+
+ return jsonify({"id": str(res.inserted_id)})
+
def post_workspace(user, token_info, workspace_id: str, body):
update_workspace_db(user, workspace_id, body)
diff --git a/web/src/SAP/src/repositories/workspaces.py b/web/src/SAP/src/repositories/workspaces.py
index 37116407..b97563b8 100644
--- a/web/src/SAP/src/repositories/workspaces.py
+++ b/web/src/SAP/src/repositories/workspaces.py
@@ -4,6 +4,7 @@
from ...src.repositories.analysis import get_analysis_with_metadata, get_single_analysis_by_object_id
from ...common.database import get_collection, WORKSPACES_COL_NAME
from ...generated.models import CreateWorkspace
+from ...generated.models import CloneWorkspace
def trim(item):
item["id"] = str(item["_id"])
@@ -61,6 +62,22 @@ def create_workspace(user: str, workspace: CreateWorkspace):
record = {**workspace.to_dict(), "created_by": user}
return workspaces.update_one({'created_by': user, 'name': workspace.name}, {"$set": record}, upsert=True)
+def clone_workspace(user: str, cloneWorkspaceInfo: CloneWorkspace):
+ workspaces = get_collection(WORKSPACES_COL_NAME)
+
+ if ObjectId.is_valid(cloneWorkspaceInfo.id):
+ workspace = trim(workspaces.find_one({"created_by": user, "_id": ObjectId(cloneWorkspaceInfo.id)}))
+ else:
+ workspace = trim(workspaces.find_one({"created_by": user, "name": cloneWorkspaceInfo.id}))
+
+ if workspace is None:
+ return None
+
+ if workspace["samples"] is None:
+ workspace["samples"] = []
+
+ return workspaces.insert_one({'created_by': user, 'name': cloneWorkspaceInfo.name, 'samples': workspace["samples"]})
+
def update_workspace(user: str, workspace_id: str, workspace: UpdateWorkspace):
workspaces = get_collection(WORKSPACES_COL_NAME)