diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py index 56a3107715..9c9c9e3cea 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py @@ -6,7 +6,6 @@ import sqlalchemy as sa from sqlalchemy import orm -from capellacollab.core import database from capellacollab.projects.toolmodels import models as toolmodels_models from capellacollab.projects.toolmodels.modelsources.t4c import models from capellacollab.settings.modelsources.t4c.repositories import ( @@ -59,10 +58,13 @@ def create_t4c_model( def patch_t4c_model( db: orm.Session, t4c_model: models.DatabaseT4CModel, - patch_model: models.SubmitT4CModel, + name: str | None = None, + repository: repositories_models.DatabaseT4CRepository | None = None, ) -> models.DatabaseT4CModel: - database.patch_database_with_pydantic_object(t4c_model, patch_model) - + if name is not None: + t4c_model.name = name + if repository is not None: + t4c_model.repository = repository db.commit() return t4c_model diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py index 2dbbd53dbb..b1713da78d 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py @@ -54,6 +54,12 @@ class SubmitT4CModel(core_pydantic.BaseModel): t4c_repository_id: int +class PatchT4CModel(core_pydantic.BaseModel): + name: str | None = None + t4c_instance_id: int | None = None + t4c_repository_id: int | None = None + + class SimpleT4CModel(core_pydantic.BaseModel): project_name: str repository_name: str diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py index 349d2c0ef9..23d118f1f7 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py @@ -15,7 +15,7 @@ from capellacollab.projects.toolmodels.backups import crud as backups_crud from capellacollab.projects.users import models as projects_users_models from capellacollab.settings.modelsources.t4c import ( - injectables as settings_t4c_injecatbles, + injectables as settings_t4c_injectables, ) from capellacollab.settings.modelsources.t4c.repositories import ( injectables as settings_t4c_repositories_injectables, @@ -87,7 +87,7 @@ def create_t4c_model( ), db: orm.Session = fastapi.Depends(database.get_db), ): - instance = settings_t4c_injecatbles.get_existing_unarchived_instance( + instance = settings_t4c_injectables.get_existing_unarchived_instance( body.t4c_instance_id, db ) repository = ( @@ -118,14 +118,26 @@ def create_t4c_model( ], ), ) -def edit_t4c_model( - body: models.SubmitT4CModel, +def update_t4c_model( + body: models.PatchT4CModel, t4c_model: models.DatabaseT4CModel = fastapi.Depends( injectables.get_existing_t4c_model ), db: orm.Session = fastapi.Depends(database.get_db), ): - return crud.patch_t4c_model(db, t4c_model, body) + if body.t4c_instance_id is not None: + instance = settings_t4c_injectables.get_existing_unarchived_instance( + body.t4c_instance_id, db + ) + else: + instance = t4c_model.repository.instance + + repository = ( + settings_t4c_repositories_injectables.get_existing_t4c_repository( + body.t4c_repository_id or t4c_model.repository.id, db, instance + ) + ) + return crud.patch_t4c_model(db, t4c_model, body.name, repository) @router.delete( diff --git a/backend/capellacollab/settings/modelsources/t4c/models.py b/backend/capellacollab/settings/modelsources/t4c/models.py index 0d1294257c..478a8b2d91 100644 --- a/backend/capellacollab/settings/modelsources/t4c/models.py +++ b/backend/capellacollab/settings/modelsources/t4c/models.py @@ -129,7 +129,6 @@ class PatchT4CInstance(core_pydantic.BaseModel): username: str | None = None password: str | None = None protocol: Protocol | None = None - version_id: int | None = None is_archived: bool | None = None _validate_rest_api_url = pydantic.field_validator("rest_api")( diff --git a/backend/tests/projects/toolmodels/fixtures.py b/backend/tests/projects/toolmodels/fixtures.py index daacd2e7ad..4bd9e5a0c3 100644 --- a/backend/tests/projects/toolmodels/fixtures.py +++ b/backend/tests/projects/toolmodels/fixtures.py @@ -9,11 +9,6 @@ import capellacollab.projects.models as projects_models import capellacollab.projects.toolmodels.crud as toolmodels_crud import capellacollab.projects.toolmodels.models as toolmodels_models -import capellacollab.projects.toolmodels.modelsources.git.crud as project_git_crud -import capellacollab.projects.toolmodels.modelsources.git.models as project_git_models -import capellacollab.projects.toolmodels.modelsources.t4c.crud as models_t4c_crud -import capellacollab.projects.toolmodels.modelsources.t4c.models as models_t4c_models -import capellacollab.settings.modelsources.t4c.repositories.models as settings_t4c_repositories_models import capellacollab.tools.models as tools_models @@ -41,7 +36,6 @@ def fixture_jupyter_model( name="Jupyter test", description="", tool_id=jupyter_tool.id, - configuration={"workspace": str(uuid.uuid4())}, ) return toolmodels_crud.create_model( db, @@ -50,30 +44,3 @@ def fixture_jupyter_model( tool=jupyter_tool, configuration={"workspace": "test"}, ) - - -@pytest.fixture(name="git_model") -def fixture_git_model( - db: orm.Session, capella_model: toolmodels_models.DatabaseToolModel -) -> project_git_models.DatabaseGitModel: - git_model = project_git_models.PostGitModel( - path="https://example.com/test/project", - entrypoint="test/test.aird", - revision="main", - username="user", - password="password", - ) - return project_git_crud.add_git_model_to_capellamodel( - db, capella_model, git_model - ) - - -@pytest.fixture(name="t4c_model") -def fixture_t4c_model( - db: orm.Session, - capella_model: toolmodels_models.DatabaseToolModel, - t4c_repository: settings_t4c_repositories_models.DatabaseT4CRepository, -) -> models_t4c_models.DatabaseT4CModel: - return models_t4c_crud.create_t4c_model( - db, capella_model, t4c_repository, "default" - ) diff --git a/backend/tests/projects/toolmodels/modelsources/fixtures.py b/backend/tests/projects/toolmodels/modelsources/fixtures.py new file mode 100644 index 0000000000..4d1299d030 --- /dev/null +++ b/backend/tests/projects/toolmodels/modelsources/fixtures.py @@ -0,0 +1,39 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from sqlalchemy import orm + +import capellacollab.projects.toolmodels.models as toolmodels_models +import capellacollab.projects.toolmodels.modelsources.git.crud as project_git_crud +import capellacollab.projects.toolmodels.modelsources.git.models as project_git_models +import capellacollab.projects.toolmodels.modelsources.t4c.crud as models_t4c_crud +import capellacollab.projects.toolmodels.modelsources.t4c.models as models_t4c_models +import capellacollab.settings.modelsources.t4c.repositories.models as settings_t4c_repositories_models + + +@pytest.fixture(name="t4c_model") +def fixture_t4c_model( + db: orm.Session, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_repository: settings_t4c_repositories_models.DatabaseT4CRepository, +) -> models_t4c_models.DatabaseT4CModel: + return models_t4c_crud.create_t4c_model( + db, capella_model, t4c_repository, "default" + ) + + +@pytest.fixture(name="git_model") +def fixture_git_model( + db: orm.Session, capella_model: toolmodels_models.DatabaseToolModel +) -> project_git_models.DatabaseGitModel: + git_model = project_git_models.PostGitModel( + path="https://example.com/test/project", + entrypoint="test/test.aird", + revision="main", + username="user", + password="password", + ) + return project_git_crud.add_git_model_to_capellamodel( + db, capella_model, git_model + ) diff --git a/backend/tests/projects/toolmodels/modelsources/test_t4c_routes.py b/backend/tests/projects/toolmodels/modelsources/test_t4c_routes.py new file mode 100644 index 0000000000..eef503f61d --- /dev/null +++ b/backend/tests/projects/toolmodels/modelsources/test_t4c_routes.py @@ -0,0 +1,186 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + + +import pytest +from fastapi import testclient +from sqlalchemy import orm + +import capellacollab.projects.models as projects_models +import capellacollab.settings.modelsources.t4c.repositories.crud as t4c_repositories_crud +import capellacollab.settings.modelsources.t4c.repositories.models as t4c_repositories_models +from capellacollab.projects.toolmodels import models as toolmodels_models +from capellacollab.projects.toolmodels.modelsources.t4c import crud as t4c_crud +from capellacollab.projects.toolmodels.modelsources.t4c import ( + models as t4c_models, +) +from capellacollab.settings.modelsources.t4c import crud as t4c_servers_crud +from capellacollab.settings.modelsources.t4c import ( + models as t4c_servers_models, +) +from capellacollab.settings.modelsources.t4c.repositories import ( + models as t4c_repositories_models, +) +from capellacollab.tools import models as tools_models + + +@pytest.mark.usefixtures("project_manager") +def test_list_t4c_models( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.get( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c" + ) + + assert response.status_code == 200 + assert len(response.json()) == 1 + assert response.json()[0]["name"] == t4c_model.name + + +@pytest.mark.usefixtures("project_manager") +def test_get_t4c_model( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.get( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}" + ) + + assert response.status_code == 200 + assert response.json()["name"] == t4c_model.name + assert ( + response.json()["repository"]["instance"]["id"] + == t4c_model.repository.instance.id + ) + + +@pytest.mark.usefixtures("admin") +def test_create_t4c_model( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + t4c_repository: t4c_repositories_models.DatabaseT4CRepository, + capella_model: toolmodels_models.DatabaseToolModel, +): + response = client.post( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c", + json={ + "name": "project", + "t4c_instance_id": t4c_repository.instance.id, + "t4c_repository_id": t4c_repository.id, + }, + ) + + assert response.status_code == 200 + assert response.json()["name"] == "project" + assert response.json()["repository"]["id"] == t4c_repository.id + assert ( + response.json()["repository"]["instance"]["id"] + == t4c_repository.instance.id + ) + + +@pytest.mark.usefixtures("admin", "t4c_model") +def test_create_t4c_model_twice_fails( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + t4c_repository: t4c_repositories_models.DatabaseT4CRepository, + capella_model: toolmodels_models.DatabaseToolModel, +): + response = client.post( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c", + json={ + "name": "default", + "t4c_instance_id": t4c_repository.instance.id, + "t4c_repository_id": t4c_repository.id, + }, + ) + + assert response.status_code == 409 + assert ( + response.json()["detail"]["err_code"] + == "T4C_INTEGRATION_ALREADY_EXISTS" + ) + + +@pytest.mark.usefixtures("admin") +def test_change_server_of_t4c_model( + db: orm.Session, + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, + tool_version: tools_models.DatabaseVersion, +): + server = t4c_servers_models.DatabaseT4CInstance( + name="test server 2", + license="lic", + host="localhost", + usage_api="http://localhost:8086", + rest_api="http://localhost:8080/api/v1.0", + username="user", + password="pass", + protocol=t4c_servers_models.Protocol.tcp, + version=tool_version, + ) + db_server = t4c_servers_crud.create_t4c_instance(db, server) + + second_t4c_repository = t4c_repositories_crud.create_t4c_repository( + db=db, repo_name="test2", instance=db_server + ) + + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}", + json={ + "t4c_instance_id": db_server.id, + "t4c_repository_id": second_t4c_repository.id, + }, + ) + + assert response.status_code == 200 + assert response.json()["name"] == t4c_model.name + assert response.json()["repository"]["id"] == second_t4c_repository.id + assert ( + response.json()["repository"]["instance"]["id"] + == second_t4c_repository.instance.id + ) + + +@pytest.mark.usefixtures("admin") +def test_patch_name_of_t4c_model( + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.patch( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}", + json={"name": "new_default"}, + ) + + assert response.status_code == 200 + assert response.json()["name"] == "new_default" + assert ( + response.json()["repository"]["instance"]["id"] + == t4c_model.repository.instance.id + ) + + +@pytest.mark.usefixtures("admin") +def test_unlink_t4c_model( + db: orm.Session, + client: testclient.TestClient, + project: projects_models.DatabaseProject, + capella_model: toolmodels_models.DatabaseToolModel, + t4c_model: t4c_models.DatabaseT4CModel, +): + response = client.delete( + f"/api/v1/projects/{project.slug}/models/{capella_model.slug}/modelsources/t4c/{t4c_model.id}", + ) + + assert response.status_code == 204 + assert t4c_crud.get_t4c_model_by_id(db, t4c_model.id) is None diff --git a/frontend/src/app/projects/models/model-detail/model-detail.component.html b/frontend/src/app/projects/models/model-detail/model-detail.component.html index c6357b6e1a..12b118643c 100644 --- a/frontend/src/app/projects/models/model-detail/model-detail.component.html +++ b/frontend/src/app/projects/models/model-detail/model-detail.component.html @@ -4,7 +4,7 @@ -->

Git repositories

-
+
@@ -58,7 +58,7 @@

Git repositories

userService.user?.role === "administrator" ) {

TeamForCapella repositories

-
+
diff --git a/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.component.html b/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.component.html index 6ac6053ea7..7630e5d982 100644 --- a/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.component.html +++ b/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.component.html @@ -81,7 +81,7 @@ -
+
@if (t4cModel !== undefined) { }
diff --git a/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.stories.ts b/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.stories.ts index bb22935171..1d2163c2b7 100644 --- a/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.stories.ts +++ b/frontend/src/app/projects/models/model-source/t4c/manage-t4c-model/manage-t4c-model.stories.ts @@ -13,6 +13,7 @@ import { mockExtendedT4CRepository, mockT4CInstance, MockT4CInstanceWrapperService, + mockT4CModel, MockT4CRepositoryWrapperService, } from 'src/storybook/t4c'; import { ManageT4CModelComponent } from './manage-t4c-model.component'; @@ -65,3 +66,27 @@ export const General: Story = { }), ], }; + +export const Modify: Story = { + args: { + t4cModel: mockT4CModel, + }, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: T4CInstanceWrapperService, + useFactory: () => + new MockT4CInstanceWrapperService(mockT4CInstance, [ + mockT4CInstance, + ]), + }, + { + provide: T4CRepositoryWrapperService, + useFactory: () => + new MockT4CRepositoryWrapperService([mockExtendedT4CRepository]), + }, + ], + }), + ], +}; diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html index 73277376ac..a3ee491283 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html @@ -43,11 +43,10 @@

