Skip to content

Commit

Permalink
feat: Add user profile page
Browse files Browse the repository at this point in the history
Users can now look at common projects with another user or have a look at their personal profile page.
  • Loading branch information
Paula-Kli committed Oct 25, 2023
1 parent 7f1b267 commit f4292d1
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 3 deletions.
34 changes: 34 additions & 0 deletions backend/capellacollab/projects/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,40 @@ def get_projects(
return projects


@router.get(
"/common/{user_id}", response_model=list[models.Project], tags=["Projects"]
)
def get_common_projects(
user_for_common_projects: users_models.DatabaseUser = fastapi.Depends(
users_injectables.get_existing_user
),
user: users_models.DatabaseUser = fastapi.Depends(
users_injectables.get_own_user
),
log: logging.LoggerAdapter = fastapi.Depends(
core_logging.get_request_logger
),
) -> list[models.DatabaseProject]:
current_users_projects = [
association.project
for association in user.projects
if not association.project.visibility == models.Visibility.INTERNAL
]
projects_for_common = [
association.project
for association in user_for_common_projects.projects
if not association.project.visibility == models.Visibility.INTERNAL
]

projects = [
project
for project in current_users_projects
if project in projects_for_common
]
log.info("Fetching the following projects: %s", projects)
return projects


@router.patch(
"/{project_slug}",
response_model=models.Project,
Expand Down
43 changes: 43 additions & 0 deletions backend/tests/users/test_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

import pytest
from fastapi import testclient
from sqlalchemy import orm

import capellacollab.projects.users.models as projects_users_models
from capellacollab.projects import models as projects_models
from capellacollab.projects.users import crud as projects_users_crud
from capellacollab.users import crud as users_crud
from capellacollab.users import models as users_models


@pytest.mark.usefixtures("user")
def test_get_no_common_projects(
client: testclient.TestClient, db: orm.Session
):
user2 = users_crud.create_user(db, "user2")
response = client.get(f"/api/v1/projects/common/{user2.id}")
assert response.status_code == 200
assert len(response.json()) == 0


@pytest.mark.usefixtures("project_user")
def test_get_common_projects(
client: testclient.TestClient,
db: orm.Session,
project: projects_models.DatabaseProject,
):
user2 = users_crud.create_user(db, "user2")
projects_users_crud.add_user_to_project(
db,
project,
user2,
role=projects_users_models.ProjectUserRole.USER,
permission=projects_users_models.ProjectUserPermission.WRITE,
)

response = client.get(f"/api/v1/projects/common/{user2.id}")
assert response.status_code == 200
assert len(response.json()) == 1
assert response.json()[0]["slug"] == project.slug
6 changes: 6 additions & 0 deletions frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { EditProjectMetadataComponent } from 'src/app/projects/project-detail/ed
import { SessionComponent } from 'src/app/sessions/session/session.component';
import { PipelinesOverviewComponent } from 'src/app/settings/core/pipelines-overview/pipelines-overview.component';
import { BasicAuthTokenComponent } from 'src/app/users/basic-auth-token/basic-auth-token.component';
import { UsersProfileComponent } from 'src/app/users/users-profile/users-profile.component';
import { EventsComponent } from './events/events.component';
import { AuthComponent } from './general/auth/auth/auth.component';
import { AuthGuardService } from './general/auth/auth-guard/auth-guard.service';
Expand Down Expand Up @@ -442,6 +443,11 @@ const routes: Routes = [
data: { breadcrumb: 'Events' },
component: EventsComponent,
},
{
path: 'user',
data: { breadcrumb: (data: Data) => data?.user?.name || 'User' },
component: UsersProfileComponent,
},
{
path: 'tokens',
data: { breadcrumb: 'Tokens' },
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 @@ -136,6 +136,7 @@ import { T4CRepoDeletionDialogComponent } from './settings/modelsources/t4c-sett
import { T4CSettingsWrapperComponent } from './settings/modelsources/t4c-settings/t4c-settings-wrapper/t4c-settings-wrapper.component';
import { T4CSettingsComponent } from './settings/modelsources/t4c-settings/t4c-settings.component';
import { SettingsComponent } from './settings/settings.component';
import { UsersProfileComponent } from './users/users-profile/users-profile.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -223,6 +224,7 @@ import { SettingsComponent } from './settings/settings.component';
TriggerPipelineComponent,
UserSessionsWrapperComponent,
UserSettingsComponent,
UsersProfileComponent,
VersionComponent,
ViewLogsDialogComponent,
],
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/app/general/header/header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@
</a>
</div>
<mat-menu #profileMenu="matMenu">
<a
class="profileMenuButton"
mat-menu-item
routerLink="user"
[queryParams]="{ user_id: userService.user?.id }"
>
Profile <mat-icon>account_circle</mat-icon>
</a>
<a
*ngIf="userService.user?.role === 'administrator'"
class="profileMenuButton"
Expand Down Expand Up @@ -84,7 +92,7 @@
mat-raised-button
[matMenuTriggerFor]="profileMenu"
>
Profile <mat-icon>account_circle</mat-icon>
Menu <mat-icon>menu</mat-icon>
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ <h2>Project members</h2>
)
"
>
<div class="flex">
<a
class="flex"
[routerLink]="['/user']"
[queryParams]="{ userId: user.user.id, userName: user.user.name }"
>
<div class="ml-0 mr-4 flex items-center">
<mat-icon class="my-auto !h-8 !w-8 text-3xl" mat-list-icon
>account_circle</mat-icon
Expand All @@ -63,7 +67,7 @@ <h2>Project members</h2>
{{ projectUserService.PERMISSIONS[user.permission] }}
</div>
</div>
</div>
</a>
<div class="ml-1 mr-0 flex items-center">
<button
mat-icon-button
Expand Down
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 {
});
}

