* Result.BUILDING added

* team editor is mostly done
* commit tracker works and is styled
* added new components for slide editor, still empty
* team resolver service
This commit is contained in:
Dávid Danyi 2018-04-09 18:41:53 +02:00
parent 11aed00a05
commit b0cbd691d5
25 changed files with 433 additions and 78 deletions

View File

@ -3,6 +3,8 @@ import { RouterModule, Routes } from '@angular/router';
import { TeamListComponent} from './team-list/team-list.component';
import { TeamService } from '../shared/service/team.service';
import { TeamResolverService } from './team-resolver.service';
import { TeamEditorComponent } from './team-editor/team-editor.component';
const routes: Routes = [
{
@ -12,6 +14,13 @@ const routes: Routes = [
resolve: {
teams: TeamService,
},
}, {
path: 'admin/team/edit/:id',
component: TeamEditorComponent,
// canActivate: [AuthGuardService, RoleGuardService],
resolve: {
team: TeamResolverService,
},
}
];

View File

@ -1,15 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AdminRoutingModule } from './admin-routing.module';
import { TeamListComponent } from './team-list/team-list.component';
import { TeamEditorComponent } from './team-editor/team-editor.component';
import { SlideEditorComponent } from './slide-editor/slide-editor.component';
import { SlideListComponent } from './slide-list/slide-list.component';
import { TeamResolverService } from './team-resolver.service';
@NgModule({
imports: [
CommonModule,
FormsModule,
AdminRoutingModule
],
declarations: [TeamListComponent],
providers: []
declarations: [TeamListComponent, TeamEditorComponent, SlideEditorComponent, SlideListComponent],
providers: [TeamResolverService]
})
export class AdminModule { }

View File

