File

src/app/dialogs/create-issue-dialog/create-issue-dialog.component.ts

Implements

OnInit

Metadata

selector app-create-issue-dialog
styleUrls ./create-issue-dialog.component.scss
templateUrl ./create-issue-dialog.component.html

Index

Properties
Methods

Constructor

constructor(dialogRef: MatDialogRef<CreateIssueDialogComponent>, dialog: MatDialog, dataService: DataService, data: DialogData, notify: UserNotifyService)
Parameters :
Name Type Optional
dialogRef MatDialogRef<CreateIssueDialogComponent> No
dialog MatDialog No
dataService DataService No
data DialogData No
notify UserNotifyService No

Methods

afterAlertClose
afterAlertClose()
Returns : void
onCreate
onCreate()
Returns : void
onNoClick
onNoClick(showConfirmDialog: boolean)
Parameters :
Name Type Optional
showConfirmDialog boolean No
Returns : void
Private updateSelectedItems
updateSelectedItems()

Updates items to be selected in the Create Issue page, including 1) components and 2) locations.

Returns : void

Properties

body
Decorators :
@ViewChild('body')
category
Default value : new FormControl('', [Validators.required])
Public data
Type : DialogData
Decorators :
@Inject(MAT_DIALOG_DATA)
Public dialogRef
Type : MatDialogRef<CreateIssueDialogComponent>
Public issueData
Type : LocalIssueData
Default value : { components: [], locations: [], labels: [], assignees: [], linksToIssues: [] }
Public loading
Default value : false
Public saveFailed
Default value : false
title
Default value : new FormControl('', [CCIMSValidators.nameValidator, Validators.required])
import {Component, Inject, OnInit, ViewChild} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from '@angular/material/dialog';
import {FormControl, Validators} from '@angular/forms';
import {IssueCategory} from '../../../generated/graphql';
import {UserNotifyService} from '@app/user-notify/user-notify.service';
import {CCIMSValidators} from '@app/utils/validators';
import {CreateIssueInput} from '../../../generated/graphql-dgql';
import {NodeId, NodeType} from '@app/data-dgql/id';
import DataService from '@app/data-dgql';
import {LocalIssueData} from '@app/issue-detail/issue-sidebar.component';
import {RemoveDialogComponent} from '@app/dialogs/remove-dialog/remove-dialog.component';

@Component({
  selector: 'app-create-issue-dialog',
  templateUrl: './create-issue-dialog.component.html',
  styleUrls: ['./create-issue-dialog.component.scss']
})
/**
 * This component opens a dialog for the issue creation.
 */
export class CreateIssueDialogComponent implements OnInit {
  @ViewChild('body') body;

  public loading = false;
  public saveFailed = false;

  constructor(
    public dialogRef: MatDialogRef<CreateIssueDialogComponent>,
    private dialog: MatDialog,
    private dataService: DataService,
    @Inject(MAT_DIALOG_DATA) public data: DialogData,
    private notify: UserNotifyService
  ) {}

  // form controls for the form fields
  title = new FormControl('', [CCIMSValidators.nameValidator, Validators.required]);
  category = new FormControl('', [Validators.required]);

  public issueData: LocalIssueData = {
    components: [],
    locations: [],
    labels: [],
    assignees: [],
    linksToIssues: []
  };

  ngOnInit(): void {
    // sets up the issue category as Unclassified
    this.category.setValue(IssueCategory.Unclassified);
    this.dialogRef.disableClose = true;

    // updates items to be selected
    this.updateSelectedItems();
  }

  /**
   * Updates items to be selected in the Create Issue page,
   * including 1) components and 2) locations.
   */
  private updateSelectedItems() {
    // updates components
    for (const componentId of this.data.components) {
      this.issueData.components.push(componentId);
      this.issueData.locations.push(componentId);
    }

    if (this.data.locations) {
      // updates locations
      for (const componentId of this.data.locations) {
        this.issueData.locations.push(componentId);
      }
    }
  }

  onNoClick(showConfirmDialog: boolean): void {
    if (showConfirmDialog) {
      this.dialog
        .open(RemoveDialogComponent, {
          data: {
            title: 'Really discard issue?',
            messages: ['Are you sure you want to discard this issue?'],
            confirmButtonText: 'Confirm'
          }
        })
        .afterClosed()
        .subscribe((close) => {
          if (close) {
            this.dialogRef.close();
          }
        });
    } else {
      this.dialogRef.close();
    }
  }

