Skip to content

Commit

Permalink
Added chartjs data visualizations. Added cli options support, includi…
Browse files Browse the repository at this point in the history
…ng visualization for category totals overall, and visualization for category totals per file type.
  • Loading branch information
Brocco authored and jeremymwells committed May 3, 2023
1 parent 1ad7ccb commit ba56dbb
Show file tree
Hide file tree
Showing 14 changed files with 556 additions and 50 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ testem.log
# System Files
.DS_Store
Thumbs.db
hd-tracker/
/hd-tracker
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"license": "MIT",
"scripts": {
"build": "nx run cli:build",
"test": "nx run cli:test"
"test": "nx run cli:test",
"test:watch": "npm run test -- --watch",
"start": "nx run cli:build && node dist/packages/cli/src/index.js"
},
"private": true,
"devDependencies": {
Expand All @@ -29,6 +31,11 @@
"typescript": "~4.9.5"
},
"dependencies": {
"chart.js": "^3.9.1",
"chart.js-image": "^6.1.3",
"chartjs-node-canvas": "^4.1.6",
"chartjs-plugin-autocolors": "^0.2.2",
"chartjs-plugin-datalabels": "^2.2.0",
"date-fns": "^2.28.0",
"git-last-commit": "^1.0.1",
"sloc": "^0.2.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export default {
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/packages/cli',
coverageDirectory: '../../coverage/packages/cli'
};
1 change: 1 addition & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#!/usr/bin/env node
export * from './lib';
21 changes: 21 additions & 0 deletions packages/cli/src/lib/argv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import yargs = require('yargs');
import { ChartConfig } from './models/chart-config';
import { hideBin } from 'yargs/helpers';

interface RawArgs {
root?: string;
config?: string;
init?: boolean;
chart?: ChartConfig
}

export interface Args extends RawArgs {
chart: ChartConfig
}

const parsedArgv: RawArgs = yargs(hideBin(global.process.argv)).argv as RawArgs;

// set a default chart config instance from cli options
parsedArgv.chart = new ChartConfig(parsedArgv.chart);

export const argv = parsedArgv as Args;
23 changes: 11 additions & 12 deletions packages/cli/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { resolve } from 'path';
import * as yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { processConfig } from './process-config';
import { initialize } from './initialize';
import { getTheRootDirectory, readConfig, saveResults } from './util';
import { createDataVizIn, getData, getTheRootDirectory, readConfig, saveResults } from './util';
import { argv } from './argv';

interface Args {
root?: string;
config?: string;
init?: boolean;
}

