Skip to content

Commit

Permalink
feat: Pre solve metrics panel updates (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmccollum-woolpert committed Jul 12, 2024
1 parent f9742e1 commit 77bb215
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
ViewChild,
} from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable, Subscription, combineLatest } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { exhaustMap, map, take } from 'rxjs/operators';
import * as fromConfig from 'src/app/core/selectors/config.selectors';
import RoutesChartSelectors from 'src/app/core/selectors/routes-chart.selectors';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,57 @@
<div class="metric-box">
<div class="mb-1 metric-box-title">Shipments in total</div>
<div class="mb-0 metric-box-value">
{{ (kpis$ | async).shipmentKpis.selected }} / {{ (kpis$ | async).shipmentKpis.total }}
{{ kpis.shipmentKpis.selected }} / {{ kpis.shipmentKpis.total }}
</div>
</div>
<div class="metric-box">
<div class="mb-1 metric-box-title">Total load demands</div>
<div class="mb-0 metric-box-value" *ngIf="!(kpis$ | async).shipmentKpis.demands?.length">-</div>
<div class="mb-0 metric-box-value" *ngFor="let demand of (kpis$ | async).shipmentKpis.demands">
{{ demand.selected | number }} {{ demand.type }}
<div class="mb-0 metric-box-value" *ngIf="kpis.shipmentKpis.demands?.length; else noValue">
{{ kpis.shipmentKpis.demands[0].selected | number }} {{ kpis.shipmentKpis.demands[0].type }}
</div>
<div class="mb-0 metric-box-value" *ngIf="kpis.shipmentKpis.demands?.length > 1">
{{ kpis.shipmentKpis.demands[1].selected | number }} {{ kpis.shipmentKpis.demands[1].type }}
</div>
<div class="metric-box-value" *ngIf="kpis.shipmentKpis.demands?.length > 2">
<a (click)="showAllKpis()">View more...</a>
</div>
</div>
<div class="metric-box">
<div class="mb-1 metric-box-title">Total vehicle capacity</div>
<div class="mb-0 metric-box-value" *ngIf="kpis.vehicleKpis.capacities?.length; else noValue">
{{ kpis.vehicleKpis.capacities[0].selected | number }}
{{ kpis.vehicleKpis.capacities[0].type }}
</div>
<div class="mb-0 metric-box-value" *ngIf="kpis.vehicleKpis.capacities?.length > 1">
{{ kpis.vehicleKpis.capacities[1].selected | number }}
{{ kpis.vehicleKpis.capacities[1].type }}
</div>
<div class="metric-box-value" *ngIf="kpis.vehicleKpis.capacities?.length > 2">
<a (click)="showAllKpis()">View more...</a>
</div>
</div>
<div class="metric-box">
<div class="mb-1 metric-box-title">Number of vehicles</div>
<div class="mb-0 metric-box-value">
{{ (kpis$ | async).vehicleKpis.selected }} / {{ (kpis$ | async).vehicleKpis.total }}
{{ kpis.vehicleKpis.selected }} / {{ kpis.vehicleKpis.total }}
</div>
</div>
<div class="metric-box">
<div class="mb-1 metric-box-title">Dwell time</div>
<div class="mb-0 metric-box-value">
{{ formattedDurationSeconds((kpis$ | async).shipmentKpis.dwellTime) }}
{{ formattedDurationSeconds(kpis.shipmentKpis.dwellTime) }}
</div>
</div>
<div class="metric-box">
<div class="mb-1 metric-box-title">Number of pickups</div>
<div class="mb-0 metric-box-value">{{ (kpis$ | async).shipmentKpis.pickups }}</div>
<div class="mb-0 metric-box-value">{{ kpis.shipmentKpis.pickups }}</div>
</div>
<div class="metric-box">
<div class="mb-1 metric-box-title">Number of dropoffs</div>
<div class="mb-0 metric-box-value">{{ (kpis$ | async).shipmentKpis.deliveries }}</div>
<div class="mb-0 metric-box-value">{{ kpis.shipmentKpis.deliveries }}</div>
</div>
</div>

<ng-template #noValue>
<span>-</span>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ScenarioKpisComponent } from './scenario-kpis.component';
import { provideMockStore } from '@ngrx/store/testing';
import { selectScenarioKpis } from '../../selectors/pre-solve.selectors';
import { MatDialog } from '@angular/material/dialog';