@ -0,0 +1,3 @@
<p>
slide-editor works!
</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SlideEditorComponent } from './slide-editor.component';
describe('SlideEditorComponent', () => {
let component: SlideEditorComponent;
let fixture: ComponentFixture<SlideEditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SlideEditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SlideEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-slide-editor',
templateUrl: './slide-editor.component.html',
styleUrls: ['./slide-editor.component.css']
})
export class SlideEditorComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,3 @@
<p>
slide-list works!
</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SlideListComponent } from './slide-list.component';
describe('SlideListComponent', () => {
let component: SlideListComponent;
let fixture: ComponentFixture<SlideListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SlideListComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SlideListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-slide-list',
templateUrl: './slide-list.component.html',
styleUrls: ['./slide-list.component.css']
})
export class SlideListComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,53 @@
<div class="ui main container">
<h1 class="ui dividing header">Team editor</h1>
<form class="ui form" #teamEditorForm (ngSubmit)="saveTeam()">
<div class="six wide field">
<label for="team_name">Team name</label>
<input id="team_name" type="text" name="team_name" [(ngModel)]="team.name">
</div>
<button type="submit" class="ui positive button">Save changes</button>
<h4 class="ui dividing header">Team members</h4>
<div class="three inline fields">
<div class="two wide field">
<button type="button" class="ui fluid button"
[class.positive]="canAddMember"
[class.disabled]="!canAddMember"
(click)="addMember()">Add
</button>
</div>
<div class="five wide field">
<input type="text"
name="member_signum"
placeholder="Signum"
[(ngModel)]="member.signum">
</div>
<div class="nine wide field">
<input type="text"
name="member_name"
placeholder="Display name"
[(ngModel)]="member.name">
</div>
</div>
<h4 class="ui dividing header"></h4>
<table class="ui celled definition table">
<thead>
<tr>
<th class="collapsing"></th>
<th class="collapsing"><i class="large user outline icon"></i>Signum</th>
<th><i class="large id card outline icon"></i>Display name</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let member of team.members">
<td class="collapsing"><a title="Remove" (click)="removeMember(member.signum)"><i
class="large fitted red trash alternate outline icon"></i></a></td>
<td class="collapsing">{{member.signum}}</td>
<td>{{member.name}}</td>
</tr>
</tbody>
</table>
</form>
</div>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TeamEditorComponent } from './team-editor.component';
describe('TeamEditorComponent', () => {
let component: TeamEditorComponent;
let fixture: ComponentFixture<TeamEditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TeamEditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TeamEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { TeamService } from '../../shared/service/team.service';
import { Team } from '../../shared/team';
import { Member } from '../../shared/member';
@Component({
selector: 'app-team-editor',
templateUrl: './team-editor.component.html',
styleUrls: ['./team-editor.component.css']
})
export class TeamEditorComponent implements OnInit {
public team: Team = new Team();
public member: Member = new Member();
constructor(private teamService: TeamService,
private titleService: Title,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.titleService.setTitle('Team editor : MTAStv');
this.route.data.subscribe((data: { team: Team }) => this.team = data.team);
}
get canAddMember(): boolean {
try {
return [this.member.name, this.member.signum].every(field => field.length !== 0);
} catch (e) {
return false;
}
}
public addMember() {
this.team.members = this.team.members.concat(Object.assign({}, this.member));
this.member = new Member();
}
public removeMember(signum: String) {
if (confirm(`Remove the member with signum ${signum}?`)) {
this.team.members = this.team.members.filter(member => member.signum !== signum);
}
}
public saveTeam() {
this.teamService.update(this.team).subscribe(
() => this.router.navigate(['/admin/teams/list'])
);
}
}

View File

@ -1,20 +1,18 @@
<div class="ui main container">
<h1 class="ui dividing header">Teams</h1>
<div class="ui raised segments">
<div class="ui segment">
<table *ngIf="teams?.length" class="ui celled definition table">
<table *ngIf="teams?.length" class="ui large padded celled definition table">
<thead>
<tr>
<th></th>
<th class="clickable">Team</th>
<th class="clickable">Members</th>
<th class="clickable"><i class="large address book outline icon"></i>Team</th>
<th class="clickable"><i class="large users icon"></i>Members</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let team of teams">
<td class="collapsing">
<a [routerLink]="['/team/edit', team.id]" title="Change"><i
class="large edit link icon"></i></a>
<a [routerLink]="['/admin/team/edit', team.id]" title="Change"><i
class="large fitted pencil alternate icon"></i></a>
</td>
<td>{{team.name}}</td>
<td>{{fancyMemberNames(team)}}</td>
@ -22,5 +20,3 @@
</tbody>
</table>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { TeamResolverService } from './team-resolver.service';
describe('TeamResolverService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [TeamResolverService]
});
});
it('should be created', inject([TeamResolverService], (service: TeamResolverService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Team } from '../shared/team';
import { environment } from '../../environments/environment';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class TeamResolverService implements Resolve<Team> {
private apiEndPoint = environment.apiUrl + '/api/team';
constructor(private httpClient: HttpClient) {}
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Team> {
return this.getTeam(route.params['id']).toPromise();
}
public getTeam(id: Number): Observable<Team> {
return this.httpClient.get<Team>(`${this.apiEndPoint}/${id}`);
}
}

View File

@ -0,0 +1,42 @@
.ui.label.inprogress {
position: relative;
}
.ui.label.inprogress::after {
content: '';
opacity: 1;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #ffffff;
border-radius: .28571429rem;
animation: label-progress-active 2.5s ease infinite;
}
.ui.label.blue-yellow {
background: linear-gradient(
to top right,
#2185d0 45%,
#fbbd08 55%);
}
.ui.label.blue-red {
background: linear-gradient(
to top right,
#2185d0 45%,
#db2828 55%);
}
@keyframes label-progress-active {
0% {
opacity: 0.5;
width: 0;
}
100% {
opacity: 0;
width: 100%;
}
}

View File

@ -1,35 +1,42 @@
<div class="ui main container">
<h1 class="ui dividing header">Commit tracker</h1>
<div class="ui raised segments">
<div class="ui segment">
<table *ngIf="commits?.length" class="ui celled table">
<div class="ui main wide-container">
<table *ngIf="commits?.length" class="ui large padded celled table">
<thead>
<tr>
<th><i class="clock outline icon"></i>Last activity</th>
<th class="collapsing"><i class="user icon"></i>Owner</th>
<th class="collapsing"><i class="clock outline icon"></i>Last activity</th>
<th><i class="sticky note outline icon"></i>Change subject</th>
<th><i class="folder open outline icon"></i>Project</th>
<th><i class="code branch icon"></i>Branch</th>
<th><i class="coffee icon"></i>Gerrit</th>
<th><i class="fire extinguisher icon"></i>Status</th>
<th class="collapsing"><i class="folder open outline icon"></i>Project</th>
<th class="collapsing"><i class="code branch icon"></i>Branch</th>
<th class="collapsing"><i class="coffee icon"></i>Gerrit</th>
<th class="collapsing"><i class="fire extinguisher icon"></i>Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let commit of commits" [ngClass]="rowClasses(commit)">
<td class="collapsing">{{commit.owner}}</td>
<td class="collapsing">{{commit.gerrit_time}}</td>
<td>{{commit.gerrit_change_subject}}</td>
<td class="collapsing">{{commit.gerrit_project}}</td>
<td class="collapsing">{{commit.branch}}</td>
<td class="collapsing">{{commit.gerrit_change_number}}</td>
<td class="collapsing">
<div [ngClass]="labelClasses(commit, CommitStatus.Verified)">{{CommitStatus.Verified}}</div>
<div [ngClass]="labelClasses(commit, CommitStatus.CodeReview)">{{CommitStatus.CodeReview}}</div>
<div [ngClass]="labelClasses(commit, CommitStatus.CommitFeedbackLoop)">{{CommitStatus.CommitFeedbackLoop}}</div>
<div [ngClass]="labelClasses(commit, CommitStatus.ShortFeedbackLoop)">{{CommitStatus.ShortFeedbackLoop}}</div>
<div [ngClass]="labelClasses(commit, CommitStatus.NightlyFeedbackLoop)">{{CommitStatus.NightlyFeedbackLoop}}</div>
<div [ngClass]="labelClasses(commit, CommitStatus.Verified)"
[title]="commit.gerrit_verified">{{CommitStatus.Verified}}
</div>
<div [ngClass]="labelClasses(commit, CommitStatus.CodeReview)"
[title]="commit.gerrit_code_review">{{CommitStatus.CodeReview}}
</div>
<div [ngClass]="labelClasses(commit, CommitStatus.CommitFeedbackLoop)"
[title]="commit.cfl_result">{{CommitStatus.CommitFeedbackLoop}}
</div>
<div [ngClass]="labelClasses(commit, CommitStatus.ShortFeedbackLoop)"
[title]="commit.sfl_result">{{CommitStatus.ShortFeedbackLoop}}
</div>
<div [ngClass]="labelClasses(commit, CommitStatus.NightlyFeedbackLoop)"
[title]="commit.nfl_result">{{CommitStatus.NightlyFeedbackLoop}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

View File

@ -6,6 +6,7 @@ import { CommitTrackerService } from '../../shared/service/commit-tracker.servic
import { Commit } from '../../shared/commit';
import { CommitStatus } from '../../shared/commit-status.enum';
import { Result } from '../../shared/result.enum';
import { Build } from '../../shared/build';
@Component({
selector: 'app-commit-tracker',
@ -18,7 +19,8 @@ export class CommitTrackerComponent implements OnInit {
constructor(private commitTrackerService: CommitTrackerService,
private titleService: Title,
private route: ActivatedRoute) {}
private route: ActivatedRoute) {
}
ngOnInit() {
this.titleService.setTitle('Commit-tracker : MTAStv');
@ -44,41 +46,77 @@ export class CommitTrackerComponent implements OnInit {
switch (commitStatus) {
case CommitStatus.Verified:
return Object.assign(classes, {
green: commit.gerrit_verified === 2,
yellow: commit.gerrit_verified === 0,
red: commit.gerrit_verified === -2,
green: commit.gerrit_verified === 1,
red: commit.gerrit_verified === -1,
});
case CommitStatus.CodeReview:
return Object.assign(classes, {
green: commit.gerrit_code_review === 1,
yellow: commit.gerrit_code_review === 0,
red: commit.gerrit_code_review === -1,
green: commit.gerrit_code_review === 2,
yellow: commit.gerrit_code_review === 1 || commit.gerrit_code_review === -1,
red: commit.gerrit_code_review === -2,
});
case CommitStatus.CommitFeedbackLoop:
return Object.assign(classes, {
'blue inprogress': commit.cfl_result === Result.Building,
'blue-yellow inprogress': commit.cfl_result === Result.Building
&& this.hasSubbuildWithCode(commit.cfl_sub_builds, Result.Unstable),
'blue-red inprogress': commit.cfl_result === Result.Building
&& this.hasSubbuildWithCode(commit.cfl_sub_builds, Result.Failure),
green: commit.cfl_result === Result.Success,
yellow: commit.cfl_result === Result.Aborted,
orange: commit.cfl_result === Result.Unstable,
grey: commit.cfl_result === Result.Aborted,
yellow: commit.cfl_result === Result.Unstable,
red: commit.cfl_result === Result.Failure,
brown: commit.cfl_result === Result.NotBuilt,
});
case CommitStatus.ShortFeedbackLoop:
return Object.assign(classes, {
'blue inprogress': commit.sfl_result === Result.Building,
'blue-yellow inprogress': commit.sfl_result === Result.Building
&& this.hasSubbuildWithCode(commit.sfl_sub_builds, Result.Unstable),
'blue-red inprogress': commit.sfl_result === Result.Building
&& this.hasSubbuildWithCode(commit.sfl_sub_builds, Result.Failure),
green: commit.sfl_result === Result.Success,
yellow: commit.sfl_result === Result.Aborted,
orange: commit.sfl_result === Result.Unstable,
grey: commit.sfl_result === Result.Aborted,
yellow: commit.sfl_result === Result.Unstable,
red: commit.sfl_result === Result.Failure,
brown: commit.sfl_result === Result.NotBuilt,
});
case CommitStatus.NightlyFeedbackLoop:
return Object.assign(classes, {
'blue inprogress': commit.nfl_result === Result.Building,
'blue-yellow inprogress': commit.nfl_result === Result.Building
&& this.hasSubbuildWithCode(commit.nfl_sub_builds, Result.Unstable),
'blue-red inprogress': commit.nfl_result === Result.Building
&& this.hasSubbuildWithCode(commit.nfl_sub_builds, Result.Failure),
green: commit.nfl_result === Result.Success,
yellow: commit.nfl_result === Result.Aborted,
grey: commit.nfl_result === Result.Aborted,
yellow: commit.nfl_result === Result.Unstable,
red: commit.nfl_result === Result.Failure,
brown: commit.nfl_result === Result.NotBuilt,
});
default:
return classes;
}
}
return classes;
private hasSubbuildWithCode(build: Build, status: Result): boolean {
if (build === null) {
return false;
}
try {
return build.build.subBuild.some(subBuild => {
let thing = false;
if (subBuild.build.subBuild) {
thing = subBuild.build.subBuild.some(subSubBuild => this.hasSubbuildWithCode(
subSubBuild, status
));
}
return subBuild.result === status || thing;
});
} catch (e) {
return false;
}
}
public rowClasses(commit: Commit) {

View File

@ -1,4 +1,4 @@
export class Member {
signum: String;
name: String;
signum: String = '';
name: String = '';
}

View File

@ -1,5 +1,6 @@
export enum Result {
Aborted = 'ABORTED',
Building = 'BUILDING',
Failure = 'FAILURE',
NotBuilt = 'NOT_BUILT',
Success = 'SUCCESS',

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
@ -21,9 +21,8 @@ export class CommitTrackerService implements Resolve<Array<Commit>> {
}
public getTeamCommits(signums: Array<String>): Observable<Array<Commit>> {
return this.httpClient.post<Array<Commit>>(this.apiEndPoint, {
json_signums: signums
});
const params = new HttpParams().set('json_signums', JSON.stringify(signums));
return this.httpClient.get<Array<Commit>>(this.apiEndPoint, {params});
}
get commits(): Array<Commit> {

View File

@ -1,10 +1,10 @@
import { Member } from './member';
export class Team {
id: Number;
name: String;
members: Array<Member>;
isActive: boolean;
createdAt: String;
updatedAt: String;
id: Number = null;
name: String = '';
members: Array<Member> = [];
isActive = false;
createdAt: String = null;
updatedAt: String = null;
}

View File

@ -1,6 +1,10 @@
/* You can add global styles to this file, and also import other style files */
.main.container {
margin-top: 5em;
margin-top: 2em;
}
.main.wide-container {
margin: 2em 2em 0 2em;
}
.ui.checkbox > label[for] {