export async function run() {
const argv: Args = yargs(hideBin(global.process.argv)).argv as Args;
(async function run() {

const localRootDir = getTheRootDirectory(global.process.cwd());

if (argv.init) {
Expand All @@ -26,6 +20,11 @@ export async function run() {
const results = await processConfig(config, rootDirectory);

saveResults(localRootDir, config.outputDir, results);
}

run();

const allData = getData(localRootDir, config.outputDir);

const parentDir = resolve(localRootDir, config.outputDir);

await createDataVizIn(parentDir, allData);
}());
46 changes: 46 additions & 0 deletions packages/cli/src/lib/models/chart-config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { argv } from '../argv';
import { ChartConfig } from './chart-config';


describe('ChartConfig', () => {
it('should be default chart configuration on argv', () => {
expect(argv.chart).toStrictEqual(new ChartConfig());
});

const fakeCliOptions = {
perCategoryAndFileType: true,
perCategoryTotals: false,
width: 42,
height: 42,
bg: 'negro',
title: 'Grine-DING',
xAxisLabel: 'tomorrows',
yAxisLabel: 'weather',
overwrite: false,
outFile: 'flibbidy-giblets.png',
} as ChartConfig;

const defaultInstance = new ChartConfig();

// loop over all public keys to set key differently
Object.keys(fakeCliOptions).forEach((keyToTest) => {

const instantiatedViaCli = new ChartConfig({ [keyToTest]: fakeCliOptions[keyToTest] } as any);

it(`should override default '${keyToTest}: ${defaultInstance[keyToTest]}' value with '${keyToTest}: ${instantiatedViaCli[keyToTest]}'`, () => {
expect(defaultInstance[keyToTest]).not.toEqual(instantiatedViaCli[keyToTest]);
});

// loop over all remaining keys after having set one key different
Object.keys(fakeCliOptions).forEach((otherKey) => {
if (otherKey === keyToTest) { return; }

// testing differing keys; values should match
it(`should be default '${otherKey}: ${defaultInstance[otherKey]}' value with '${otherKey}: ${instantiatedViaCli[otherKey]}'`, () => {
expect(defaultInstance[otherKey]).toEqual(instantiatedViaCli[otherKey]);
});
});
})

});

86 changes: 86 additions & 0 deletions packages/cli/src/lib/models/chart-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export class ChartConfig {
private _perCategoryAndFileType = false;
private _perCategoryTotals = true;
private _width = 1200;
private _height = 800;
private _bg = 'white';
private _title = 'Migration Progress LOC';
private _xAxisLabel = 'Date';
private _yAxisLabel = 'Totals';
private _overwrite = true;
private _outFile = 'viz.png';

get perCategoryAndFileType(): boolean {
// if false, use false; false !== undefined
return this._cliOptions.perCategoryAndFileType !== undefined ?
this._cliOptions.perCategoryAndFileType :
this._perCategoryAndFileType;
}

get perCategoryTotals(): boolean {
// if false, use false; false !== undefined
return this._cliOptions.perCategoryTotals !== undefined ?
this._cliOptions.perCategoryTotals:
this._perCategoryTotals;
}

get width(): number {
// if set and non-numeric
if (this._cliOptions.width !== undefined && isNaN(Number(this._cliOptions.width))) {
throw Error('--chart.width must be a number');
}
// if unset or numeric
return this._cliOptions.width !== undefined ?
this._cliOptions.width:
this._width;
}

get height(): number {
// if set and non-numeric
if (this._cliOptions.height !== undefined && isNaN(Number(this._cliOptions.height))) {
throw Error('--chart.height must be a number');
}
// if unset or numeric
return this._cliOptions.height !== undefined ?
this._cliOptions.height:
this._height;
}

get bg(): string {
return this._cliOptions.bg ?
this._cliOptions.bg:
this._bg;
}

get title(): string {
return this._cliOptions.title ?
this._cliOptions.title:
this._title;
}

get xAxisLabel(): string {
return this._cliOptions.xAxisLabel ?
this._cliOptions.xAxisLabel:
this._xAxisLabel;
}

get yAxisLabel(): string {
return this._cliOptions.yAxisLabel ?
this._cliOptions.yAxisLabel:
this._yAxisLabel;
}

get overwrite(): boolean {
return this._cliOptions.overwrite !== undefined ?
this._cliOptions.overwrite:
this._overwrite;
}

get outFile(): string {
return this._cliOptions.outFile ?
this._cliOptions.outFile:
this._outFile;
}

constructor(private _cliOptions: ChartConfig = {} as ChartConfig) { }
}
4 changes: 4 additions & 0 deletions packages/cli/src/lib/models/viz-dataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type VizDataset = {
label: string;
data: number[];
};
6 changes: 6 additions & 0 deletions packages/cli/src/lib/models/viz-labels-datasets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { VizDataset } from './viz-dataset'

export type VizLabelsDatasets = {
labels: string[],
datasets: VizDataset[],
}
89 changes: 89 additions & 0 deletions packages/cli/src/lib/tracker-chart.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { argv } from './argv';
import { ChartConfig } from './models/chart-config';
import { TrackerChart } from './tracker-chart';


describe('TrackerChart', () => {

describe('ctor', () => {
let chart;
beforeEach(() => {
chart = new TrackerChart(argv.chart, [], 'foo')
});

it('should have default _config', () => {
expect((chart as any)._config).toStrictEqual(new ChartConfig());
});

it('should have empty _allProcessResults', () => {
expect((chart as any)._allProcessResults).toStrictEqual([]);
});

it('should have foo _dateFormat', () => {
expect((chart as any)._dateFormat).toStrictEqual('foo');
});
});

describe('private methods', () => {
let spyChart: TrackerChart;
let ogGetDataAndLabels;
let getDataAndLabelsSpy: jest.SpyInstance;
let getTotalsPerCategorySpy: jest.SpyInstance;
let getTotalsPerFileTypePerCategorySpy: jest.SpyInstance;
let generateGraphImageFileSpy: jest.SpyInstance;
const noop = () => { undefined };
beforeEach(() => {
spyChart = new TrackerChart(argv.chart, [1] as any, 'foo');
ogGetDataAndLabels = (spyChart as any).getDataAndLabels;
getDataAndLabelsSpy = jest.spyOn((spyChart as any), 'getDataAndLabels').mockImplementation(noop);
getTotalsPerCategorySpy = jest.spyOn((spyChart as any), 'getTotalsPerCategory').mockImplementation(noop);
getTotalsPerFileTypePerCategorySpy = jest.spyOn((spyChart as any), 'getTotalsPerFileTypePerCategory').mockImplementation(noop);
generateGraphImageFileSpy = jest.spyOn((spyChart as any), 'generateGraphImageFile').mockImplementation(noop);

});

afterEach(() => {
getDataAndLabelsSpy.mockReset();
getTotalsPerCategorySpy.mockReset();
getTotalsPerFileTypePerCategorySpy.mockReset();
generateGraphImageFileSpy.mockReset();
});

it('calls getDataAndLabels inside writeTo', () => {
spyChart.writeTo('');
expect(getDataAndLabelsSpy).toHaveBeenCalledWith([1], 'total');
});

it('calls generateGraphImageFileSpy inside writeTo', () => {
spyChart.writeTo('');
expect(generateGraphImageFileSpy).toHaveBeenCalledWith('', undefined);
});

it('calls getTotalsPerCategory inside getDataLabels', () => {
(spyChart as any).getDataAndLabels = ogGetDataAndLabels;
(spyChart as any).getDataAndLabels([1], 'total');
expect(getTotalsPerCategorySpy).toHaveBeenCalledWith([1], 'total');
(spyChart as any).getDataAndLabels = getDataAndLabelsSpy;
});

it('calls getTotalsPerFileTypePerCategory inside getDataLabels', () => {
(spyChart as any)._config._perCategoryTotals = false;
(spyChart as any)._config._perCategoryAndFileType = true;
(spyChart as any).getDataAndLabels = ogGetDataAndLabels;
(spyChart as any).getDataAndLabels([1], 'total');
expect(getTotalsPerFileTypePerCategorySpy).toHaveBeenCalledWith([1], 'total');
(spyChart as any).getDataAndLabels = getDataAndLabelsSpy;
});

it('calls getTotalsPerCategory inside getDataLabels by default', () => {
(spyChart as any)._config._perCategoryTotals = undefined;
(spyChart as any)._config._perCategoryAndFileType = undefined;
(spyChart as any).getDataAndLabels = ogGetDataAndLabels;
(spyChart as any).getDataAndLabels([1], 'total');
expect(getTotalsPerCategorySpy).toHaveBeenCalledWith([1], 'total');
(spyChart as any).getDataAndLabels = getDataAndLabelsSpy;
});

});

});
Loading

0 comments on commit ba56dbb

Please sign in to comment.