Skip to content

Commit

Permalink
Implement Edit Project dialog (#1003)
Browse files Browse the repository at this point in the history
* implement edit project functionality

* add unit test

* add e2e test
  • Loading branch information
kgroschoff authored and kubermatic-bot committed Jan 23, 2019
1 parent 7e67fce commit 5022de6
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 8 deletions.
19 changes: 19 additions & 0 deletions e2e/src/projects/projects.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export class ProjectsPage extends NavPage {
private _deleteProjectDialog = by.id('km-delete-project-dialog');
private _deleteProjectDialogInput = by.id('km-delete-project-dialog-input');
private _deleteProjectDialogButton = by.id('km-delete-project-dialog-btn');
private _editProjectDialogEditBtn= by.id('km-edit-project-dialog-edit-btn');
private _editProjectDialogInput = by.id('km-edit-project-dialog-input');
private _editProjectDialog = by.id('km-edit-project-dialog');

navigateTo(): any {
return this.getProjectsNavButton().click();
Expand Down Expand Up @@ -50,4 +53,20 @@ export class ProjectsPage extends NavPage {
getDeleteProjectDialogButton(): any {
return element(this._deleteProjectDialogButton);
}

getProjectEditBtn(projectName: string): any {
return element(by.id(`km-edit-project-${projectName}`));
}

getEditProjectDialogEditBtn(): any {
return element(this._editProjectDialogEditBtn);
}

getEditProjectDialogInput(): any {
return element(this._editProjectDialogInput);
}

getEditProjectDialog(): any {
return element(this._editProjectDialog);
}
}
27 changes: 25 additions & 2 deletions e2e/src/stories/basic/basic.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('Basic story', () => {
const dexPage = new DexPage();
const membersPage = new MembersPage();

const projectName = 'e2e-test-project';
let projectName = 'e2e-test-project';
const clusterName = 'e2e-test-cluster';
const providerName = 'bringyourown';
const datacenterLocation = 'Frankfurt';
Expand Down Expand Up @@ -167,8 +167,31 @@ describe('Basic story', () => {
expect(clustersPage.getClusterItem(clusterName).isPresent()).toBeFalsy();
});

it('should delete created project', () => {
it('should edit created project name', async () => {
const oldProjectName = projectName;
projectsPage.navigateTo();
KMElement.waitForNotifications();
KMElement.waitToAppear(projectsPage.getProjectEditBtn(projectName));
projectsPage.getProjectEditBtn(projectName).click();
expect(projectsPage.getEditProjectDialog().isPresent()).toBeTruthy();

KMElement.waitToAppear(projectsPage.getEditProjectDialogInput());
projectName = 'e2e-test-project-2';
KMElement.sendKeys(projectsPage.getEditProjectDialogInput(), projectName);
KMElement.waitForClickable(projectsPage.getEditProjectDialogEditBtn());
projectsPage.getEditProjectDialogEditBtn().click();

KMElement.waitToDisappear(projectsPage.getEditProjectDialog());

// Switch views to reload members list
clustersPage.navigateTo();
projectsPage.navigateTo();

KMElement.waitToAppear(projectsPage.getProjectItem(projectName));
expect(await projectsPage.getProjectItem(projectName).getText()).not.toEqual(oldProjectName);
});

it('should delete created project', () => {
KMElement.waitForNotifications();
KMElement.waitToAppear(projectsPage.getDeleteProjectButton(projectName));
projectsPage.getDeleteProjectButton(projectName).click();
Expand Down
3 changes: 3 additions & 0 deletions src/app/core/components/sidenav/sidenav.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class SidenavComponent implements OnInit, OnDestroy {
for (const i in this.projects) {
if (this.projectService.compareProjectsEquality(this.projects[i], data)) {
this.selectedProject = data;
if (this.projects[i].name !== data.name) {
this.loadProjects();
}
return;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/app/core/services/api/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {CreateMemberEntity, MemberEntity} from '../../../shared/entity/MemberEnt
import {NodeDeploymentEntity} from '../../../shared/entity/NodeDeploymentEntity';
import {NodeDeploymentPatch} from '../../../shared/entity/NodeDeploymentPatch';
import {NodeEntity} from '../../../shared/entity/NodeEntity';
import {ProjectEntity} from '../../../shared/entity/ProjectEntity';
import {EditProjectEntity, ProjectEntity} from '../../../shared/entity/ProjectEntity';
import {AzureSizes} from '../../../shared/entity/provider/azure/AzureSizeEntity';
import {DigitaloceanSizes} from '../../../shared/entity/provider/digitalocean/DropletSizeEntity';
import {OpenstackFlavor, OpenstackNetwork, OpenstackSecurityGroup, OpenstackSubnet, OpenstackTenant,} from '../../../shared/entity/provider/openstack/OpenstackSizeEntity';
Expand Down Expand Up @@ -75,6 +75,11 @@ export class ApiService {
return this.http.post<ProjectEntity>(url, createProjectModel, {headers: this.headers});
}

editProject(projectID: string, editProjectEntity: EditProjectEntity): Observable<any> {
const url = `${this.restRoot}/projects/${projectID}`;
return this.http.put(url, editProjectEntity, {headers: this.headers});
}

deleteProject(projectID: string): Observable<any> {
const url = `${this.restRoot}/projects/${projectID}`;
return this.http.delete(url, {headers: this.headers});
Expand Down
31 changes: 31 additions & 0 deletions src/app/project/edit-project/edit-project.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div id="km-edit-project-dialog" class="km-dialog-box">
<mat-toolbar>
Edit Project
<button mat-icon-button mat-dialog-close>
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</mat-toolbar>
<mat-dialog-content>
<div class="mat-dialog-content">
<form [formGroup]="editProjectForm" fxLayout="column">
<p>You are on the way to edit the project name for "{{project.name}}".</p>
<mat-form-field fxFlex>
<input id="km-edit-project-dialog-input" matInput formControlName="name" type="text" placeholder="New project name*:" autocomplete="off">
<mat-error *ngIf="editProjectForm.controls.name.hasError('required')">
Name is <strong>required</strong>
</mat-error>
</mat-form-field>

</form>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-dialog-close mat-raised-button>
Close
</button>
<button id="km-edit-project-dialog-edit-btn" mat-raised-button color="primary" (click)="editProject()"
[disabled]="!editProjectForm.valid">
Edit
</button>
</mat-dialog-actions>
</div>
79 changes: 79 additions & 0 deletions src/app/project/edit-project/edit-project.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import {MatDialogRef} from '@angular/material';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {SlimLoadingBarModule} from 'ng2-slim-loading-bar';
import Spy = jasmine.Spy;
import {ApiService} from '../../core/services';
import {SharedModule} from '../../shared/shared.module';
import {fakeProject} from '../../testing/fake-data/project.fake';
import {asyncData} from '../../testing/services/api-mock.service';
import {MatDialogRefMock} from '../../testing/services/mat-dialog-ref-mock';
import {EditProjectComponent} from './edit-project.component';

const modules: any[] = [
BrowserModule,
BrowserAnimationsModule,
SlimLoadingBarModule.forRoot(),
SharedModule,
];

describe('EditProjectComponent', () => {
let fixture: ComponentFixture<EditProjectComponent>;
let component: EditProjectComponent;
let editProjectSpy: Spy;

beforeEach(async(() => {
const apiMock = jasmine.createSpyObj('ApiService', ['editProject']);
editProjectSpy = apiMock.editProject.and.returnValue(asyncData(fakeProject()));

TestBed
.configureTestingModule({
imports: [
...modules,
],
declarations: [
EditProjectComponent,
],
providers: [
{provide: MatDialogRef, useClass: MatDialogRefMock},
{provide: ApiService, useValue: apiMock},
],
})
.compileComponents();
}));

beforeEach(async(() => {
fixture = TestBed.createComponent(EditProjectComponent);
component = fixture.componentInstance;
component.project = fakeProject();
fixture.detectChanges();
}));

it('should create the edit project component', async(() => {
expect(component).toBeTruthy();
}));

it('should have invalid form after creating', () => {
expect(component.editProjectForm.valid).toBeFalsy();
});

it('should have required fields', () => {
expect(component.editProjectForm.valid).toBeFalsy('form is initially not valid');
expect(component.editProjectForm.controls.name.valid).toBeFalsy('name field is initially not valid');
expect(component.editProjectForm.controls.name.hasError('required'))
.toBeTruthy('name field has initially required error');

component.editProjectForm.controls.name.patchValue('new-project-name');
expect(component.editProjectForm.controls.name.hasError('required'))
.toBeFalsy('name field has no required error after setting name');
});

it('should call editProject method', fakeAsync(() => {
component.editProjectForm.controls.name.patchValue('new-project-name');
component.editProject();
tick();

expect(editProjectSpy.and.callThrough()).toHaveBeenCalled();
}));
});
34 changes: 34 additions & 0 deletions src/app/project/edit-project/edit-project.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {Component, Input, OnInit} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';
import {MatDialogRef} from '@angular/material';
import {ApiService} from '../../core/services';
import {NotificationActions} from '../../redux/actions/notification.actions';
import {EditProjectEntity, ProjectEntity} from '../../shared/entity/ProjectEntity';

@Component({
selector: 'kubermatic-edit-project',
templateUrl: './edit-project.component.html',
})
export class EditProjectComponent implements OnInit {
@Input() project: ProjectEntity;
editProjectForm: FormGroup;

constructor(private api: ApiService, private dialogRef: MatDialogRef<EditProjectComponent>) {}

ngOnInit(): void {
this.editProjectForm = new FormGroup({
name: new FormControl('', [Validators.required]),
});
}

editProject(): void {
const editProjectEntity: EditProjectEntity = {
name: this.editProjectForm.controls.name.value,
};

this.api.editProject(this.project.id, editProjectEntity).subscribe((res) => {
this.dialogRef.close(res);
NotificationActions.success('Success', `Project has been edited successfully`);
});
}
}
4 changes: 4 additions & 0 deletions src/app/project/project-item/project-item.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
</div>
<div fxFlex="17%" class="km-project-item-actions">
<p class="km-icon">
<button [attr.id]="'km-edit-project-' + project.name"
class="km-icon-edit black"
(click)="editProject()"
[disabled]="!!userGroup && !userGroupConfig[userGroup].projects.edit"></button>
<button [attr.id]="'km-delete-project-' + project.name"
class="km-icon-delete black"
(click)="deleteProject()"
Expand Down
12 changes: 10 additions & 2 deletions src/app/project/project-item/project-item.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ p {

&.km-icon {
padding-right: 20px;
height: 21px;
width: 21px;

.km-icon-edit {
margin-right: 15px;
}

.km-icon-edit,
.km-icon-delete {
height: 21px;
width: 21px;
}
}
}

Expand Down
17 changes: 16 additions & 1 deletion src/app/project/project-item/project-item.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {AppConfigService} from '../../app-config.service';
import {ProjectService, UserService} from '../../core/services';
import {ProjectEntity} from '../../shared/entity/ProjectEntity';
import {UserGroupConfig} from '../../shared/model/Config';
import {EditProjectComponent} from '../edit-project/edit-project.component';
import {ProjectDeleteConfirmationComponent} from '../project-delete-confirmation/project-delete-confirmation.component';

@Component({
Expand All @@ -15,6 +16,7 @@ export class ProjectItemComponent implements OnInit {
@Input() index: number;
@Input() project: ProjectEntity;
clickedDeleteProject = {};
clickedEditProject = {};
userGroup: string;
userGroupConfig: UserGroupConfig;

Expand All @@ -36,11 +38,24 @@ export class ProjectItemComponent implements OnInit {
}

selectProject(): void {
if (!this.clickedDeleteProject[this.project.id]) {
if (!this.clickedDeleteProject[this.project.id] && !this.clickedEditProject[this.project.id]) {
this.projectService.changeAndStoreSelectedProject(this.project);
}
}

editProject(): void {
this.clickedEditProject[this.project.id] = true;
const modal = this.dialog.open(EditProjectComponent);
modal.componentInstance.project = this.project;
const sub = modal.afterClosed().subscribe((edited) => {
if (!!edited) {
this.projectService.changeAndStoreSelectedProject(edited);
}
delete this.clickedEditProject[this.project.id];
sub.unsubscribe();
});
}

deleteProject(): void {
this.clickedDeleteProject[this.project.id] = true;
const modal = this.dialog.open(ProjectDeleteConfirmationComponent);
Expand Down
4 changes: 3 additions & 1 deletion src/app/project/project.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {NgModule} from '@angular/core';
import {SharedModule} from '../shared/shared.module';
import {EditProjectComponent} from './edit-project/edit-project.component';
import {ProjectDeleteConfirmationComponent} from './project-delete-confirmation/project-delete-confirmation.component';
import {ProjectItemComponent} from './project-item/project-item.component';
import {ProjectRoutingModule} from './project-routing.module';
Expand All @@ -9,6 +10,7 @@ const components: any[] = [
ProjectComponent,
ProjectItemComponent,
ProjectDeleteConfirmationComponent,
EditProjectComponent,
];

@NgModule({
Expand All @@ -22,7 +24,7 @@ const components: any[] = [
exports: [
...components,
],
entryComponents: [ProjectDeleteConfirmationComponent],
entryComponents: [ProjectDeleteConfirmationComponent, EditProjectComponent],
})
export class ProjectModule {
}
4 changes: 4 additions & 0 deletions src/app/shared/entity/ProjectEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ export class ProjectEntity {
name: string;
status: string;
}

export class EditProjectEntity {
name: string;
}
6 changes: 5 additions & 1 deletion src/app/testing/services/api-mock.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {defer, Observable, of} from 'rxjs';
import {ClusterEntity, MasterVersion, Token} from '../../shared/entity/ClusterEntity';
import {CreateMemberEntity, MemberEntity} from '../../shared/entity/MemberEntity';
import {NodeEntity} from '../../shared/entity/NodeEntity';
import {ProjectEntity} from '../../shared/entity/ProjectEntity';
import {EditProjectEntity, ProjectEntity} from '../../shared/entity/ProjectEntity';
import {VSphereNetwork} from '../../shared/entity/provider/vsphere/VSphereEntity';
import {SSHKeyEntity} from '../../shared/entity/SSHKeyEntity';
import {CreateClusterModel} from '../../shared/model/CreateClusterModel';
Expand Down Expand Up @@ -43,6 +43,10 @@ export class ApiMockService {
return of(this.project);
}

editProject(projectID: string, editProjectEntity: EditProjectEntity): Observable<any> {
return of(this.project);
}

deleteProject(projectID: string): Observable<any> {
return of(null);
}
Expand Down

0 comments on commit 5022de6

Please sign in to comment.