src/app/node-details/node-details.component.ts
This component shows details of components or interfaces
selector | app-node-details |
styleUrls | ./node-details.component.scss |
templateUrl | ./node-details.component.html |
Properties |
Methods |
|
Inputs |
constructor(router: Router, componentStoreService: ComponentStoreService, interfaceStoreService: InterfaceStoreService, dialog: MatDialog, notify: UserNotifyService)
|
||||||||||||||||||
Parameters :
|
callback | |
Type : NodeUpdatedCallbackFn
|
|
Function to be called if a node was updated or deleted |
nodeId | |
Type : string
|
|
Id of the node |
nodeType | |
Type : NodeDetailsType
|
|
Either component or interface |
projectId | |
Type : string
|
|
The project that contains the node |
Public getNodeName |
getNodeName()
|
Get the name of the node, or an empty string if it has not been fetched yet
Returns :
string
|
Public getNodeTypeString |
getNodeTypeString()
|
Get the type of the node as a formatted string
Returns :
string
|
Public node |
node()
|
Access the node
Returns :
GetComponentQuery | GetInterfaceQuery
|
Public onCancelClick |
onCancelClick()
|
Returns :
void
|
Public onDeleteClick |
onDeleteClick()
|
Returns :
void
|
Public onEditClick |
onEditClick()
|
Returns :
void
|
Public onSaveClick |
onSaveClick()
|
Returns :
void
|
Private resetValues |
resetValues()
|
Returns :
void
|
Private showDeleteDialog | ||||||
showDeleteDialog(affected: string[])
|
||||||
Parameters :
Returns :
void
|
Private updateComponent |
updateComponent()
|
Returns :
void
|
Private updateInterface |
updateInterface()
|
Returns :
void
|
component |
Type : GetBasicComponentQuery
|
editMode |
Type : boolean
|
interface |
Type : GetInterfaceQuery
|
issueListId |
Type : ListId
|
placeholder |
Type : string
|
Default value : 'placeholder'
|
showName |
Default value : false
|
validationDescription |
Default value : new FormControl('', CCIMSValidators.contentValidator)
|
validationIMS |
Default value : new FormControl('', [Validators.required, CCIMSValidators.urlValidator])
|
validationName |
Default value : new FormControl('', [Validators.required, CCIMSValidators.nameFormatValidator])
|
validationProvider |
Default value : new FormControl('', [Validators.required, CCIMSValidators.urlValidator])
|
validationType |
Default value : new FormControl('')
|
validationUrl |
Default value : new FormControl('', [Validators.required, CCIMSValidators.urlValidator])
|
import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
import {
GetBasicComponentQuery,
GetComponentQuery,
GetInterfaceQuery,
UpdateComponentInput,
UpdateComponentInterfaceInput
} from '../../generated/graphql';
import {FormControl, Validators} from '@angular/forms';
import {ListId, ListType, NodeType} from '@app/data-dgql/id';
import {Router} from '@angular/router';
import {ComponentStoreService} from '@app/data/component/component-store.service';
import {InterfaceStoreService} from '@app/data/interface/interface-store.service';
import {MatDialog} from '@angular/material/dialog';
import {UserNotifyService} from '@app/user-notify/user-notify.service';
import {QueryComponent} from '@app/utils/query-component/query.component';
import {RemoveDialogComponent} from '@app/dialogs/remove-dialog/remove-dialog.component';
import {CCIMSValidators} from '@app/utils/validators';
/**
* A node shown in the details component can either be a component or an interface
*/
export enum NodeDetailsType {
Component,
Interface
}
export declare type NodeUpdatedCallbackFn = (nodeDeleted: boolean) => void;
/**
* This component shows details of components or interfaces
*/
@Component({
selector: 'app-node-details',
templateUrl: './node-details.component.html',
styleUrls: ['./node-details.component.scss']
})
export class NodeDetailsComponent implements OnInit, AfterViewInit {
/**
* The project that contains the node
*/
@Input() projectId: string;
/**
* Id of the node
*/
@Input() nodeId: string;
/**
* Either component or interface
*/
@Input() nodeType: NodeDetailsType;
/**
* Function to be called if a node was updated or deleted
*/
@Input() callback?: NodeUpdatedCallbackFn;
/** @ignore */
@ViewChild('nodeQuery') nodeQuery: QueryComponent;
/** @ignore */
@ViewChild('deleteQuery') deleteQuery: QueryComponent;
/** @ignore */
@ViewChild('updateQuery') updateQuery: QueryComponent;
/** @ignore */
Type = NodeDetailsType;
issueListId: ListId;
component: GetBasicComponentQuery;
interface: GetInterfaceQuery;
editMode: boolean;
showName = false;
placeholder = 'placeholder';
// TODO: Validators
validationProvider = new FormControl('', [Validators.required, CCIMSValidators.urlValidator]);
validationName = new FormControl('', [Validators.required, CCIMSValidators.nameFormatValidator]);
validationUrl = new FormControl('', [Validators.required, CCIMSValidators.urlValidator]);
validationIMS = new FormControl('', [Validators.required, CCIMSValidators.urlValidator]);
validationType = new FormControl('');
validationDescription = new FormControl('', CCIMSValidators.contentValidator);
constructor(
private router: Router,
private componentStoreService: ComponentStoreService,
private interfaceStoreService: InterfaceStoreService,
private dialog: MatDialog,
private notify: UserNotifyService
) {}
ngOnInit(): void {
this.editMode = false;
if (this.nodeType === NodeDetailsType.Component) {
this.issueListId = {
node: {type: NodeType.Component, id: this.nodeId},
type: ListType.Issues
};
} else {
this.issueListId = {
node: {type: NodeType.ComponentInterface, id: this.nodeId},
type: ListType.IssuesOnLocation
};
}
this.validationIMS.setValue('?');
this.validationUrl.setValue('?');
}
ngAfterViewInit() {
if (this.nodeType === NodeDetailsType.Component) {
this.nodeQuery.listenTo(this.componentStoreService.getBasicComponent(this.nodeId), (component) => {
if (component.node) {
this.component = component;
this.validationIMS.setValue('This is a placeholder');
this.validationUrl.setValue(component.node.repositoryURL);
} else {
this.nodeQuery.setError();
}
});
} else if (this.nodeType === NodeDetailsType.Interface) {
this.nodeQuery.listenTo(this.interfaceStoreService.getInterface(this.nodeId), (int) => {
if (int.node) {
this.interface = int;
} else {
this.nodeQuery.setError();
}
});
}
}
/**
* Get the name of the node, or an empty string if it has not been fetched yet
*/
public getNodeName(): string {
if (!this.nodeQuery) {
return '';
}
if (this.nodeQuery.ready()) {
return this.node().node.name;
}
return '';
}
/**
* Get the type of the node as a formatted string
*/
public getNodeTypeString(): string {
return this.nodeType === NodeDetailsType.Interface ? 'Interface' : 'Component';
}
/**
* Access the node
*/
public node(): GetComponentQuery | GetInterfaceQuery {
if (this.nodeType === NodeDetailsType.Component) {
return this.component;
} else if (this.nodeType === NodeDetailsType.Interface) {
return this.interface;
}
}
public onCancelClick(): void {
this.resetValues();
this.editMode = false;
}
public onEditClick(): void {
this.editMode = true;
}
public onDeleteClick(): void {
const affected: string[] = [];
// Collect affected interfaces and components, then show the delete dialog
if (this.nodeType === NodeDetailsType.Component) {
this.deleteQuery.listenTo(this.componentStoreService.getComponentInterfaces(this.nodeId), (interfaces) => {
for (const i of interfaces.node.interfaces.nodes) {
let affectedInterface = `Interface "${i.name}" will be deleted`;
if (i.consumedBy.nodes.length > 0) {
affectedInterface += ', which will affect the following component(s):';
}
affected.push(affectedInterface);
for (const component of i.consumedBy.nodes) {
affected.push(' ' + component.name);
}
}
this.showDeleteDialog(affected);
});
} else if (this.nodeType === NodeDetailsType.Interface) {
this.deleteQuery.listenTo(this.interfaceStoreService.getConsumingComponents(this.nodeId), (components) => {
affected.push('Deleting this interface will affect the following component(s):');
affected.push(' ' + components.node.component.name);
for (const c of components.node.consumedBy.nodes) {
affected.push(' ' + c.name);
}
this.showDeleteDialog(affected);
});
}
}
private showDeleteDialog(affected: string[]): void {
if (this.nodeType === NodeDetailsType.Component) {
const confirmDeleteDialogRef = this.dialog.open(RemoveDialogComponent, {
data: {
title: `Really delete component "${this.component.node.name}"?`,
messages: [
`Are you sure you want to delete the component "${this.component.node.name}"?`,
'This action cannot be undone!'
].concat(affected),
verificationName: this.component.node.name
}
});
confirmDeleteDialogRef.afterClosed().subscribe((deleteData) => {
if (deleteData) {
this.deleteQuery.listenTo(this.componentStoreService.deleteComponent(this.nodeId), () => {
this.notify.notifyInfo(`Successfully deleted component "${this.component.node.name}"`);
if (this.callback) {
this.callback(true);
}
});
}
});
} else if (this.nodeType === NodeDetailsType.Interface) {
const confirmDeleteDialogRef = this.dialog.open(RemoveDialogComponent, {
data: {
title: `Really delete interface "${this.interface.node.name}"?`,
messages: [
`Are you sure you want to delete the interface "${this.interface.node.name}"?`,
'This action cannot be undone!'
].concat(affected),
verificationName: this.interface.node.name
}
});
confirmDeleteDialogRef.afterClosed().subscribe((deleteData) => {
// dialog returns if the deleting was successful
if (deleteData) {
this.deleteQuery.listenTo(this.interfaceStoreService.delete(this.nodeId), () => {
this.notify.notifyInfo(`Successfully deleted interface "${this.interface.node.name}"`);
if (this.callback) {
this.callback(true);
}
});
}
});
}
}
public onSaveClick(): void {
if (this.nodeType === NodeDetailsType.Component) {
this.component.node.name = this.validationName.value;
// FIXME
// this.component.node.ims.imsType = this.validationProvider.value;
this.component.node.description = this.validationDescription.value;
this.updateComponent();
} else if (this.nodeType === NodeDetailsType.Interface) {
this.interface.node.name = this.validationName.value;
this.interface.node.description = this.validationDescription.value;
this.updateInterface();
}
}
private resetValues() {
if (this.nodeType === NodeDetailsType.Component) {
this.validationName.setValue(this.component.node.name);
this.validationIMS.setValue('http://example.ims.com');
// FIXME
// this.validationProvider.setValue(this.component.node.ims.imsType);
this.validationUrl.setValue('http://example.repo.com');
this.validationDescription.setValue(this.component.node.description);
} else if (this.nodeType === NodeDetailsType.Interface) {
this.validationName.setValue(this.interface.node.name);
this.validationDescription.setValue(this.interface.node.description);
}
}
private updateComponent(): void {
const input: UpdateComponentInput = {
component: this.component.node.id,
name: this.component.node.name,
description: this.component.node.description
};
this.updateQuery.listenTo(this.componentStoreService.updateComponent(input), () => {
this.editMode = false;
if (this.callback) {
this.callback(false);
}
});
}
private updateInterface(): void {
const MutationinputData: UpdateComponentInterfaceInput = {
componentInterface: this.interface.node.id,
name: this.interface.node.name,
description: this.interface.node.description
};
this.updateQuery.listenTo(this.interfaceStoreService.update(MutationinputData), () => {
this.editMode = false;
if (this.callback) {
this.callback(false);
}
});
}
}
<app-query-component
#nodeQuery
[errorMessage]="
'Failed to load ' + getNodeTypeString().toLowerCase() + ' information!'
"
>
<ng-template appQueryBody>
<mat-tab-group>
<mat-tab [label]="getNodeTypeString() + ' Issues'">
<div class="details">
<app-issue-list [projectId]="projectId" [listId]="issueListId"></app-issue-list>
</div>
</mat-tab>
<mat-tab [label]="getNodeTypeString() + ' Details'">
<div style="padding: 8px">
<form *ngIf="nodeType == Type.Component">
<div class="row">
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>Name</mat-label>
<input
[readonly]="!this.editMode"
name="name"
[ngModel]="this.node().node.name"
matInput
[formControl]="this.validationName"
/>
<mat-error *ngIf="this.validationName.invalid && this.editMode"> Invalid component name </mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>Repository-URL</mat-label>
<input [readonly]="!this.editMode" name="url" matInput [formControl]="this.validationUrl" />
<mat-error *ngIf="this.validationUrl.invalid && this.editMode"> Enter a valid URL </mat-error>
</mat-form-field>
</div>
<div class="row">
<mat-form-field floatLabel="always" *ngIf="!this.editMode" appearance="outline">
<mat-label>Provider Type</mat-label>
<input
[ngModel]="this.placeholder"
[readonly]="!this.editMode"
name="provider2"
matInput
[formControl]="this.validationProvider"
/>
<mat-error *ngIf="this.validationProvider.invalid && this.editMode"> Enter a valid URL </mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" *ngIf="this.editMode" appearance="outline">
<mat-label>Provider Type (IMS)</mat-label>
<mat-select name="provider" [formControl]="this.validationProvider">
<mat-option value="GITHUB">GitHub</mat-option>
</mat-select>
<mat-error *ngIf="this.validationProvider.invalid && this.editMode"> Select the Provider </mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>IMS-URL</mat-label>
<input name="ims" [readonly]="!this.editMode" matInput [formControl]="this.validationIMS" />
<mat-error *ngIf="this.validationIMS.invalid && this.editMode"> Enter a valid URL </mat-error>
</mat-form-field>
</div>
<div class="row">
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>Description</mat-label>
<textarea
[readonly]="!this.editMode"
[ngModel]="this.node().node.description"
[formControl]="this.validationDescription"
placeholder="No description provided"
class="description-field"
name="description"
matInput
></textarea>
</mat-form-field>
</div>
</form>
<form *ngIf="nodeType == Type.Interface">
<div class="row">
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>Name</mat-label>
<input
[readonly]="!this.editMode"
name="name"
[ngModel]="interface.node.name"
matInput
[formControl]="this.validationName"
/>
<mat-error *ngIf="this.validationName.invalid && this.editMode"> Name your Interface </mat-error>
</mat-form-field>
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>Interface Type</mat-label>
<input readonly matInput [formControl]="this.validationType" />
<mat-error *ngIf="this.validationType.invalid && this.editMode"> Enter a valid URL </mat-error>
</mat-form-field>
</div>
<div class="row">
<mat-form-field floatLabel="always" appearance="outline">
<mat-label>Description</mat-label>
<textarea
[readonly]="!this.editMode"
[ngModel]="interface.node.description"
[formControl]="this.validationDescription"
name="description"
class="description-field"
matInput
></textarea>
</mat-form-field>
</div>
</form>
<div class="row">
<button mat-raised-button *ngIf="!this.editMode" color="primary" (click)="this.onEditClick()">
<mat-icon>edit</mat-icon>
Edit {{ getNodeTypeString().toLowerCase() }}
</button>
<ng-container *ngIf="this.editMode">
<app-query-component
#updateQuery
[errorMessage]="
'Failed to update ' + getNodeTypeString().toLowerCase()
"
>
<button mat-raised-button color="primary" style="width: 100%" (click)="this.onSaveClick()" appQueryButton>
<mat-icon>save</mat-icon>
Save changes
</button>
</app-query-component>
</ng-container>
<button mat-raised-button *ngIf="this.editMode" color="basic" (click)="this.onCancelClick()">
<mat-icon>cancel</mat-icon>
Discard changes
</button>
<app-query-component #deleteQuery errorMessage="Failed to load affected components">
<button mat-raised-button color="warn" style="width: 100%" (click)="this.onDeleteClick()" appQueryButton>
<mat-icon>delete</mat-icon>
Delete {{ getNodeTypeString().toLowerCase() }}
</button>
</app-query-component>
</div>
</div>
</mat-tab>
</mat-tab-group>
</ng-template>
</app-query-component>
./node-details.component.scss
.description-field {
min-height: 50px;
}
.row {
display: flex;
flex-direction: row;
width: 100%;
}
.row > * {
margin-right: 4px;
flex-grow: 1;
}
.row > *:last-child {
margin-right: 0;
flex-grow: 1;
}
:host ::ng-deep .mat-tab-body-wrapper {
height: 100%;
}
:host ::ng-deep .mat-tab-body.mat-tab-body-active {
height: 100%;
}