Skip to content

Commit

Permalink
feat: Move model from one project to another
Browse files Browse the repository at this point in the history
  • Loading branch information
Paula-Kli committed Sep 28, 2023
1 parent 035542d commit 13951a6
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 0 deletions.
45 changes: 45 additions & 0 deletions backend/capellacollab/projects/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,51 @@ def get_projects(
return projects


@router.get(
"/role/{role}",
response_model=abc.Sequence[models.Project],
tags=["Projects"],
)
def get_projects_by_role(
role: str,
user: users_models.DatabaseUser = fastapi.Depends(
users_injectables.get_own_user
),
db: orm.Session = fastapi.Depends(database.get_db),
token=fastapi.Depends(jwt_bearer.JWTBearer()),
log: logging.LoggerAdapter = fastapi.Depends(
core_logging.get_request_logger
),
) -> abc.Sequence[models.DatabaseProject]:
if auth_injectables.RoleVerification(
required_role=users_models.Role.ADMIN, verify=False
)(token, db):
log.debug("Fetching all projects")
return crud.get_projects(db)

Check warning on line 89 in backend/capellacollab/projects/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/routes.py#L88-L89

Added lines #L88 - L89 were not covered by tests

match role:

Check warning on line 91 in backend/capellacollab/projects/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/routes.py#L91

Added line #L91 was not covered by tests
case "user":
required_role = projects_users_models.ProjectUserRole.USER

Check warning on line 93 in backend/capellacollab/projects/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/routes.py#L93

Added line #L93 was not covered by tests
case "manager":
required_role = projects_users_models.ProjectUserRole.MANAGER

Check warning on line 95 in backend/capellacollab/projects/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/routes.py#L95

Added line #L95 was not covered by tests
case "administrator":
required_role = projects_users_models.ProjectUserRole.ADMIN
case _:
raise fastapi.HTTPException(

Check warning on line 99 in backend/capellacollab/projects/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/routes.py#L97-L99

Added lines #L97 - L99 were not covered by tests
status_code=status.HTTP_400_BAD_REQUEST,
detail={"reason": "Requested role does not exist"},
)

projects = [
association.project
for association in user.projects
if association.role == required_role
]

log.debug("Fetching the following projects: %s", projects)
return projects

Check warning on line 111 in backend/capellacollab/projects/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/routes.py#L110-L111

Added lines #L110 - L111 were not covered by tests


@router.patch(
"/{project_slug}",
response_model=models.Project,
Expand Down
11 changes: 11 additions & 0 deletions backend/capellacollab/projects/toolmodels/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ def update_model(
return model


def move_model(
db: orm.Session,
model: models.DatabaseCapellaModel,
project: projects_model.DatabaseProject,
) -> models.DatabaseCapellaModel:
model.project = project
model.project_id = project.id
db.commit()
return model

Check warning on line 151 in backend/capellacollab/projects/toolmodels/crud.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/toolmodels/crud.py#L148-L151

Added lines #L148 - L151 were not covered by tests


def delete_model(db: orm.Session, model: models.DatabaseCapellaModel):
db.delete(model)
db.commit()
26 changes: 26 additions & 0 deletions backend/capellacollab/projects/toolmodels/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,32 @@ def patch_tool_model(
return crud.update_model(db, model, body.description, version, nature)


@router.patch(
"/{model_slug}/move",
response_model=models.CapellaModel,
dependencies=[
fastapi.Depends(
auth_injectables.ProjectRoleVerification(
required_role=projects_users_models.ProjectUserRole.MANAGER
)
)
],
tags=["Projects - Models"],
)
def move_tool_model(
body: dict,
model: models.DatabaseCapellaModel = fastapi.Depends(
injectables.get_existing_capella_model
),
db: orm.Session = fastapi.Depends(database.get_db),
) -> models.DatabaseCapellaModel:
new_project = projects_injectables.get_existing_project(

Check warning on line 169 in backend/capellacollab/projects/toolmodels/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/toolmodels/routes.py#L169

Added line #L169 was not covered by tests
body["new_project_slug"], db
)

return crud.move_model(db, model, new_project)

Check warning on line 173 in backend/capellacollab/projects/toolmodels/routes.py

View check run for this annotation

Codecov / codecov/patch

backend/capellacollab/projects/toolmodels/routes.py#L173

Added line #L173 was not covered by tests


@router.delete(
"/{model_slug}",
status_code=204,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import { ModelWrapperComponent } from './projects/models/model-wrapper/model-wra
import { EditProjectMetadataComponent } from './projects/project-detail/edit-project-metadata/edit-project-metadata.component';
import { ModelComplexityBadgeComponent } from './projects/project-detail/model-overview/model-complexity-badge/model-complexity-badge.component';
import { ModelOverviewComponent } from './projects/project-detail/model-overview/model-overview.component';
import { MoveModelComponent } from './projects/project-detail/model-overview/move-model/move-model.component';
import { ProjectDetailsComponent } from './projects/project-detail/project-details.component';
import { ProjectMetadataComponent } from './projects/project-detail/project-metadata/project-metadata.component';
import { AddUserToProjectComponent } from './projects/project-detail/project-users/add-user-to-project/add-user-to-project.component';
Expand Down Expand Up @@ -184,6 +185,7 @@ import { SettingsComponent } from './settings/settings.component';
ModelOverviewComponent,
ModelRestrictionsComponent,
ModelWrapperComponent,
MoveModelComponent,
NavBarMenuComponent,
NoticeComponent,
PipelineRunWrapperComponent,
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/app/projects/models/service/model.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ export class ModelService {
);
};
}

moveModelToProject(
projectSlug: string,
modelSlug: string,
new_project_slug: string
): Observable<Model> {
return this.http
.patch<Model>(`${this.backendURLFactory(projectSlug, modelSlug)}/move`, {
new_project_slug,
})
.pipe(
tap(() => {
this.loadModels(projectSlug);
this._model.next(undefined);
})
);
}
}

export type NewModel = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ <h2>Models</h2>
>
<mat-icon>key</mat-icon>
</a>
<button
mat-mini-fab
matTooltip="Move model to different project"
(click)="openMoveToProjectDialog(model)"
*ngIf="projectUserService.verifyRole('manager')"
>
<mat-icon>drive_file_move</mat-icon>
</button>
<a
mat-mini-fab
matTooltip="Configure model sources"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Model,
ModelService,
} from 'src/app/projects/models/service/model.service';
import { MoveModelComponent } from 'src/app/projects/project-detail/model-overview/move-model/move-model.component';
import { ProjectUserService } from 'src/app/projects/project-detail/project-users/service/project-user.service';
import { UserService } from 'src/app/services/user/user.service';
import { SessionService } from 'src/app/sessions/service/session.service';
Expand Down Expand Up @@ -72,4 +73,14 @@ export class ModelOverviewComponent implements OnInit {
const primaryModel = getPrimaryGitModel(model);
return primaryModel ? primaryModel.path : '';
}

openMoveToProjectDialog(model: Model): void {
this.projectService.project$.pipe(first()).subscribe((project) => {
this.dialog.open(MoveModelComponent, {
height: '40vh',
width: '40vw',
data: { project_slug: project?.slug, model: model },
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!--
~ SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
~ SPDX-License-Identifier: Apache-2.0
-->

<mat-card>
<h2>Move Model to Different Project</h2>
<div>Select the project to which you want to move the model to.</div>
<mat-form-field id="search" appearance="outline">
<mat-label>Search</mat-label>
<input
[(ngModel)]="search"
autocomplete="off"
matInput
placeholder="Project Name"
class="w-full"
/>
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
<mat-selection-list
[multiple]="false"
class="scrollable simple-scroll overflow-y-auto max-h-72"
(selectionChange)="onProjectSelect($event.options[0].value)"
>
<mat-list-option
*ngFor="let project of projectService.projects$ | async"
[value]="project"
>
<div mat-line>{{ project.name }}</div>
</mat-list-option>
</mat-selection-list>
<div *ngIf="selectedProject">
<button
mat-flat-button
color="primary"
(click)="moveModelToProject(selectedProject)"
>
<mat-icon>drive_file_move</mat-icon>
Move model {{ data.model.name }} to project {{ selectedProject.name }}
</button>
</div>
</mat-card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { ToastService } from 'src/app/helpers/toast/toast.service';
import {
Model,
ModelService,
} from 'src/app/projects/models/service/model.service';
import {
Project,
ProjectService,
} from 'src/app/projects/service/project.service';

@Component({
selector: 'app-move-model',
templateUrl: './move-model.component.html',
styleUrls: ['./move-model.component.css'],
})
export class MoveModelComponent {
selectedProject?: Project;
search = '';
constructor(
private modelService: ModelService,
private router: Router,
private dialogRef: MatDialogRef<MoveModelComponent>,
private toastService: ToastService,
public projectService: ProjectService,
@Inject(MAT_DIALOG_DATA)
public data: { project_slug: string; model: Model }
) {
this.projectService.loadProjectsForRole('manager');
}

onProjectSelect(project: Project) {
this.selectedProject = project;
}

async moveModelToProject(project: Project) {
this.modelService
.moveModelToProject(
this.data.project_slug,
this.data.model.slug,
project.slug
)
.subscribe(() => {
this.toastService.showSuccess(
'Model moved',
`The model “${this.data.model.name}” was successfuly moved to project "${this.data.project_slug}".`
);
this.dialogRef.close();
});
}
}
9 changes: 9 additions & 0 deletions frontend/src/app/projects/service/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ export class ProjectService {
});
}

loadProjectsForRole(role: string): void {
this.http
.get<Project[]>(`${this.BACKEND_URL_PREFIX}/role/${role}`)
.subscribe({
next: (projects) => this._projects.next(projects),
error: () => this._projects.next(undefined),
});
}

loadProjectBySlug(slug: string): void {
this.http.get<Project>(`${this.BACKEND_URL_PREFIX}/${slug}`).subscribe({
next: (project) => this._project.next(project),
Expand Down

0 comments on commit 13951a6

Please sign in to comment.