describe('ScenarioKpisComponent', () => {
let component: ScenarioKpisComponent;
let fixture: ComponentFixture<ScenarioKpisComponent>;
let _matDialog: jasmine.SpyObj<MatDialog>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ScenarioKpisComponent],
providers: [
{ provide: MatDialog, useValue: jasmine.createSpyObj('matDialog', ['open']) },
provideMockStore({
selectors: [
{
Expand All @@ -52,6 +55,8 @@ describe('ScenarioKpisComponent', () => {
],
}).compileComponents();

_matDialog = TestBed.inject(MatDialog) as jasmine.SpyObj<MatDialog>;

fixture = TestBed.createComponent(ScenarioKpisComponent);
component = fixture.componentInstance;
fixture.detectChanges();
Expand All @@ -60,4 +65,22 @@ describe('ScenarioKpisComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('should sort load demands by type', () => {
const capacities = [
{
selected: 0,
total: 0,
type: 'weight',
},
{
selected: 0,
total: 0,
type: 'volume',
},
];
capacities.sort(component.sortLoadDemandsByType);
expect(capacities[0].type).toBe('volume');
expect(capacities[1].type).toBe('weight');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { ScenarioKpis } from '../../models';
import { Observable } from 'rxjs';
import { LoadDemandKPI, ScenarioKpis } from '../../models';
import { selectScenarioKpis } from '../../selectors/pre-solve.selectors';
import { formattedDurationSeconds } from 'src/app/util';
import { MatDialog } from '@angular/material/dialog';
import { LoadDemandsMetricsComponent } from 'src/app/shared/components/load-demands-metrics/load-demands-metrics.component';

@Component({
selector: 'app-scenario-kpis',
Expand All @@ -27,13 +28,40 @@ import { formattedDurationSeconds } from 'src/app/util';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ScenarioKpisComponent implements OnInit {
kpis$: Observable<ScenarioKpis>;
kpis: ScenarioKpis;

formattedDurationSeconds = formattedDurationSeconds;

constructor(private store: Store) {}
constructor(
private store: Store,
private detectorRef: ChangeDetectorRef,
private dialog: MatDialog
) {}

ngOnInit(): void {
this.kpis$ = this.store.pipe(select(selectScenarioKpis));
this.store.pipe(select(selectScenarioKpis)).subscribe((kpis) => {
kpis.shipmentKpis.demands.sort(this.sortLoadDemandsByType);
kpis.vehicleKpis.capacities.sort(this.sortLoadDemandsByType);
this.kpis = kpis;
this.detectorRef.markForCheck();
});
}

showAllKpis(): void {
this.dialog.open(LoadDemandsMetricsComponent, {
panelClass: 'metric-box-dialog',
data: {
kpis: this.kpis,
},
});
}

sortLoadDemandsByType(a: LoadDemandKPI, b: LoadDemandKPI): number {
if (a.type < b.type) {
return -1;
} else if (a.type > b.type) {
return 1;
}
return 0;
}
}
44 changes: 23 additions & 21 deletions application/frontend/src/app/core/models/scenario-kpis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export interface ScenarioKpis {
shipmentKpis: {
total: number;
selected: number;
demands: {
selected: number;
total: number;
type: string;
}[];
pickups: number;
deliveries: number;
dwellTime: number;
};
vehicleKpis: {
total: number;
selected: number;
capacities: {
selected: number;
total: number;
type: string;
}[];
};
shipmentKpis: ShipmentKPIs;
vehicleKpis: VehicleKPIs;
}

export interface ShipmentKPIs {
total: number;
selected: number;
demands: LoadDemandKPI[];
pickups: number;
deliveries: number;
dwellTime: number;
}

export interface VehicleKPIs {
total: number;
selected: number;
capacities: LoadDemandKPI[];
}

export interface LoadDemandKPI {
selected: number;
total: number;
type: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div class="title" mat-dialog-title>
<div class="m-0 mr-6">Total Scenario Load Demands</div>
<button mat-icon-button type="button" title="Cancel" class="close-button" matDialogClose="">
<mat-icon svgIcon="clear"></mat-icon>
</button>
</div>
<div mat-dialog-container>
<table cdk-table [dataSource]="kpiData">
<ng-container cdkColumnDef="type">
<th cdk-header-cell *cdkHeaderCellDef class="kpi-title">Load Type</th>
<td cdk-cell *cdkCellDef="let kpi" class="metric-box-value">{{ kpi.type }}</td>
</ng-container>
<ng-container cdkColumnDef="demand">
<th cdk-header-cell *cdkHeaderCellDef class="kpi-title">Total Shipment Demand</th>
<td cdk-cell *cdkCellDef="let kpi" class="metric-box-value">{{ kpi.demand | number }}</td>
</ng-container>
<ng-container cdkColumnDef="capacity">
<th cdk-header-cell *cdkHeaderCellDef class="kpi-title">Total Vehicle Capacity</th>
<td cdk-cell *cdkCellDef="let kpi" class="metric-box-value">{{ kpi.capacity | number }}</td>
</ng-container>

<tr cdk-header-row *cdkHeaderRowDef="displayedColumns"></tr>
<tr cdk-row *cdkRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@import '../../../../styles/variables.scss';

:host {
text-align: left;
}

table {
border-spacing: 20px 0;
margin-left: -20px;
margin-right: -20px;
}

.close-button {
margin-top: -1rem;
margin-right: -1rem;
}

.title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
line-height: 24px;
font-weight: 400;
letter-spacing: 0.15px;
margin-bottom: 26px;
}

.kpi-title {
font-weight: 400;
font-size: 12px;
line-height: 24px;

padding-right: 35px;
margin-right: 20px;

&:last-child {
padding-right: 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { LoadDemandsMetricsComponent } from './load-demands-metrics.component';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';

describe('LoadDemandsMetricsComponent', () => {
let component: LoadDemandsMetricsComponent;
let fixture: ComponentFixture<LoadDemandsMetricsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [LoadDemandsMetricsComponent],
providers: [
{ provide: MatDialogRef, useValue: {} },
{
provide: MAT_DIALOG_DATA,
useValue: {
kpis: {
shipmentKpis: {
total: 1,
selected: 1,
demands: [
{
selected: 10,
total: 20,
type: 'weight',
},
{
selected: 5,
total: 5,
type: 'volume',
},
],
pickups: 1,
deliveries: 1,
dwellTime: 1,
},
vehicleKpis: {
total: 1,
selected: 1,
capacities: [
{
selected: 20,
total: 20,
type: 'weight',
},
{
selected: 10,
total: 15,
type: 'volume',
},
],
},
},
},
},
],
}).compileComponents();

fixture = TestBed.createComponent(LoadDemandsMetricsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

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

it('should parse KPIs object into an array', () => {
expect(component.kpiData).toEqual([
{
demand: 10,
capacity: 20,
type: 'weight',
},
{
demand: 5,
capacity: 10,
type: 'volume',
},
]);
});
});
Loading

0 comments on commit 77bb215

Please sign in to comment.