  afterAlertClose(): void {
    this.saveFailed = false;
  }

  onCreate(): void {
    const issueData: CreateIssueInput = {
      title: this.title.value,
      body: this.body.code,
      category: this.category.value,
      clientMutationID: Math.random().toString(36),
      components: this.issueData.components.map((node) => node.id),
      locations: this.issueData.locations.map((node) => node.id),
      labels: this.issueData.labels.map((node) => node.id),
      assignees: this.issueData.assignees.map((node) => node.id)
    };
    this.loading = true;
    this.saveFailed = false;
    this.dataService.mutations
      .createIssue(issueData)
      .then(async (result) => {
        const issueId = {type: NodeType.Issue, id: result.id};
        const promises = [];
        for (const linked of this.issueData.linksToIssues) {
          promises.push(
            this.dataService.mutations.linkIssue(Math.random().toString(), issueId, linked).catch((err) => {
              this.notify.notifyError('Failed to link issue!', err);
              // aborting on this error would cause weird non-recoverable state so we won't rethrow it
            })
          );
        }
        await Promise.all(promises);

        this.dialogRef.close(true);
      })
      .catch((err) => {
        this.notify.notifyError('Failed to create issue!', err);
        this.saveFailed = true;
      })
      .finally(() => {
        this.loading = false;
      });
  }
}

/**
 * Interface that defines what data is injected to the dialog.
 */
export interface DialogData {
  projectId: string;
  // initial state of the issue's component list
  components: NodeId[];
  // initial state of the issue's location list
  locations: NodeId[];
}
<h1 mat-dialog-title>Create Issue</h1>
<div mat-dialog-content>
  <form>
    <!-- Issue title -->
    <mat-form-field class="stretch" appearance="outline">
      <mat-label>Title</mat-label>
      <input matInput [formControl]="title" required />
      <mat-error *ngIf="title.invalid">Invalid issue title</mat-error>
    </mat-form-field>

    <!-- Issue description -->
    <app-markdown-editor #body></app-markdown-editor>

    <!-- Issue category -->
    <!-- FIXME: Extract into component (used in issue details page as well) -->
    <mat-button-toggle-group class="category-selector" [formControl]="category" required>
      <mat-button-toggle value="UNCLASSIFIED">
        <mat-icon [svgIcon]="'issue-uncategorized'"></mat-icon>
        Unclassified
      </mat-button-toggle>
      <mat-button-toggle value="BUG">
        <mat-icon [svgIcon]="'issue-bug'"></mat-icon>
        Bug
      </mat-button-toggle>
      <mat-button-toggle value="FEATURE_REQUEST">
        <mat-icon [svgIcon]="'issue-feature'"></mat-icon>
        Feature Request
      </mat-button-toggle>
    </mat-button-toggle-group>

    <!-- Issue data -->
    <app-issue-sidebar [(localIssue)]="issueData" [projectId]="data.projectId"></app-issue-sidebar>

    <!-- Save failed error message -->
    <!-- FIXME: This should use material styling -->
    <nz-alert
      *ngIf="this.saveFailed"
      class="error-message"
      nzType="error"
      nzMessage="Something went wrong"
      nzShowIcon
      nzCloseable
      (nzOnClose)="afterAlertClose()"
    ></nz-alert>
  </form>
</div>

<div mat-dialog-actions class="actions">
  <!-- Cancel button, stops the issue creation process -->
  <button mat-raised-button color="warn" (click)="onNoClick(body.code?.length > 0 || title.value.length > 0)">Cancel</button>

  <!-- Create button, finishes the issue creation process -->
  <button
    mat-raised-button
    color="success"
    [class.spinner]="loading"
    [disabled]="loading || title.invalid || category.invalid"
    (click)="onCreate()"
  >
    Create
  </button>
</div>

./create-issue-dialog.component.scss

@import "src/styles/dialog";

.category-selector {
  width: 100%;
  margin-bottom: 8px;

  mat-button-toggle {
    width: 100%;

    ::ng-deep &.mat-button-toggle-checked {
      font-weight: 600;
    }
  }
}

@keyframes spinner {
  to {
    transform: rotate(360deg);
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""