File

src/app/node-details/node-details.component.ts

Description

This component shows details of components or interfaces

Implements

OnInit AfterViewInit

Metadata

selector app-node-details
styleUrls ./node-details.component.scss
templateUrl ./node-details.component.html

Index

Properties
Methods
Inputs

Constructor

constructor(router: Router, componentStoreService: ComponentStoreService, interfaceStoreService: InterfaceStoreService, dialog: MatDialog, notify: UserNotifyService)
Parameters :
Name Type Optional
router Router No
componentStoreService ComponentStoreService No
interfaceStoreService InterfaceStoreService No
dialog MatDialog No
notify UserNotifyService No

Inputs

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

Methods

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 :
Name Type Optional
affected string[] No
Returns : void
Private updateComponent
updateComponent()
Returns : void
Private updateInterface
updateInterface()
Returns : void

Properties

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%;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""