Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
create rule table(#199)
Browse files Browse the repository at this point in the history
iam-flo committed Jan 13, 2025
1 parent da277a8 commit 981740d
Showing 7 changed files with 281 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -50,6 +50,8 @@ public PullRequestBadPracticeRuleDTO createRule(String repositoryNameWithOwner,
newRule.setRepository(repository);
newRule.setActive(rule.active());

logger.debug("Saving rule: {}", newRule);

return PullRequestBadPracticeRuleDTO.fromPullRequestBadPracticeRule(
pullRequestBadPracticeRuleRepository.save(newRule));
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package de.tum.in.www1.hephaestus.activity.model;

import de.tum.in.www1.hephaestus.gitprovider.repository.Repository;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -18,7 +15,8 @@
public class PullRequestBadPracticeRule {

@Id
private Long id;
@GeneratedValue
private Long id;

private String title;

5 changes: 5 additions & 0 deletions webapp/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import { AdminGuard } from '@app/core/security/admin.guard';
import { AuthGuard } from '@app/core/security/auth.guard';
import { MentorGuard } from '@app/core/security/mentor.guard';
import { ActivityDashboardComponent } from '@app/home/activity/activity-dashboard.component';
import { WorkspaceRulesComponent } from '@app/workspace/badpractices/rules.component';

export const routes: Routes = [
// Public routes
@@ -34,6 +35,10 @@ export const routes: Routes = [
{
path: 'teams',
component: WorkspaceTeamsComponent
},
{
path: 'rules',
component: WorkspaceRulesComponent
}
]
},
27 changes: 27 additions & 0 deletions webapp/src/app/workspace/badpractices/rules.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, inject } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import { WorkspaceService } from '@app/core/modules/openapi';
import { injectQuery } from '@tanstack/angular-query-experimental';
import { WorkspaceRulesTableComponent } from '@app/workspace/badpractices/table/rules-table/rules-table.component';
import { BrnSelectModule } from '@spartan-ng/ui-select-brain';
import { HlmSelectModule } from '@spartan-ng/ui-select-helm';
import { FormsModule } from '@angular/forms';
import { HlmTdComponent, HlmThComponent } from '@spartan-ng/ui-table-helm';

@Component({
selector: 'app-workspace-rules',
standalone: true,
imports: [BrnSelectModule, HlmSelectModule, FormsModule, HlmThComponent, HlmTdComponent, HlmThComponent, WorkspaceRulesTableComponent],
template: `
<h1 class="text-3xl font-bold mb-4">Bad practice rules</h1>
<app-workspace-rules-table [availableRepos]="allReposQuery.data()" />
`
})
export class WorkspaceRulesComponent {
protected workspaceService = inject(WorkspaceService);

allReposQuery = injectQuery(() => ({
queryKey: ['workspace', 'repositoriesToMonitor'],
queryFn: async () => lastValueFrom(this.workspaceService.getRepositoriesToMonitor())
}));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<div class="flex flex-col gap-2">
<div>
<brn-select class="inline-block" placeholder="Select a repository" [(ngModel)]="_selectedRepo">
<hlm-select-trigger class="inline-flex">
<hlm-select-value />
</hlm-select-trigger>
<hlm-select-content class="min-w-[9.5rem]">
@for (repo of availableRepos(); track repo) {
<hlm-option [value]="repo" class="containerSize">
<span class="">{{ repo }}</span>
</hlm-option>
}
</hlm-select-content>
</brn-select>
</div>
@if (_selectedRepo()) {
<div class="grid grid-cols-3 gap-2">
<div class="flex flex-col justify-start col-span-1 gap-2">
<brn-popover sideOffset="5" closeDelay="100">
<button brnPopoverTrigger (click)="resetCreateForm()" hlmBtn variant="outline" class="w-full">Create new rule</button>
<div hlmPopoverContent class="w-full grid gap-4" *brnPopoverContent="let ctx">
<div class="space-y-2">
<h4 class="font-medium leading-none">New bad practice rule</h4>
<p class="text-sm text-muted-foreground">Create a new bad practice rule for pull requests.</p>
</div>
<div class="grid gap-2">
<div class="items-center grid grid-cols-3 gap-4">
<label hlmLabel for="title">Title</label>
<input hlmInput [formControl]="_newTitle" id="title" placeholder="Title" class="h-8 col-span-2" />
</div>
<div class="items-start grid grid-cols-3 gap-4">
<label hlmLabel for="description">Description</label>
<textarea hlmInput [formControl]="_newDescription" id="description" placeholder="Description" class="h-32 col-span-2 resize-none"></textarea>
</div>
<div class="items-start grid grid-cols-3 gap-4">
<label hlmLabel for="conditions">Conditions</label>
<textarea hlmInput [formControl]="_newConditions" id="conditions" placeholder="Conditions" class="h-32 col-span-2 resize-none"></textarea>
</div>
<button hlmBtn variant="default" (click)="createRule.mutate()" class="w-full">Create rule</button>
</div>
</div>
</brn-popover>
<brn-table
hlm
stickyHeader
class="border-border block overflow-auto rounded-md border cursor-pointer"
[dataSource]="rules()"
[displayedColumns]="['active', 'title']"
[trackBy]="_trackBy"
>
<brn-column-def name="active">
<hlm-th *brnHeaderDef>
<span class="text-sm font-semibold">Active</span>
</hlm-th>
<hlm-td *brnCellDef="let element">
@if (element.active) {
<hlm-icon name="lucideCheck" class="text-github-success-foreground" />
} @else {
<hlm-icon name="lucideX" class="text-github-danger-foreground" />
}
</hlm-td>
</brn-column-def>
<brn-column-def name="title">
<hlm-th *brnHeaderDef>
<span class="text-sm font-semibold">Title</span>
</hlm-th>
<hlm-td truncate *brnCellDef="let element" (click)="selectRule(element)">
<span class="text-sm">{{ element.title }}</span>
</hlm-td>
</brn-column-def>
</brn-table>
</div>
<div class="col-span-2">
@if (_selectedRule()) {
<section hlmCard>
<div hlmCardContent class="grid gap-2">
<div class="flex flex-row justify-between items-baseline gap-2">
<input hlmInput [formControl]="_newEditTitle" placeholder="Title" class="w-full" />
<button hlmBtn variant="outline" (click)="cancelEdit()">Cancel</button>
</div>
<label hlmLabel>
Description
<textarea hlmInput [formControl]="_newEditDescription" placeholder="Description" class="w-full h-32 resize-none"></textarea>
</label>
<label hlmLabel>
Conditions
<textarea hlmInput [formControl]="_newEditConditions" placeholder="Conditions" class="w-full h-32 resize-none"></textarea>
</label>
<label class="flex items-center" hlmLabel>
<hlm-switch class="mr-2" [formControl]="_newEditIsActive" />
Active
</label>
<button hlmBtn variant="default" (click)="save()">Save rule</button>
</div>
</section>
}
</div>
</div>
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Component, computed, inject, input, signal, TrackByFunction } from '@angular/core';
import { BrnSelectModule } from '@spartan-ng/ui-select-brain';
import { HlmSelectModule } from '@spartan-ng/ui-select-helm';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrnTableModule } from '@spartan-ng/ui-table-brain';
import { HlmTableModule } from '@spartan-ng/ui-table-helm';
import { BadPracticeRuleService, PullRequestBadPracticeRule } from '@app/core/modules/openapi';
import { BrnPopoverComponent, BrnPopoverImports, BrnPopoverTriggerDirective } from '@spartan-ng/ui-popover-brain';
import { HlmPopoverModule } from '@spartan-ng/ui-popover-helm';
import { HlmInputDirective } from '@spartan-ng/ui-input-helm';
import { HlmButtonModule } from '@spartan-ng/ui-button-helm';
import { injectMutation, injectQuery, injectQueryClient } from '@tanstack/angular-query-experimental';
import { lastValueFrom } from 'rxjs';
import { HlmMenuModule } from '@spartan-ng/ui-menu-helm';
import { HlmSkeletonModule } from '@spartan-ng/ui-skeleton-helm';
import { HlmCardModule } from '@spartan-ng/ui-card-helm';
import { HlmLabelDirective } from '@spartan-ng/ui-label-helm';
import { HlmSwitchComponent } from '@spartan-ng/ui-switch-helm';
import { HlmIconComponent } from '@spartan-ng/ui-icon-helm';
import { lucideCheck, lucideX } from '@ng-icons/lucide';
import { provideIcons } from '@ng-icons/core';

@Component({
selector: 'app-workspace-rules-table',
standalone: true,
imports: [
FormsModule,
ReactiveFormsModule,
HlmMenuModule,
BrnTableModule,
HlmTableModule,
HlmButtonModule,
HlmInputDirective,
BrnSelectModule,
HlmSelectModule,
HlmSkeletonModule,
HlmCardModule,
HlmPopoverModule,
BrnPopoverComponent,
BrnPopoverTriggerDirective,
HlmLabelDirective,
BrnPopoverImports,
HlmSwitchComponent,
HlmIconComponent
],
templateUrl: './rules-table.component.html',
providers: [provideIcons({ lucideCheck, lucideX })],
styles: ``
})
export class WorkspaceRulesTableComponent {
protected ruleService = inject(BadPracticeRuleService);
protected queryClient = injectQueryClient();

availableRepos = input<string[]>();

_newTitle = new FormControl('');
_newDescription = new FormControl('');
_newConditions = new FormControl('');
_newIsActive = new FormControl(true);

_newEditTitle = new FormControl('');
_newEditDescription = new FormControl('');
_newEditConditions = new FormControl('');
_newEditIsActive = new FormControl(true);

protected readonly _trackBy: TrackByFunction<PullRequestBadPracticeRule> = (_: number, r: PullRequestBadPracticeRule) => r.id;
protected _selectedRule = signal<PullRequestBadPracticeRule | undefined>(undefined);
protected readonly _selectedRepo = signal<string | undefined>(undefined);
protected readonly repoOwner = computed(() => this._selectedRepo()?.split('/')[0] ?? '');
protected readonly repoName = computed(() => this._selectedRepo()?.split('/')[1] ?? '');
protected readonly rules = computed(() => this.getRulesForRepoQuery.data() ?? []);

resetCreateForm() {
this._newTitle.setValue('');
this._newDescription.setValue('');
this._newConditions.setValue('');
this._newIsActive.setValue(true);
}

resetEditForm() {
this._newEditTitle.setValue('');
this._newEditDescription.setValue('');
this._newEditConditions.setValue('');
this._newEditIsActive.setValue(true);
}

selectRule(selectedRule: PullRequestBadPracticeRule) {
this._selectedRule.set(selectedRule);
this._newEditTitle.setValue(selectedRule.title ?? '');
this._newEditDescription.setValue(selectedRule.description ?? '');
this._newEditConditions.setValue(selectedRule.conditions ?? '');
this._newEditIsActive.setValue(selectedRule.active ?? true);
}

cancelEdit() {
this._selectedRule.set(undefined);
this.resetEditForm();
}

save() {
this.saveRule.mutate();
}

saveRule = injectMutation(() => ({
mutationFn: () =>
lastValueFrom(
this.ruleService.updateRule(this._selectedRule()?.id ?? 0, {
title: this._newEditTitle.value,
description: this._newEditDescription.value,
conditions: this._newEditConditions.value,
active: this._newEditIsActive.value
} as PullRequestBadPracticeRule)
),
queryKey: ['workspace', 'rules', 'update'],
onSettled: () => this.invalidateRepoQuery()
}));

createRule = injectMutation(() => ({
mutationFn: () =>
lastValueFrom(
this.ruleService.createRule(this.repoOwner(), this.repoName(), {
title: this._newTitle.value,
description: this._newDescription.value,
conditions: this._newConditions.value,
active: true
} as PullRequestBadPracticeRule)
),
queryKey: ['workspace', 'rules', 'create'],
onSettled: () => this.invalidateRepoQuery()
}));

getRulesForRepoQuery = injectQuery(() => ({
queryFn: () => lastValueFrom(this.ruleService.getRulesByRepository(this.repoOwner(), this.repoName())),
queryKey: ['activity', 'rules', this._selectedRepo()]
}));

protected invalidateRepoQuery() {
this.queryClient.invalidateQueries({ queryKey: ['activity', 'rules'] });
}
}
7 changes: 4 additions & 3 deletions webapp/src/app/workspace/layout.component.ts
Original file line number Diff line number Diff line change
@@ -4,20 +4,21 @@ import { ReactiveFormsModule } from '@angular/forms';
import { HlmButtonModule } from '@spartan-ng/ui-button-helm';
import { RouterLinkActive, RouterModule, RouterOutlet } from '@angular/router';
import { HlmIconComponent, provideIcons } from '@spartan-ng/ui-icon-helm';
import { lucideUserCircle, lucideCog, lucideUsers2 } from '@ng-icons/lucide';
import { lucideUserCircle, lucideCog, lucideUsers2, lucideAxe } from '@ng-icons/lucide';

type NavItem = { icon: string; label: string; route: string; exact?: boolean };
@Component({
selector: 'app-workspace-layout',
standalone: true,
imports: [CommonModule, RouterModule, ReactiveFormsModule, HlmButtonModule, RouterOutlet, RouterLinkActive, HlmIconComponent],
providers: [provideIcons({ lucideUserCircle, lucideCog, lucideUsers2 })],
providers: [provideIcons({ lucideUserCircle, lucideCog, lucideUsers2, lucideAxe })],
templateUrl: './layout.component.html'
})
export class WorkspaceLayoutComponent {
navItems: NavItem[] = [
{ icon: 'lucideCog', label: 'Settings', route: '.', exact: true },
{ icon: 'lucideUserCircle', label: 'Users', route: 'users' },
{ icon: 'lucideUsers2', label: 'Teams', route: 'teams' }
{ icon: 'lucideUsers2', label: 'Teams', route: 'teams' },
{ icon: 'lucideAxe', label: 'Rules', route: 'rules' }
];
}

0 comments on commit 981740d

Please sign in to comment.