diff --git a/src/app/app.component.html b/src/app/app.component.html
index 3f7aab3..705e9b7 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,3 +1,15 @@
-
GitHub Usage Report
-This is an entirely client side application. Your usage report never leaves your computer.
-
\ No newline at end of file
+
+
+
+
Visualize your Github usage
+
This is an entirely client side application. Your usage report never leaves your computer.
+
+
+
\ No newline at end of file
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 52ff0d9..e0509c9 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -7,8 +7,9 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { UsageComponent } from './usage/usage.component';
import { UsageTableComponent } from './usage-table/usage-table.component';
import { FileUploadComponent } from './file-upload/file-upload.component';
-import { ChartPieOwnerComponent } from './chart-pie-owner/chart-pie-owner.component';
+import { ChartPieUserComponent } from './chart-pie-user/chart-pie-user.component';
import { ChartLineUsageTimeComponent } from './chart-line-usage-time/chart-line-usage-time.component';
+import { TableWorkflowUsageComponent } from './table-workflow-usage/table-workflow-usage.component';
import { HighchartsChartModule } from 'highcharts-angular';
@@ -18,8 +19,9 @@ import { HighchartsChartModule } from 'highcharts-angular';
UsageComponent,
UsageTableComponent,
FileUploadComponent,
- ChartPieOwnerComponent,
- ChartLineUsageTimeComponent
+ ChartPieUserComponent,
+ ChartLineUsageTimeComponent,
+ TableWorkflowUsageComponent
],
imports: [
BrowserModule,
diff --git a/src/app/chart-pie-owner/chart-pie-owner.component.html b/src/app/chart-pie-user/chart-pie-user.component.html
similarity index 100%
rename from src/app/chart-pie-owner/chart-pie-owner.component.html
rename to src/app/chart-pie-user/chart-pie-user.component.html
diff --git a/src/app/chart-pie-owner/chart-pie-owner.component.scss b/src/app/chart-pie-user/chart-pie-user.component.scss
similarity index 100%
rename from src/app/chart-pie-owner/chart-pie-owner.component.scss
rename to src/app/chart-pie-user/chart-pie-user.component.scss
diff --git a/src/app/chart-pie-owner/chart-pie-owner.component.spec.ts b/src/app/chart-pie-user/chart-pie-user.component.spec.ts
similarity index 57%
rename from src/app/chart-pie-owner/chart-pie-owner.component.spec.ts
rename to src/app/chart-pie-user/chart-pie-user.component.spec.ts
index 6f25b95..345f9a2 100644
--- a/src/app/chart-pie-owner/chart-pie-owner.component.spec.ts
+++ b/src/app/chart-pie-user/chart-pie-user.component.spec.ts
@@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ChartPieOwnerComponent } from './chart-pie-owner.component';
+import { ChartPieUserComponent } from './chart-pie-user.component';
describe('ChartPieOwnerComponent', () => {
- let component: ChartPieOwnerComponent;
- let fixture: ComponentFixture;
+ let component: ChartPieUserComponent;
+ let fixture: ComponentFixture;
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [ ChartPieOwnerComponent ]
+ declarations: [ ChartPieUserComponent ]
})
.compileComponents();
- fixture = TestBed.createComponent(ChartPieOwnerComponent);
+ fixture = TestBed.createComponent(ChartPieUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
diff --git a/src/app/chart-pie-owner/chart-pie-owner.component.ts b/src/app/chart-pie-user/chart-pie-user.component.ts
similarity index 77%
rename from src/app/chart-pie-owner/chart-pie-owner.component.ts
rename to src/app/chart-pie-user/chart-pie-user.component.ts
index 3c3d668..3c2faff 100644
--- a/src/app/chart-pie-owner/chart-pie-owner.component.ts
+++ b/src/app/chart-pie-user/chart-pie-user.component.ts
@@ -3,11 +3,11 @@ import { UsageReportLine } from 'github-usage-report/types';
import * as Highcharts from 'highcharts';
@Component({
- selector: 'app-chart-pie-owner',
- templateUrl: './chart-pie-owner.component.html',
- styleUrls: ['./chart-pie-owner.component.scss']
+ selector: 'app-chart-pie-user',
+ templateUrl: './chart-pie-user.component.html',
+ styleUrls: ['./chart-pie-user.component.scss']
})
-export class ChartPieOwnerComponent {
+export class ChartPieUserComponent {
@Input() data!: UsageReportLine[];
Highcharts: typeof Highcharts = Highcharts;
options: Highcharts.Options = {
@@ -15,7 +15,7 @@ export class ChartPieOwnerComponent {
type: 'pie'
},
title: {
- text: 'Usage by Owner'
+ text: 'Usage by username'
},
tooltip: {
pointFormat: '{series.name}: {point.percentage:.1f}%'
@@ -38,9 +38,9 @@ export class ChartPieOwnerComponent {
type: 'pie', // Add the type property
name: 'Usage',
data: this.data.reduce((acc, line) => {
- const index = acc.findIndex((item) => item[0] === line.owner);
+ const index = acc.findIndex((item) => item[0] === line.username);
if (index === -1) {
- acc.push([line.owner, line.quantity]);
+ acc.push([line.username, line.quantity]);
} else {
acc[index][1] += line.quantity;
}
diff --git a/src/app/file-upload/file-upload.component.html b/src/app/file-upload/file-upload.component.html
index 5d78da0..b53bd55 100644
--- a/src/app/file-upload/file-upload.component.html
+++ b/src/app/file-upload/file-upload.component.html
@@ -1,2 +1,5 @@
-
-
+
+
\ No newline at end of file
diff --git a/src/app/table-workflow-usage/table-workflow-usage.component.html b/src/app/table-workflow-usage/table-workflow-usage.component.html
new file mode 100644
index 0000000..a61bf12
--- /dev/null
+++ b/src/app/table-workflow-usage/table-workflow-usage.component.html
@@ -0,0 +1,21 @@
+Workflow Usage
+
+
+
+ @for (column of columns; track column) {
+
+
+ {{column.header}}
+ |
+
+ {{column.cell(row)}}
+ |
+
+ }
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/table-workflow-usage/table-workflow-usage.component.scss b/src/app/table-workflow-usage/table-workflow-usage.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/table-workflow-usage/table-workflow-usage.component.spec.ts b/src/app/table-workflow-usage/table-workflow-usage.component.spec.ts
new file mode 100644
index 0000000..82c26d5
--- /dev/null
+++ b/src/app/table-workflow-usage/table-workflow-usage.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TableWorkflowUsageComponent } from './table-workflow-usage.component';
+
+describe('TableWorkflowUsageComponent', () => {
+ let component: TableWorkflowUsageComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TableWorkflowUsageComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TableWorkflowUsageComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/table-workflow-usage/table-workflow-usage.component.ts b/src/app/table-workflow-usage/table-workflow-usage.component.ts
new file mode 100644
index 0000000..eff33a9
--- /dev/null
+++ b/src/app/table-workflow-usage/table-workflow-usage.component.ts
@@ -0,0 +1,77 @@
+import { Component, Input, ViewChild } from '@angular/core';
+import { UsageReport, UsageReportLine } from 'github-usage-report/types';
+import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
+import { MatTableDataSource, MatTableModule } from '@angular/material/table';
+import { MatSort } from '@angular/material/sort';
+
+@Component({
+ selector: 'app-table-workflow-usage',
+ templateUrl: './table-workflow-usage.component.html',
+ styleUrl: './table-workflow-usage.component.scss'
+})
+export class TableWorkflowUsageComponent {
+ columns = [
+ {
+ columnDef: 'workflow',
+ header: 'Workflow',
+ cell: (workflowItem: any) => `${workflowItem.workflow}`,
+ },
+ {
+ columnDef: 'total',
+ header: 'Total',
+ cell: (workflowItem: any) => `${workflowItem.total}`,
+ }
+ ];
+ displayedColumns = this.columns.map(c => c.columnDef);
+ @Input() data!: UsageReport;
+ dataSource: MatTableDataSource = new MatTableDataSource(); // Initialize the dataSource property
+
+ @ViewChild(MatPaginator) paginator!: MatPaginator;
+ @ViewChild(MatSort) sort!: MatSort;
+
+ ngOnInit() {
+ const workflowUsage = this.data.lines.filter(a => a.actionsWorkflow).reduce((acc, line) => {
+ const workflowEntry = acc.find(a => a.workflow === line.actionsWorkflow);
+ const date = new Date(line.date);
+ const month: string = date.toLocaleString('default', { month: 'long' });
+ if (workflowEntry) {
+ if (workflowEntry[month]) {
+ workflowEntry[month] += line.quantity || 0;
+ } else {
+ workflowEntry[month] = line.quantity || 0;
+ }
+ workflowEntry.total += line.quantity || 0;
+ if (!this.columns.find(c => c.columnDef === month)) {
+ this.columns.push({
+ columnDef: month,
+ header: month,
+ cell: (cost: any) => `${cost[month]}`,
+ });
+ this.displayedColumns = this.columns.map(c => c.columnDef);
+ }
+ } else {
+ acc.push({
+ workflow: line.actionsWorkflow,
+ repo: line.repositorySlug,
+ total: line.quantity || 0,
+ [month]: line.quantity || 0
+ });
+ }
+ return acc; // Add this line to return the accumulator
+ }, [] as any[]);
+ // fill in undefined months in workflowUsage
+ workflowUsage.forEach((workflowItem: any) => {
+ this.columns.forEach((column: any) => {
+ if (!workflowItem[column.columnDef]) {
+ workflowItem[column.columnDef] = 0;
+ }
+ });
+ });
+ this.dataSource = new MatTableDataSource(workflowUsage);
+ }
+
+ ngAfterViewInit() {
+ this.dataSource.paginator = this.paginator;
+ this.dataSource.sort = this.sort;
+ }
+}
diff --git a/src/app/usage-table/usage-table.component.ts b/src/app/usage-table/usage-table.component.ts
index 8acb407..d5d7a0d 100644
--- a/src/app/usage-table/usage-table.component.ts
+++ b/src/app/usage-table/usage-table.component.ts
@@ -2,7 +2,7 @@ import { Input } from '@angular/core';
import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {MatPaginator, MatPaginatorModule} from '@angular/material/paginator';
import {MatTableDataSource, MatTableModule} from '@angular/material/table';
-import { UsageReportLine } from 'github-usage-report/types';
+import { UsageReport, UsageReportLine } from 'github-usage-report/types';
/**
* @title Table with pagination
diff --git a/src/app/usage/usage.component.html b/src/app/usage/usage.component.html
index f5f158c..0e3c003 100644
--- a/src/app/usage/usage.component.html
+++ b/src/app/usage/usage.component.html
@@ -1,8 +1,9 @@
+
+
-
+
+
-
-
diff --git a/src/app/usage/usage.component.ts b/src/app/usage/usage.component.ts
index 1bb1490..e6712a3 100644
--- a/src/app/usage/usage.component.ts
+++ b/src/app/usage/usage.component.ts
@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { ChangeDetectorRef, Component } from '@angular/core';
import { UsageReport } from 'github-usage-report/types';
import { readGithubUsageReport } from 'github-usage-report/usage-report';
@@ -10,6 +10,8 @@ import { readGithubUsageReport } from 'github-usage-report/usage-report';
export class UsageComponent {
usage: UsageReport | null = null;
+ constructor(private cdr: ChangeDetectorRef) { } // inject ChangeDetectorRef
+
ngOnInit() {
const oldUsage = localStorage.getItem('usage');
this.usage = oldUsage ? JSON.parse(oldUsage) : null;
@@ -18,5 +20,6 @@ export class UsageComponent {
async onFileText(fileText: string) {
const usage = await readGithubUsageReport(fileText);
this.usage = usage;
+ this.cdr.detectChanges(); // manually trigger change detection
}
}
diff --git a/src/assets/github-mark-white.svg b/src/assets/github-mark-white.svg
new file mode 100644
index 0000000..d5e6491
--- /dev/null
+++ b/src/assets/github-mark-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/github-mark.svg b/src/assets/github-mark.svg
new file mode 100644
index 0000000..37fa923
--- /dev/null
+++ b/src/assets/github-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 5050250..f017afc 100644
--- a/src/index.html
+++ b/src/index.html
@@ -10,7 +10,7 @@
-
+
diff --git a/src/material.module.ts b/src/material.module.ts
index 2b095d6..bf1cf56 100644
--- a/src/material.module.ts
+++ b/src/material.module.ts
@@ -6,6 +6,7 @@ import { MatTableModule } from '@angular/material/table';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button';
+import { MatIconModule } from '@angular/material/icon';
@NgModule({
exports: [
@@ -17,7 +18,8 @@ import { MatButtonModule } from '@angular/material/button';
MatButtonModule,
MatTableModule,
MatPaginatorModule,
- MatSortModule
+ MatSortModule,
+ MatIconModule
]
})
export class MaterialModule { }
diff --git a/src/material.theme.scss b/src/material.theme.scss
new file mode 100644
index 0000000..63a17c0
--- /dev/null
+++ b/src/material.theme.scss
@@ -0,0 +1,34 @@
+@use '@angular/material' as mat;
+
+@include mat.core();
+
+// Define a dark theme
+$dark-theme: mat.define-dark-theme((
+ color: (
+ primary: mat.define-palette(mat.$pink-palette),
+ accent: mat.define-palette(mat.$blue-grey-palette),
+ ),
+ // Only include `typography` and `density` in the default dark theme.
+ typography: mat.define-typography-config(),
+ density: 0,
+));
+
+// Define a light theme
+$light-theme: mat.define-light-theme((
+ color: (
+ primary: mat.define-palette(mat.$indigo-palette),
+ accent: mat.define-palette(mat.$pink-palette),
+ ),
+));
+
+// Apply the dark theme by default
+// @include mat.core-theme($dark-theme);
+// @include mat.button-theme($dark-theme);
+
+// Apply the light theme only when the user prefers light themes.
+@media (prefers-color-scheme: light) {
+ // Use the `-color` mixins to only apply color styles without reapplying the same
+ // typography and density styles.
+ @include mat.core-color($light-theme);
+ @include mat.button-color($light-theme);
+}
\ No newline at end of file
diff --git a/src/styles.scss b/src/styles.scss
index 6293ddc..b437cc2 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1,7 +1,49 @@
-/* You can add global styles to this file, and also import other style files */
+@import './material.theme.scss';
-html, body { height: 100%; }
-body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+html,
+body {
+ height: 100%;
+}
-html, body { height: 100%; }
-body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
+body {
+ margin: 0;
+}
+
+.flex-center {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+}
+
+.flex-center-horizontally {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.fill {
+ width: 100%;
+ height: 100%;
+}
+
+.container {
+ margin: 0 10px;
+ padding: 0 10px;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ vertical-align: middle;
+ margin: 0 0 56px;
+ padding: 10px;
+ align-items: center;
+ h1 {
+ margin: 0 !important;
+ padding: 0;
+ }
+ .logo {
+ margin-right: 10px;
+ }
+}
\ No newline at end of file