@if ( (t4cInstanceWrapperService.t4cInstance$ | async) !== undefined && - (t4cInstanceWrapperService.t4cInstance$ | async)!.version.id !== - form.value.version_id + editing ) { Models are not auto-migrated on version change.The version can't be updated. } @else if ( form.controls.version_id.errors?.required || @@ -180,8 +179,6 @@

The password is required. } @else if (existing && !form.value.password) { Is not changed if empty - } @else { - } diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts index db12916deb..2a75a41b84 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts @@ -158,6 +158,7 @@ export class EditT4CInstanceComponent implements OnInit, OnDestroy { enableEditing(): void { this.editing = true; this.form.enable(); + this.form.controls.version_id.disable(); this.form.controls.password.patchValue(''); this.form.controls.password.removeValidators(Validators.required); diff --git a/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.html b/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.html index b0abeff081..953df59658 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.html +++ b/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.html @@ -115,7 +115,7 @@

Repositories

class="!h-[56px]" mat-stroked-button color="primary" - [disabled]="!form.valid" + [disabled]="!form.valid || repositoryCreationInProgress" (click)="createRepository()" >
diff --git a/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.ts index c338b4ffd4..bb3357a5e5 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.ts +++ b/frontend/src/app/settings/modelsources/t4c-settings/t4c-instance-settings/t4c-instance-settings.component.ts @@ -65,6 +65,8 @@ export class T4CInstanceSettingsComponent implements OnChanges, OnDestroy { search = ''; + repositoryCreationInProgress = false; + constructor( public t4cRepoService: T4CRepositoryWrapperService, private dialog: MatDialog, @@ -102,13 +104,20 @@ export class T4CInstanceSettingsComponent implements OnChanges, OnDestroy { } createRepository(): void { + this.repositoryCreationInProgress = true; if (this.form.valid) { this.t4cRepoService .createRepository( this.instance!.id, this.form.value as CreateT4CRepository, ) - .subscribe(() => this.form.reset()); + .subscribe({ + next: () => { + this.form.reset(); + this.repositoryCreationInProgress = false; + }, + error: () => (this.repositoryCreationInProgress = false), + }); } } diff --git a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html index 2399cfab1c..e9dd8eaee5 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html +++ b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html @@ -3,7 +3,7 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -