diff --git a/server/application-server/openapi.yaml b/server/application-server/openapi.yaml index f76b3029..0c2e89a8 100644 --- a/server/application-server/openapi.yaml +++ b/server/application-server/openapi.yaml @@ -37,6 +37,29 @@ paths: application/json: schema: $ref: "#/components/schemas/UserDTO" + /admin/teams: + put: + tags: + - admin + operationId: createTeam + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + color: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TeamDTO" /admin/config/repositories: post: tags: @@ -286,6 +309,25 @@ paths: application/json: schema: $ref: "#/components/schemas/UserDTO" + /admin/teams/{teamId}: + delete: + tags: + - admin + operationId: deleteTeam + parameters: + - name: teamId + in: path + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/TeamDTO" components: schemas: IssueCommentDTO: @@ -407,6 +449,20 @@ components: type: array items: $ref: "#/components/schemas/IssueCommentDTO" + TeamDTO: + required: + - color + - id + - name + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + color: + type: string PullRequestReviewDTO: required: - createdAt @@ -516,9 +572,8 @@ components: type: string state: type: string - description: |- - State of the PullRequest. - Does not include the state of the merge. + description: "State of the PullRequest.\r\n Does not include the state of\ + \ the merge." enum: - CLOSED - OPEN @@ -696,15 +751,12 @@ components: description: Display name of the user. url: type: string - description: |- - Unique URL to the user's profile. - Not the website a user can set in their profile. + description: "Unique URL to the user's profile.\r\n Not the website a user\ + \ can set in their profile." avatarUrl: type: string - description: |- - URL to the user's avatar. - If unavailable, a fallback can be generated from the login, e.g. on Github: - https://github.com/{login}.png + description: "URL to the user's avatar.\r\n If unavailable, a fallback can\ + \ be generated from the login, e.g. on Github:\r\n https://github.com/{login}.png" type: type: string description: Type of the user. Used to distinguish between users and bots. @@ -736,20 +788,6 @@ components: type: array items: $ref: "#/components/schemas/PullRequestReview" - TeamDTO: - required: - - color - - id - - name - type: object - properties: - id: - type: integer - format: int64 - name: - type: string - color: - type: string MetaDataDTO: type: object properties: diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminController.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminController.java index 1a9ee023..65d8364d 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminController.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminController.java @@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import de.tum.in.www1.hephaestus.codereview.team.TeamDTO; import de.tum.in.www1.hephaestus.codereview.user.UserDTO; import de.tum.in.www1.hephaestus.codereview.user.UserTeamsDTO; @@ -76,4 +77,16 @@ public ResponseEntity removeTeamFromUser(@PathVariable String login, @P .map(ResponseEntity::ok) .orElseGet(() -> ResponseEntity.notFound().build()); } + + @PutMapping("/teams") + public ResponseEntity createTeam(@RequestBody String name, @RequestBody String color) { + return ResponseEntity.ok(adminService.createTeam(name, color)); + } + + @DeleteMapping("/teams/{teamId}") + public ResponseEntity deleteTeam(@PathVariable Long teamId) { + return adminService.deleteTeam(teamId) + .map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } } diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminService.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminService.java index a4705b1f..dcf21a1e 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminService.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/admin/AdminService.java @@ -116,4 +116,19 @@ public Optional removeTeamFromUser(String login, Long teamId) { teamService.saveTeam(team); return Optional.of(new UserDTO(user.getId(), user.getLogin(), user.getEmail(), user.getName(), user.getUrl())); } + + public TeamDTO createTeam(String name, String color) { + logger.info("Creating team with name: " + name + " and color: " + color); + return TeamDTO.fromTeam(teamService.createTeam(name, color)); + } + + public Optional deleteTeam(Long teamId) { + logger.info("Deleting team with ID: " + teamId); + Optional optionalTeam = teamService.getTeam(teamId); + if (optionalTeam.isEmpty()) { + return Optional.empty(); + } + teamService.deleteTeam(teamId); + return Optional.of(TeamDTO.fromTeam(optionalTeam.get())); + } } diff --git a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/team/TeamService.java b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/team/TeamService.java index 27fb5935..a8046eda 100644 --- a/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/team/TeamService.java +++ b/server/application-server/src/main/java/de/tum/in/www1/hephaestus/codereview/team/TeamService.java @@ -35,29 +35,51 @@ public List getAllTeams() { public Team saveTeam(Team team) { logger.info("Saving team: " + team); + return teamRepository.saveAndFlush(team); + } + + public Team createTeam(String name, String color) { + logger.info("Creating team with name: " + name + " and color: " + color); + Team team = new Team(); + team.setName(name); + team.setColor(color); return teamRepository.save(team); } + public void deleteTeam(Long id) { + logger.info("Deleting team with id: " + id); + teamRepository.deleteById(id); + } + public Boolean createDefaultTeams() { logger.info("Creating default teams"); Team iris = teamRepository.save(new Team()); iris.setName("Iris"); + iris.setColor("#69feff"); Team athena = teamRepository.save(new Team()); athena.setName("Athena"); + athena.setColor("#69feff"); Team atlas = teamRepository.save(new Team()); atlas.setName("Atlas"); + atlas.setColor("#69feff"); Team programming = teamRepository.save(new Team()); programming.setName("Programming"); + programming.setColor("#69feff"); Team hephaestus = teamRepository.save(new Team()); hephaestus.setName("Hephaestus"); + hephaestus.setColor("#69feff"); Team communication = teamRepository.save(new Team()); communication.setName("Communication"); + communication.setColor("#69feff"); Team lectures = teamRepository.save(new Team()); lectures.setName("Lectures"); + lectures.setColor("#69feff"); Team usability = teamRepository.save(new Team()); usability.setName("Usability"); + usability.setColor("#69feff"); Team ares = teamRepository.save(new Team()); ares.setName("Ares"); + ares.setColor("#69feff"); teamRepository.saveAll( List.of(iris, athena, atlas, programming, hephaestus, communication, lectures, usability, ares)); return true; diff --git a/webapp/src/app/admin/admin.component.html b/webapp/src/app/admin/admin.component.html index cc6294ad..080af1db 100644 --- a/webapp/src/app/admin/admin.component.html +++ b/webapp/src/app/admin/admin.component.html @@ -1,12 +1,11 @@ -
-

