This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Add icon component (#720)

  • Loading branch information...
nakhbari committed Apr 20, 2018
1 parent e94c8d8 commit a66f69fc5f5db0740b57f6b6ddd69e801218c360
View
@@ -17,7 +17,7 @@
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"prefix": "fci",
"styles": [
"global/theme.scss",
"global/grid.scss"
View
@@ -120,13 +120,13 @@
"directive-selector": [
true,
"attribute",
"app",
"fci",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"fci",
"kebab-case"
],
"no-output-on-prefix": true,
View
@@ -1,5 +1,5 @@
import {Component} from '@angular/core';
@Component({selector: 'app-root', templateUrl: './app.component.html'})
@Component({selector: 'fci-root', templateUrl: './app.component.html'})
export class AppComponent {
}
View
@@ -5,17 +5,23 @@ import {MomentModule} from 'ngx-moment';
import {AppRoutingModule} from './/app-routing.module';
import {AppComponent} from './app.component';
import {CommonComponentsModule} from './common/components/common-components.module';
import {DashboardComponent} from './dashboard/dashboard.component';
import {ProjectComponent} from './project/project.component';
import {DataService} from './services/data.service';
import {SharedMaterialModule} from './shared_material.module';
@NgModule({
declarations: [AppComponent, DashboardComponent, ProjectComponent],
declarations: [
AppComponent,
DashboardComponent,
ProjectComponent,
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule,
CommonComponentsModule,
/** Angular Material Imports */
SharedMaterialModule,
/** Third-Party Module Imports */
@@ -0,0 +1,15 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {SharedMaterialModule} from '../../shared_material.module';
import {StatusIconComponent} from './status-icon/status-icon.component';
@NgModule({
declarations: [StatusIconComponent],
imports: [SharedMaterialModule, CommonModule],
exports: [StatusIconComponent]
})
export class CommonComponentsModule {
}
@@ -0,0 +1,14 @@
<ng-container [ngSwitch]="status">
<mat-icon *ngSwitchCase="BuildStatus.SUCCESS" class="fci-status-icon-success">
sentiment_very_satisfied
</mat-icon>
<mat-icon *ngSwitchCase="BuildStatus.PENDING" class="fci-status-icon-pending">
sentiment_neutral
</mat-icon>
<mat-icon *ngSwitchCase="isFailedState()" class="fci-status-icon-failed">
sentiment_very_dissatisfied
</mat-icon>
<mat-icon *ngSwitchDefault class="fci-status-icon-default">
sentiment_satisfied
</mat-icon>
</ng-container>
@@ -0,0 +1,16 @@
.fci-status-icon-success {
color: #4CAF50;
}
.fci-status-icon-pending {
color: #FFEB3B;
}
.fci-status-icon-failed {
color: #DD2C00;
}
.fci-status-icon-default {
color: black;
opacity: 0.1;
}
@@ -0,0 +1,73 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MatIconModule} from '@angular/material/icon';
import {BuildStatus} from '../../constants';
import {StatusIconComponent} from './status-icon.component';
interface ExpectedIcon {
iconString: string;
class: string;
}
const EXPECTED_STATUSES = new Map<BuildStatus, ExpectedIcon>([
[
BuildStatus.SUCCESS,
{iconString: 'sentiment_very_satisfied', class: 'fci-status-icon-success'}
],
[
BuildStatus.PENDING,
{iconString: 'sentiment_neutral', class: 'fci-status-icon-pending'}
],
[
BuildStatus.FAILED, {
iconString: 'sentiment_very_dissatisfied',
class: 'fci-status-icon-failed'
}
],
[
BuildStatus.INTERNAL_ISSUE, {
iconString: 'sentiment_very_dissatisfied',
class: 'fci-status-icon-failed'
}
],
[
BuildStatus.MISSING_FASTFILE, {
iconString: 'sentiment_very_dissatisfied',
class: 'fci-status-icon-failed'
}
],
[
undefined,
{iconString: 'sentiment_satisfied', class: 'fci-status-icon-default'}
]
]);
describe('StatusIconComponent', () => {
let component: StatusIconComponent;
let fixture: ComponentFixture<StatusIconComponent>;
let iconEl: HTMLElement;
beforeEach(async(() => {
TestBed
.configureTestingModule(
{imports: [MatIconModule], declarations: [StatusIconComponent]})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatusIconComponent);
component = fixture.componentInstance;
iconEl = fixture.nativeElement;
expect(component.status).toBeUndefined();
});
EXPECTED_STATUSES.forEach((expectedIcon, status) => {
it(`should have expect icon and class when status is ${status}`, () => {
component.status = status;
fixture.detectChanges();
const icon = iconEl.querySelector('.mat-icon');
expect(icon.textContent.trim()).toBe(expectedIcon.iconString);
expect(icon.classList).toContain(expectedIcon.class);
});
});
});
@@ -0,0 +1,26 @@
import {Component, HostBinding, Input, OnInit} from '@angular/core';
import {BuildStatus} from '../../constants';
const FAILED_STATUSES: BuildStatus[] = [
BuildStatus.FAILED, BuildStatus.INTERNAL_ISSUE, BuildStatus.MISSING_FASTFILE
];
@Component({
selector: 'fci-status-icon',
templateUrl: './status-icon.component.html',
styleUrls: ['./status-icon.component.scss']
})
export class StatusIconComponent {
@HostBinding('class') classes = ['fci-status-icon'];
@Input() status: BuildStatus;
readonly BuildStatus = BuildStatus;
/**
* Returns the status if it is a failed status, or false otherwise to skip the
* ngSwitchCase.
*/
isFailedState(): BuildStatus|false {
return FAILED_STATUSES.includes(this.status) ? this.status : false;
}
}
@@ -25,23 +25,3 @@ export function fastlaneStatusToEnum(status: FastlaneStatus): BuildStatus {
throw new Error(`Unknown status type ${status}`);
}
}
export function buildStatusToIcon(status?: BuildStatus) {
if (status) {
switch (status) {
case BuildStatus.SUCCESS:
return 'done';
case BuildStatus.PENDING:
return 'pause_circle_filled';
case BuildStatus.FAILED:
case BuildStatus.MISSING_FASTFILE:
return 'error';
case BuildStatus.INTERNAL_ISSUE:
return 'warning';
default:
throw new Error(`Unknown build status ${status}`);
}
} else {
return 'pause_circle_filled';
}
}
@@ -17,7 +17,7 @@
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef> Project Name </mat-header-cell>
<mat-cell *matCellDef="let project">
<mat-icon>{{project.statusIcon}}</mat-icon>
<fci-status-icon [status]="project.latestStatus"></fci-status-icon>
<span>{{project.name}}</span>
</mat-cell>
</ng-container>
@@ -30,6 +30,13 @@
&:hover {
background-color: #F3E5F5
}
.mat-cell {
align-items: center;
display: flex;
.fci-status-icon {
margin-right: 8px;
}
}
}
}
@@ -6,6 +6,7 @@ import {MomentModule} from 'ngx-moment';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {CommonComponentsModule} from '../common/components/common-components.module';
import {BuildStatus} from '../common/constants';
import {mockProjectSummaryList} from '../common/test_helpers/mock_project_data';
import {ProjectSummary} from '../models/project_summary';
@@ -24,7 +25,10 @@ describe('DashboardComponent', () => {
TestBed
.configureTestingModule({
imports: [SharedMaterialModule, MomentModule, RouterModule],
imports: [
SharedMaterialModule, CommonComponentsModule, MomentModule,
RouterModule
],
declarations: [
DashboardComponent,
],
@@ -50,8 +54,8 @@ describe('DashboardComponent', () => {
expect(component.projects[0].id).toBe('1');
expect(component.projects[0].name).toBe('the coolest project');
expect(component.projects[1].latestStatus).toBe(BuildStatus.SUCCESS);
expect(component.projects[2].statusIcon).toBe('error');
expect(component.projects[2].latestStatus).toBe(BuildStatus.FAILED);
expect(component.projects[3].latestDate).toBeUndefined();
expect(component.projects[3].statusIcon).toBe('pause_circle_filled');
expect(component.projects[3].latestStatus).toBeUndefined();
});
});
@@ -4,7 +4,7 @@ import {ProjectSummary, ProjectSummaryResponse} from '../models/project_summary'
import {DataService} from '../services/data.service';
@Component({
selector: 'app-dashboard',
selector: 'fci-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
@@ -1,4 +1,4 @@
import {BuildStatus, buildStatusToIcon, FastlaneStatus, fastlaneStatusToEnum} from '../common/constants';
import {BuildStatus, FastlaneStatus, fastlaneStatusToEnum} from '../common/constants';
const SHORT_SHA_LENGTH = 6;
@@ -17,15 +17,13 @@ export class BuildSummary {
readonly sha: string;
readonly shortSha: string;
readonly date: Date;
readonly statusIcon: string;
constructor(buildSummary: BuildSummaryResponse) {
this.number = buildSummary.number;
this.duration = buildSummary.duration;
this.sha = buildSummary.sha;
this.shortSha = this.sha.slice(0, SHORT_SHA_LENGTH);
this.status = fastlaneStatusToEnum(buildSummary.status);
this.statusIcon = buildStatusToIcon(this.status);
this.date = new Date(buildSummary.timestamp);
}
}
@@ -1,4 +1,4 @@
import {BuildStatus, buildStatusToIcon, FastlaneStatus, fastlaneStatusToEnum} from '../common/constants';
import {BuildStatus, FastlaneStatus, fastlaneStatusToEnum} from '../common/constants';
export interface ProjectSummaryResponse {
id: string;
@@ -14,14 +14,16 @@ export class ProjectSummary {
readonly lane: string;
readonly latestStatus?: BuildStatus;
readonly latestDate?: Date;
readonly statusIcon: string;
constructor(projectSummary: ProjectSummaryResponse) {
this.name = projectSummary.name;
this.id = projectSummary.id;
this.lane = projectSummary.lane;
this.latestStatus = projectSummary.latest_status ? fastlaneStatusToEnum(projectSummary.latest_status) : undefined;
this.statusIcon = buildStatusToIcon(this.latestStatus);
this.latestDate = projectSummary.latest_timestamp ? new Date(projectSummary.latest_timestamp) : undefined;
this.latestStatus = projectSummary.latest_status ?
fastlaneStatusToEnum(projectSummary.latest_status) :
undefined;
this.latestDate = projectSummary.latest_timestamp ?
new Date(projectSummary.latest_timestamp) :
undefined;
}
}
@@ -12,7 +12,7 @@
<ng-container matColumnDef="number">
<mat-header-cell *matHeaderCellDef> Build </mat-header-cell>
<mat-cell *matCellDef="let build">
<mat-icon>{{build.statusIcon}}</mat-icon>
<fci-status-icon [status]="build.status"></fci-status-icon>
<span>{{build.number}}</span>
</mat-cell>
</ng-container>
@@ -1,3 +1,10 @@
.fci-build-table {
margin-top: 24px;
.mat-cell {
align-items: center;
display: flex;
.fci-status-icon {
margin-right: 8px;
}
}
}
@@ -6,6 +6,7 @@ import {MomentModule} from 'ngx-moment';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';
import {CommonComponentsModule} from '../common/components/common-components.module';
import {mockProject} from '../common/test_helpers/mock_project_data';
import {Project} from '../models/project';
import {DataService} from '../services/data.service';
@@ -23,7 +24,7 @@ describe('ProjectComponent', () => {
TestBed
.configureTestingModule({
imports: [SharedMaterialModule, MomentModule],
imports: [CommonComponentsModule, SharedMaterialModule, MomentModule],
declarations: [
ProjectComponent,
],
@@ -7,7 +7,7 @@ import {Project} from '../models/project';
import {DataService} from '../services/data.service';
@Component({
selector: 'app-project',
selector: 'fci-project',
templateUrl: './project.component.html',
styleUrls: ['./project.component.scss']
})
View
@@ -12,7 +12,7 @@
</head>
<body>
<app-root></app-root>
<fci-root></fci-root>
</body>
</html>

0 comments on commit a66f69f

Please sign in to comment.