File

src/app/graphs/issue-graph-controls/issue-graph-controls.component.ts

Description

This component contains the graph toggles, the search bar and the button for creating new components. Additionally it contains the actual graph component and feeds data to it. This component collects the state of the search bar and graph toggles, combines it and emits it via this.filter$. Another observable retrieved from the IssueGraphStateService maps these values into the graph data matching the filters. Whenever new graph data arrives it is feed to the actual graph component. (see ngAfterViewInit)

Implements

AfterViewInit OnDestroy

Metadata

selector app-issue-graph-controls
styleUrls ./issue-graph-controls.component.scss
templateUrl ./issue-graph-controls.component.html

Index

Properties
Methods

Constructor

constructor(dialog: MatDialog, gs: IssueGraphStateService, route: ActivatedRoute)
Parameters :
Name Type Optional
dialog MatDialog No
gs IssueGraphStateService No
route ActivatedRoute No

Methods

Private getSelectedCategories
getSelectedCategories()

Gathers booleans indicating whether the toggle switches coressponding to values in IssueCategory are turned on or off

Returns : SelectedCategories
layoutGraph
layoutGraph()
Returns : void
setRelationVisibility
setRelationVisibility()

Tell the graph component whether to show issue relations or not.

Returns : void
Public updateSelectedCategories
updateSelectedCategories()

Emit newly selected categories via this.selectedCategories$

Returns : void

Properties

bug
Default value : true
Private destroy$
Default value : new ReplaySubject<void>(1)
Public dialog
Type : MatDialog
featureRequests
Default value : true
filter$
Type : BehaviorSubject<FilterState>
issueGraph
Type : IssueGraphComponent
Decorators :
@ViewChild(IssueGraphComponent)
labelSearch
Type : LabelSearchComponent
Decorators :
@ViewChild(LabelSearchComponent)
projectId
Type : string
Public selectedCategories$
Default value : new BehaviorSubject<SelectedCategories>(this.getSelectedCategories())
showRelations
Default value : true
unclassified
Default value : true
import {AfterViewInit, Component, OnDestroy, ViewChild} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {ActivatedRoute} from '@angular/router';
import {IssueGraphComponent} from '../issue-graph/issue-graph.component';
import {IssueCategory} from 'src/generated/graphql';
import {BehaviorSubject, combineLatest, ReplaySubject} from 'rxjs';
import {SelectedCategories} from '../shared';
import {IssueGraphStateService} from '../../data/issue-graph/issue-graph-state.service';
import {LabelSearchComponent} from '../label-search/label-search.component';
import {map, takeUntil} from 'rxjs/operators';
import {FilterState} from '@app/graphs/shared';

/**
 * This component contains the graph toggles, the search bar and the button
 * for creating new components. Additionally it contains the actual graph component and feeds
 * data to it. This component collects the state of the search bar and graph toggles, combines it and emits it via this.filter$.
 * Another observable retrieved from the IssueGraphStateService maps these values into the graph
 * data matching the filters. Whenever new graph data arrives it is feed to the actual graph component. (see ngAfterViewInit)
 */
@Component({
  selector: 'app-issue-graph-controls',
  templateUrl: './issue-graph-controls.component.html',
  styleUrls: ['./issue-graph-controls.component.scss']
})
export class IssueGraphControlsComponent implements AfterViewInit, OnDestroy {
  @ViewChild(IssueGraphComponent) issueGraph: IssueGraphComponent;
  @ViewChild(LabelSearchComponent) labelSearch: LabelSearchComponent;

  projectId: string;

  // these 3 booleans are bound to the issue category toggles via ngModel
  featureRequests = true;
  bug = true;
  unclassified = true;

  showRelations = true;
  // emits state of toggles and search bar combined
  filter$: BehaviorSubject<FilterState>;
  private destroy$ = new ReplaySubject<void>(1);

  constructor(public dialog: MatDialog, private gs: IssueGraphStateService, private route: ActivatedRoute) {
    this.projectId = this.route.snapshot.paramMap.get('id');
    this.filter$ = new BehaviorSubject({
      selectedCategories: this.getSelectedCategories(),
      selectedFilter: {
        labels: [],
        texts: []
      }
    });
  }

  public selectedCategories$ = new BehaviorSubject<SelectedCategories>(this.getSelectedCategories());

  /**
   * Emit newly selected categories via this.selectedCategories$
   */
  public updateSelectedCategories(): void {
    this.selectedCategories$.next(this.getSelectedCategories());
  }

  /**
   * Gathers booleans indicating whether the toggle switches
   * coressponding to values in IssueCategory are turned on or off
   */
  private getSelectedCategories(): SelectedCategories {
    return {
      [IssueCategory.Bug]: this.bug,
      [IssueCategory.FeatureRequest]: this.featureRequests,
      [IssueCategory.Unclassified]: this.unclassified
    };
  }

  layoutGraph(): void {
    this.issueGraph.layoutGraph();
    this.issueGraph.drawGraph();
    this.issueGraph.fitGraphInView();
  }