loadCommonProjects(userId: number): void {
this.http
.get<Project[]>(`${this.BACKEND_URL_PREFIX}/common/${userId}`)
.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
4 changes: 4 additions & 0 deletions frontend/src/app/services/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export class UserService {
return this.http.get<User>(this.BACKEND_URL_PREFIX + user.id);
}

getUserById(userId: number): Observable<User> {
return this.http.get<User>(this.BACKEND_URL_PREFIX + userId);
}

getCurrentUser(): Observable<User> {
return this.http.get<User>(this.BACKEND_URL_PREFIX + 'current');
}
Expand Down
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
*/
27 changes: 27 additions & 0 deletions frontend/src/app/users/users-profile/users-profile.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!--
~ SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
~ SPDX-License-Identifier: Apache-2.0
-->

<h2>Profile of {{ userName }}</h2>

<div *ngIf="userId !== currentUser?.id">
Common Projects

<ng-container
*ngIf="(projectService.projects$ | async)?.length; else elseBlock"
>
<mat-card
*ngFor="let project of projectService.projects$ | async"
class="!ml-0 cursor-pointer"
[routerLink]="['/project', project.slug]"
>
<mat-card-content>
<b>{{ project.name }}</b
><br />
{{ project.description }}
</mat-card-content>
</mat-card>
</ng-container>
<ng-template #elseBlock>You do not have any common projects.</ng-template>
</div>
41 changes: 41 additions & 0 deletions frontend/src/app/users/users-profile/users-profile.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProjectService } from 'src/app/projects/service/project.service';
import { User, UserService } from 'src/app/services/user/user.service';

@Component({
selector: 'app-users-profile',
templateUrl: './users-profile.component.html',
styleUrls: ['./users-profile.component.css'],
})
export class UsersProfileComponent implements OnInit {
userName: string | undefined;
userId: number | undefined;
currentUser: User | undefined;
constructor(
public userService: UserService,
public projectService: ProjectService,
private route: ActivatedRoute,
) {}

ngOnInit() {
this.route.queryParams.subscribe((params) => {
this.userId = params.userId;
this.userName = params.userName;
});
this.userService.getCurrentUser().subscribe((res) => {
this.currentUser = res;
if (!this.userId) {
this.userId = res.id;
this.userName = res.name;
} else {
this.projectService.loadCommonProjects(this.userId!);
}
});
}
}

0 comments on commit f4292d1

Please sign in to comment.