User

- -

Config

- -
-
- -
- + +

Environment

+
+
+

Repositories to monitor:

+ +

User:

+
+
diff --git a/webapp/src/app/admin/admin.component.ts b/webapp/src/app/admin/admin.component.ts index 8893fafd..ce2a9bfe 100644 --- a/webapp/src/app/admin/admin.component.ts +++ b/webapp/src/app/admin/admin.component.ts @@ -36,11 +36,24 @@ export class AdminComponent { queryKey: ['admin', 'config'], queryFn: async () => { const adminConfig = await lastValueFrom(this.adminService.getConfig()); - this.repositoriesForm.setValue(JSON.stringify(adminConfig.repositoriesToMonitor)); + this.repositoriesForm.setValue(JSON.stringify(adminConfig.repositoriesToMonitor, null, 4)); return adminConfig; } })); + userForm = new FormControl( + JSON.stringify( + { + id: this.user()?.id, + email: this.user()?.email, + name: this.user()?.name, + anonymous: this.user()?.anonymous, + roles: this.user()?.roles + }, + null, + 4 + ) + ); repositoriesForm = new FormControl(''); saveRepositories() { diff --git a/webapp/src/app/admin/layout.component.html b/webapp/src/app/admin/layout.component.html index 9a77f72b..122ee6a5 100644 --- a/webapp/src/app/admin/layout.component.html +++ b/webapp/src/app/admin/layout.component.html @@ -1,15 +1,37 @@
-

Admin Page

-
- Admin -
    -
  • Config
  • -
  • Users
  • -
  • Teams
  • +
    + Admin Area +
    -
    +
    diff --git a/webapp/src/app/admin/layout.component.ts b/webapp/src/app/admin/layout.component.ts index 9b64cc29..6e18b70a 100644 --- a/webapp/src/app/admin/layout.component.ts +++ b/webapp/src/app/admin/layout.component.ts @@ -2,12 +2,15 @@ import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; -import { RouterModule, RouterOutlet } from '@angular/router'; +import { RouterLinkActive, RouterModule, RouterOutlet } from '@angular/router'; +import { HlmIconComponent, provideIcons } from '@spartan-ng/ui-icon-helm'; +import { lucideUserCircle, lucideFileCog, lucideUsers2 } from '@ng-icons/lucide'; @Component({ selector: 'app-admin-layout', standalone: true, - imports: [CommonModule, RouterModule, ReactiveFormsModule, HlmButtonModule, RouterOutlet], + imports: [CommonModule, RouterModule, ReactiveFormsModule, HlmButtonModule, RouterOutlet, RouterLinkActive, HlmIconComponent], + providers: [provideIcons({ lucideUserCircle, lucideFileCog, lucideUsers2 })], templateUrl: './layout.component.html' }) export class AdminLayoutComponent {} diff --git a/webapp/src/app/admin/teams/table/teams-table.component.html b/webapp/src/app/admin/teams/table/teams-table.component.html new file mode 100644 index 00000000..00d961d5 --- /dev/null +++ b/webapp/src/app/admin/teams/table/teams-table.component.html @@ -0,0 +1,136 @@ +
    + + +
    + + + + + @for (column of _brnColumnManager.allColumns; track column.name) { + + } + + +
    +
    + + + + + + + + + + + + + + + + @if (this.isLoading()) { + + } @else { + {{ element.name }} + } + + + + Color + +
    + {{ element.color }} +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    + + No entries found +
    +
    +
    +
    + {{ _selected().length }} of {{ _totalElements() }} row(s) selected +
    + + + + + + @for (size of _availablePageSizes; track size) { + + {{ size === 10000 ? 'All' : size }} + + } + + + +
    + + +
    +
    +
    + +
    +
    +

    Create new team

    +
    +
    + + +
    +
    + +
    +
    diff --git a/webapp/src/app/admin/teams/table/teams-table.component.ts b/webapp/src/app/admin/teams/table/teams-table.component.ts new file mode 100644 index 00000000..f38a85a3 --- /dev/null +++ b/webapp/src/app/admin/teams/table/teams-table.component.ts @@ -0,0 +1,200 @@ +import { SelectionModel } from '@angular/cdk/collections'; +import { DecimalPipe, TitleCasePipe } from '@angular/common'; +import { Component, TrackByFunction, computed, effect, inject, input, signal } from '@angular/core'; +import { toObservable, toSignal } from '@angular/core/rxjs-interop'; +import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { lucideArrowUpDown, lucideChevronDown, lucideMoreHorizontal, lucideRotateCw, lucideXOctagon } from '@ng-icons/lucide'; +import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; +import { HlmCheckboxCheckIconComponent, HlmCheckboxComponent } from '@spartan-ng/ui-checkbox-helm'; +import { HlmIconComponent, provideIcons } from '@spartan-ng/ui-icon-helm'; +import { HlmInputDirective } from '@spartan-ng/ui-input-helm'; +import { BrnMenuTriggerDirective } from '@spartan-ng/ui-menu-brain'; +import { HlmMenuModule } from '@spartan-ng/ui-menu-helm'; +import { BrnTableModule, PaginatorState, useBrnColumnManager } from '@spartan-ng/ui-table-brain'; +import { HlmTableModule } from '@spartan-ng/ui-table-helm'; +import { BrnSelectModule } from '@spartan-ng/ui-select-brain'; +import { HlmSelectModule } from '@spartan-ng/ui-select-helm'; +import { HlmSkeletonModule } from '@spartan-ng/ui-skeleton-helm'; +import { HlmCardModule } from '@spartan-ng/ui-card-helm'; +import { debounceTime, map } from 'rxjs'; +import { AdminService, TeamDTO } from '@app/core/modules/openapi'; +import { RouterLink } from '@angular/router'; +import { injectQueryClient } from '@tanstack/angular-query-experimental'; +import { octNoEntry } from '@ng-icons/octicons'; + +const LOADING_TEAMS: TeamDTO[] = [ + { + id: 1, + name: 'Team A', + color: '#FF0000' + }, + { + id: 2, + name: 'Team B', + color: '#00FF00' + } +]; + +@Component({ + selector: 'app-admin-teams-table', + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + RouterLink, + + BrnMenuTriggerDirective, + HlmMenuModule, + + BrnTableModule, + HlmTableModule, + + HlmButtonModule, + + DecimalPipe, + TitleCasePipe, + HlmIconComponent, + HlmInputDirective, + + HlmCheckboxCheckIconComponent, + HlmCheckboxComponent, + + BrnSelectModule, + HlmSelectModule, + + HlmSkeletonModule, + HlmCardModule + ], + providers: [provideIcons({ lucideChevronDown, lucideMoreHorizontal, lucideArrowUpDown, lucideRotateCw, lucideXOctagon })], + templateUrl: './teams-table.component.html' +}) +export class AdminTeamsTableComponent { + protected adminService = inject(AdminService); + protected queryClient = injectQueryClient(); + protected octNoEntry = octNoEntry; + + isLoading = input(false); + teamData = input.required(); + + _teams = computed(() => this.teamData() ?? LOADING_TEAMS); + protected readonly _rawFilterInput = signal(''); + protected readonly _nameFilter = signal(''); + private readonly _debouncedFilter = toSignal(toObservable(this._rawFilterInput).pipe(debounceTime(300))); + + private readonly _displayedIndices = signal({ start: 0, end: 0 }); + protected readonly _availablePageSizes = [5, 10, 20, 10000]; + protected readonly _pageSize = signal(this._availablePageSizes[0]); + + private readonly _selectionModel = new SelectionModel(true); + protected readonly _isUserSelected = (user: TeamDTO) => this._selectionModel.isSelected(user); + protected readonly _selected = toSignal(this._selectionModel.changed.pipe(map((change) => change.source.selected)), { + initialValue: [] + }); + + protected readonly _brnColumnManager = useBrnColumnManager({ + name: { visible: true, label: 'Name' }, + color: { visible: true, label: 'Color' } + }); + protected readonly _allDisplayedColumns = computed(() => ['select', ...this._brnColumnManager.displayedColumns(), 'actions']); + + private readonly _filteredNames = computed(() => { + const nameFilter = this._nameFilter()?.trim()?.toLowerCase(); + if (nameFilter && nameFilter.length > 0) { + return this._teams().filter((u) => u.name!.toLowerCase().includes(nameFilter)); + } + return this._teams(); + }); + private readonly _nameSort = signal<'ASC' | 'DESC' | null>(null); + protected readonly _filteredSortedPaginatedTeams = computed(() => { + const sort = this._nameSort(); + const start = this._displayedIndices().start; + const end = this._displayedIndices().end + 1; + const names = this._filteredNames(); + if (!sort) { + return names.slice(start, end); + } + return [...names].sort((p1, p2) => (sort === 'ASC' ? 1 : -1) * p1.name.localeCompare(p2.name)).slice(start, end); + }); + protected readonly _allFilteredPaginatedTeamsSelected = computed(() => this._filteredSortedPaginatedTeams().every((team) => this._selected().includes(team))); + protected readonly _checkboxState = computed(() => { + const noneSelected = this._selected().length === 0; + const allSelectedOrIndeterminate = this._allFilteredPaginatedTeamsSelected() ? true : 'indeterminate'; + return noneSelected ? false : allSelectedOrIndeterminate; + }); + + protected readonly _trackBy: TrackByFunction = (_: number, u: TeamDTO) => u.id; + protected readonly _totalElements = computed(() => this._filteredNames().length); + protected readonly _onStateChange = ({ startIndex, endIndex }: PaginatorState) => this._displayedIndices.set({ start: startIndex, end: endIndex }); + + constructor() { + // needed to sync the debounced filter to the name filter, but being able to override the + // filter when loading new users without debounce + effect(() => this._nameFilter.set(this._debouncedFilter() ?? ''), { allowSignalWrites: true }); + } + + protected toggleTeam(team: TeamDTO) { + this._selectionModel.toggle(team); + } + + protected handleHeaderCheckboxChange() { + const previousCbState = this._checkboxState(); + if (previousCbState === 'indeterminate' || !previousCbState) { + this._selectionModel.select(...this._filteredSortedPaginatedTeams()); + } else { + this._selectionModel.deselect(...this._filteredSortedPaginatedTeams()); + } + } + + protected handleNameSortChange() { + const sort = this._nameSort(); + if (sort === 'ASC') { + this._nameSort.set('DESC'); + } else if (sort === 'DESC') { + this._nameSort.set(null); + } else { + this._nameSort.set('ASC'); + } + } + + protected deleteTeam(team: TeamDTO) { + if (this.isLoading()) { + return; + } + this.adminService.deleteTeam(team.id!); + this.invalidateTeams(); + } + + protected copyName(element: TeamDTO) { + console.log('Copying name', element); + navigator.clipboard.writeText(element.name!); + } + + protected invalidateTeams() { + if (this.isLoading()) { + return; + } + for (const team of this._selected()) { + this._selectionModel.deselect(team); + } + this.queryClient.invalidateQueries({ queryKey: ['admin', 'teams'] }); + } + + _newTeamName = new FormControl(''); + _newTeamColor = new FormControl(''); + + protected createTeam() { + if (this.isLoading() || !this._newTeamName.value || !this._newTeamColor.value) { + return; + } + this.adminService + .createTeam({ + name: this._newTeamName.value, + color: this._newTeamColor.value + }) + .subscribe({ + next: () => console.log('Team created'), + error: (err) => console.error('Error creating team', err) + }); + this.invalidateTeams(); + } +} diff --git a/webapp/src/app/admin/teams/teams.component.ts b/webapp/src/app/admin/teams/teams.component.ts new file mode 100644 index 00000000..f9cd126b --- /dev/null +++ b/webapp/src/app/admin/teams/teams.component.ts @@ -0,0 +1,24 @@ +import { Component, inject } from '@angular/core'; +import { lastValueFrom } from 'rxjs'; +import { TeamService } from '@app/core/modules/openapi'; +import { injectQuery } from '@tanstack/angular-query-experimental'; +import { RouterLink } from '@angular/router'; +import { AdminTeamsTableComponent } from './table/teams-table.component'; + +@Component({ + selector: 'app-admin-teams', + standalone: true, + imports: [RouterLink, AdminTeamsTableComponent], + template: ` +

    Teams

    + + ` +}) +export class AdminTeamsComponent { + protected teamService = inject(TeamService); + + teamsQuery = injectQuery(() => ({ + queryKey: ['admin', 'teams'], + queryFn: async () => lastValueFrom(this.teamService.getTeams()) + })); +} diff --git a/webapp/src/app/admin/users/table/users-table.component.html b/webapp/src/app/admin/users/table/users-table.component.html index 6125cd0a..de78cea4 100644 --- a/webapp/src/app/admin/users/table/users-table.component.html +++ b/webapp/src/app/admin/users/table/users-table.component.html @@ -76,7 +76,7 @@ Teams @for (team of element.teams; track team) { - @let label = { name: team.name, color: '69feff' }; + @let label = { name: team.name, color: team.color ?? '69feff' }; } @@ -148,5 +148,5 @@ - +
diff --git a/webapp/src/app/admin/users/table/users-table.component.ts b/webapp/src/app/admin/users/table/users-table.component.ts index 5fd159e2..72697e92 100644 --- a/webapp/src/app/admin/users/table/users-table.component.ts +++ b/webapp/src/app/admin/users/table/users-table.component.ts @@ -202,6 +202,17 @@ export class AdminUsersTableComponent { this.invalidateUsers(); } + protected removeTeamFromSelected() { + for (const user of this._selected()) { + console.log('Removing team from user', user.login, this._selectedTeam()); + this.adminService.removeTeamFromUser(user.login, this._selectedTeam()!.id).subscribe({ + next: () => console.log('Team removed from user', user), + error: (err) => console.error('Error removing team from user', user, err) + }); + } + this.invalidateUsers(); + } + protected invalidateUsers() { if (this.isLoading()) { return; diff --git a/webapp/src/app/admin/users/users.component.html b/webapp/src/app/admin/users/users.component.html deleted file mode 100644 index 1e6fe6e2..00000000 --- a/webapp/src/app/admin/users/users.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-

Admin - Users

- Back -
- - -
diff --git a/webapp/src/app/admin/users/users.component.ts b/webapp/src/app/admin/users/users.component.ts index 31d2496e..04993a4b 100644 --- a/webapp/src/app/admin/users/users.component.ts +++ b/webapp/src/app/admin/users/users.component.ts @@ -4,13 +4,15 @@ import { AdminService, TeamService } from '@app/core/modules/openapi'; import { injectQuery } from '@tanstack/angular-query-experimental'; import { AdminUsersTableComponent } from './table/users-table.component'; import { RouterLink } from '@angular/router'; -import { HlmButtonModule } from '@spartan-ng/ui-button-helm'; @Component({ selector: 'app-admin-users', standalone: true, - imports: [RouterLink, HlmButtonModule, AdminUsersTableComponent], - templateUrl: './users.component.html' + imports: [RouterLink, AdminUsersTableComponent], + template: ` +

Users

+ + ` }) export class AdminUsersComponent { protected adminService = inject(AdminService); diff --git a/webapp/src/app/app.routes.ts b/webapp/src/app/app.routes.ts index 56f81349..1831b1f9 100644 --- a/webapp/src/app/app.routes.ts +++ b/webapp/src/app/app.routes.ts @@ -6,6 +6,7 @@ import { AdminGuard } from '@app/core/security/admin.guard'; import { UserProfileComponent } from '@app/user/user-profile.component'; import { AdminUsersComponent } from './admin/users/users.component'; import { AdminLayoutComponent } from './admin/layout.component'; +import { AdminTeamsComponent } from './admin/teams/teams.component'; export const routes: Routes = [ { path: '', component: HomeComponent }, @@ -22,6 +23,10 @@ export const routes: Routes = [ { path: 'users', component: AdminUsersComponent + }, + { + path: 'teams', + component: AdminTeamsComponent } ] }, diff --git a/webapp/src/app/core/modules/openapi/.openapi-generator/FILES b/webapp/src/app/core/modules/openapi/.openapi-generator/FILES index 60f91ad0..13a41b4b 100644 --- a/webapp/src/app/core/modules/openapi/.openapi-generator/FILES +++ b/webapp/src/app/core/modules/openapi/.openapi-generator/FILES @@ -20,6 +20,7 @@ encoder.ts git_push.sh index.ts model/admin-config.ts +model/create-team-request.ts model/issue-comment-dto.ts model/issue-comment.ts model/leaderboard-entry.ts diff --git a/webapp/src/app/core/modules/openapi/api/admin.service.ts b/webapp/src/app/core/modules/openapi/api/admin.service.ts index 2432e9eb..10d8a32e 100644 --- a/webapp/src/app/core/modules/openapi/api/admin.service.ts +++ b/webapp/src/app/core/modules/openapi/api/admin.service.ts @@ -21,6 +21,10 @@ import { Observable } from 'rxjs'; // @ts-ignore import { AdminConfig } from '../model/admin-config'; // @ts-ignore +import { CreateTeamRequest } from '../model/create-team-request'; +// @ts-ignore +import { TeamDTO } from '../model/team-dto'; +// @ts-ignore import { UserDTO } from '../model/user-dto'; // @ts-ignore import { UserInfoDto } from '../model/user-info-dto'; @@ -227,6 +231,142 @@ export class AdminService implements AdminServiceInterface { ); } + /** + * @param createTeamRequest + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public createTeam(createTeamRequest: CreateTeamRequest, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public createTeam(createTeamRequest: CreateTeamRequest, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public createTeam(createTeamRequest: CreateTeamRequest, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public createTeam(createTeamRequest: CreateTeamRequest, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (createTeamRequest === null || createTeamRequest === undefined) { + throw new Error('Required parameter createTeamRequest was null or undefined when calling createTeam.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/admin/teams`; + return this.httpClient.request('put', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: createTeamRequest, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * @param teamId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public deleteTeam(teamId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public deleteTeam(teamId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public deleteTeam(teamId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public deleteTeam(teamId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (teamId === null || teamId === undefined) { + throw new Error('Required parameter teamId was null or undefined when calling deleteTeam.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/admin/teams/${this.configuration.encodeParam({name: "teamId", value: teamId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`; + return this.httpClient.request('delete', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + /** * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. * @param reportProgress flag to report request and response progress. diff --git a/webapp/src/app/core/modules/openapi/api/admin.serviceInterface.ts b/webapp/src/app/core/modules/openapi/api/admin.serviceInterface.ts index d04a99c1..aae7480f 100644 --- a/webapp/src/app/core/modules/openapi/api/admin.serviceInterface.ts +++ b/webapp/src/app/core/modules/openapi/api/admin.serviceInterface.ts @@ -14,6 +14,8 @@ import { HttpHeaders } from '@angular/comm import { Observable } from 'rxjs'; import { AdminConfig } from '../model/models'; +import { CreateTeamRequest } from '../model/models'; +import { TeamDTO } from '../model/models'; import { UserDTO } from '../model/models'; import { UserInfoDto } from '../model/models'; import { UserTeamsDTO } from '../model/models'; @@ -41,6 +43,20 @@ export interface AdminServiceInterface { */ admin(extraHttpRequestParams?: any): Observable; + /** + * + * + * @param createTeamRequest + */ + createTeam(createTeamRequest: CreateTeamRequest, extraHttpRequestParams?: any): Observable; + + /** + * + * + * @param teamId + */ + deleteTeam(teamId: number, extraHttpRequestParams?: any): Observable; + /** * * diff --git a/webapp/src/app/core/modules/openapi/model/create-team-request.ts b/webapp/src/app/core/modules/openapi/model/create-team-request.ts new file mode 100644 index 00000000..5368a823 --- /dev/null +++ b/webapp/src/app/core/modules/openapi/model/create-team-request.ts @@ -0,0 +1,18 @@ +/** + * Hephaestus API + * API documentation for the Hephaestus application server. + * + * The version of the OpenAPI document: 0.0.1 + * Contact: felixtj.dietrich@tum.de + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface CreateTeamRequest { + name?: string; + color?: string; +} + diff --git a/webapp/src/app/core/modules/openapi/model/models.ts b/webapp/src/app/core/modules/openapi/model/models.ts index 7f51ca77..ff2e98a0 100644 --- a/webapp/src/app/core/modules/openapi/model/models.ts +++ b/webapp/src/app/core/modules/openapi/model/models.ts @@ -1,4 +1,5 @@ export * from './admin-config'; +export * from './create-team-request'; export * from './issue-comment'; export * from './issue-comment-dto'; export * from './leaderboard-entry'; diff --git a/webapp/src/app/core/modules/openapi/model/pull-request.ts b/webapp/src/app/core/modules/openapi/model/pull-request.ts index 98c19061..7676c134 100644 --- a/webapp/src/app/core/modules/openapi/model/pull-request.ts +++ b/webapp/src/app/core/modules/openapi/model/pull-request.ts @@ -24,7 +24,7 @@ export interface PullRequest { title: string; url: string; /** - * State of the PullRequest. Does not include the state of the merge. + * State of the PullRequest. Does not include the state of the merge. */ state: PullRequest.StateEnum; additions?: number; diff --git a/webapp/src/app/core/modules/openapi/model/user.ts b/webapp/src/app/core/modules/openapi/model/user.ts index a00782a1..29d0c8e8 100644 --- a/webapp/src/app/core/modules/openapi/model/user.ts +++ b/webapp/src/app/core/modules/openapi/model/user.ts @@ -30,11 +30,11 @@ export interface User { */ name?: string; /** - * Unique URL to the user\'s profile. Not the website a user can set in their profile. + * Unique URL to the user\'s profile. Not the website a user can set in their profile. */ url: string; /** - * URL to the user\'s avatar. If unavailable, a fallback can be generated from the login, e.g. on Github: https://github.com/{login}.png + * URL to the user\'s avatar. If unavailable, a fallback can be generated from the login, e.g. on Github: https://github.com/{login}.png */ avatarUrl?: string; /** diff --git a/webapp/src/libs/ui/ui-button-helm/src/lib/hlm-button.directive.ts b/webapp/src/libs/ui/ui-button-helm/src/lib/hlm-button.directive.ts index 35e39e84..c0c01af8 100644 --- a/webapp/src/libs/ui/ui-button-helm/src/lib/hlm-button.directive.ts +++ b/webapp/src/libs/ui/ui-button-helm/src/lib/hlm-button.directive.ts @@ -13,7 +13,8 @@ export const buttonVariants = cva( outline: 'border border-input hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'underline-offset-4 hover:underline text-primary' + link: 'underline-offset-4 hover:underline text-primary', + navButton: 'font-normal hover:bg-accent hover:text-accent-foreground justify-start gap-2 w-full' }, size: { default: 'h-10 py-2 px-4', diff --git a/webapp/src/libs/ui/ui-icon-helm/src/lib/hlm-icon.component.ts b/webapp/src/libs/ui/ui-icon-helm/src/lib/hlm-icon.component.ts index b68442af..60f70960 100644 --- a/webapp/src/libs/ui/ui-icon-helm/src/lib/hlm-icon.component.ts +++ b/webapp/src/libs/ui/ui-icon-helm/src/lib/hlm-icon.component.ts @@ -5,7 +5,7 @@ import { hlm } from '@spartan-ng/ui-core'; import { cva } from 'class-variance-authority'; import type { ClassValue } from 'clsx'; -const DEFINED_SIZES = ['xs', 'sm', 'base', 'lg', 'xl', 'none'] as const; +const DEFINED_SIZES = ['xs', 'sm', 'normal', 'base', 'lg', 'xl', 'none'] as const; type DefinedSizes = (typeof DEFINED_SIZES)[number]; @@ -14,6 +14,7 @@ export const iconVariants = cva('inline-flex', { variant: { xs: 'h-3 w-3', sm: 'h-4 w-4', + normal: 'h-5 w-5', base: 'h-6 w-6', lg: 'h-8 w-8', xl: 'h-12 w-12',