  /**
   * Setup this.filter$ and create subscription for observable returned from graphDataForFilter
   */
  ngAfterViewInit(): void {
    // sets up emission of values representing the state of the graph toggles and the search bar via this.filter$
    combineLatest([this.selectedCategories$, this.labelSearch.filterSelection$])
      .pipe(
        takeUntil(this.destroy$),
        map(([selectedCategories, filterSelection]) => ({
          selectedCategories,
          selectedFilter: filterSelection
        }))
      )
      .subscribe((filterState) => this.filter$.next(filterState));

    // gets an obervable from GraphStateService that emits the matching graph state
    // after this component emits values on this.filter$ or the IssueGraphComponent
    // signals the need for a reload via this.issueGraph.reload$. Whenever new graph state
    // arrives we pass it to the graph and issue a redraw on it.
    this.gs
      .graphDataForFilter(this.filter$, this.issueGraph.reload$, this.destroy$)
      .pipe(takeUntil(this.destroy$))
      .subscribe((graphData) => {
        this.issueGraph.graphData = graphData;
        this.issueGraph.drawGraph();
      });
  }

  /**
   * Tell the graph component whether to show issue relations or not.
   *
   */
  setRelationVisibility(): void {
    this.issueGraph.setRelationVisibility(this.showRelations);
  }

  /**
   * Cancel subscriptions by emitting a value on this.destroy$
   */
  ngOnDestroy() {
    this.destroy$.next();
  }
}
<!--Graph page controls-->
<div class="container">
  <!--Controls-->
  <div class="controls">
    <!--Feature Requests toggle-->
    <mat-slide-toggle
      title="Feature Requests"
      [(ngModel)]="featureRequests"
      matTooltip="Feature Requests"
      class="slide-toggle"
      [checked]="true"
      (change)="updateSelectedCategories()"
    >
      <mat-icon class="feature-request-icon">emoji_objects</mat-icon>
    </mat-slide-toggle>

    <!--Bug Reports toggle-->
    <mat-slide-toggle
      title="Bug Reports"
      [(ngModel)]="bug"
      matTooltip="Bug Reports"
      class="slide-toggle"
      [checked]="true"
      (change)="updateSelectedCategories()"
    >
      <mat-icon class="bug-report-icon">bug_report</mat-icon>
    </mat-slide-toggle>

    <!--Unclassified Issues toggle-->
    <mat-slide-toggle
      title="Unclassified Issues"
      [(ngModel)]="unclassified"
      matTooltip="Unclassified Issues"
      class="slide-toggle"
      [checked]="true"
      (change)="updateSelectedCategories()"
    >
      <mat-icon class="unclassified-icon">help</mat-icon>
    </mat-slide-toggle>

    <!--Issue Relations toggle-->
    <mat-slide-toggle
      title="Issue Relations"
      [(ngModel)]="showRelations"
      matTooltip="Issue Relations"
      class="slide-toggle"
      [checked]="true"
      (change)="setRelationVisibility()"
    >
      <mat-icon class="relation-edge-icon" svgIcon="relation-edge"></mat-icon>
    </mat-slide-toggle>

    <!--Center Focus button-->
    <button mat-icon-button title="Fit Graph Into View" (click)="this.issueGraph.fitGraphInView()" style="margin-left: 20px">
      <mat-icon>center_focus_strong</mat-icon>
    </button>

    <!--Dashboard button-->
    <button mat-icon-button title="Layout Graph" (click)="this.layoutGraph()" style="margin-left: 20px">
      <mat-icon>dashboard</mat-icon>
    </button>

    <!--Search Label field-->
    <app-label-search title="Label Search" #labelSearch></app-label-search>

    <!--Create New Component button-->
    <button
      mat-fab
      title="Create New Component"
      color="primary"
      class="create-component-button"
      (click)="this.issueGraph.openCreateComponentDialog()"
    >
      <mat-icon>add</mat-icon>
    </button>
  </div>

  <!--?-->
  <app-issue-graph [projectId]="projectId"></app-issue-graph>
</div>

./issue-graph-controls.component.scss

@import "~@angular/material/theming";
@import "variables";

.container {
  display: flex;
  flex-direction: column;
  height: 100%;
  width: 100%;
  margin: 0;
}
.container app-issue-graph {
  flex-grow: 1;
}

.controls {
  display: flex;
  flex-direction: row;
  background-color: $background-controls;
  border-bottom: 1px solid $border-color-controls;
}

.controls app-label-search {
  display: inline-block;
  height: 44px;
  flex-grow: 1;
  margin-left: 20px;
}

.slide-toggle {
  margin-left: 20px;
  margin-top: 10px;
  margin-bottom: 10px;
}

.bug-report-icon {
  color: red;
}

.feature-request-icon {
  color: #005eff;
}

.feature-request-icon,
.bug-report-icon,
.notification-icon,
.unclassified-icon {
  margin-top: 7px;
}

.relation-edge-icon {
  margin-top: 12px;
}
.create-component-button {
  margin: 10px;
  margin-left: 20px;
  right: 10px;
}

.spacer {
  flex: 1 1 auto;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""