Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

kbme

Package Overview
Dependencies
Maintainers
3
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

kbme - npm Package Compare versions

Comparing version 2.3.2 to 2.4.0

95

lib/config.js

@@ -1,45 +0,41 @@

const { join } = require('path')
const { date_ago } = require('./util')
const { join } = require("path");
const { date_ago } = require("./util");
require('dotenv').config({
path: join(process.cwd(), '.env')
})
require("dotenv").config({
path: join(process.cwd(), ".env")
});
module.exports = {
alias: {
c: 'csv',
k: 'keys',
t: 'types',
a: 'auto',
d: 'done',
t: 'todo',
s: 'start',
f: 'finish',
u: ['user', 'username'],
p: ['pass', 'password'],
j: ['url', 'jira'],
e: 'endpoint',
q: 'query',
r: 'report',
i: 'interval',
x: 'period',
h: 'help'
c: "csv",
k: "keys",
t: "types",
a: "auto",
d: "done",
t: "todo",
s: "start",
f: "finish",
u: ["user", "username"],
p: ["pass", "password"],
j: ["url", "jira"],
e: "endpoint",
q: "query",
r: "report",
i: "interval",
x: "period",
h: "help"
},
boolean: [
'csv',
'help',
'report'
],
boolean: ["csv", "help", "report"],
string: [
'keys',
'done',
'todo',
'start',
'finish',
'user',
'pass',
'jira',
'query',
'types',
'endpoint'
"keys",
"done",
"todo",
"start",
"finish",
"user",
"pass",
"jira",
"query",
"types",
"endpoint"
],

@@ -56,17 +52,20 @@ default: {

finish: process.env.KBME_FINISH || date_ago(0),
done: process.env.KBME_DONE || 'Done',
todo: process.env.KBME_TODO || 'Pending',
keys: process.env.KBME_KEYS || 'ELEMENTS',
types: process.env.KBME_TYPES || 'NOT IN (Epic)',
query: process.env.KBME_QUERY || 'project IN (%keys) AND status IN (%done) AND resolutiondate > %start AND resolutiondate < %finish AND issuetype %types',
endpoint: process.env.KBME_ENDPOINT || '%jira/search?jql=%jql&expand=changelog'
done: process.env.KBME_DONE || "Done",
todo: process.env.KBME_TODO || "Pending",
keys: process.env.KBME_KEYS || "ELEMENTS",
types: process.env.KBME_TYPES || "NOT IN (Epic)",
query:
process.env.KBME_QUERY ||
"project IN (%keys) AND status IN (%done) AND resolutiondate > %start AND resolutiondate < %finish AND issuetype %types",
endpoint:
process.env.KBME_ENDPOINT || "%jira/search?jql=%jql&expand=changelog"
},
unknown: function(flag) {
console.log(`Unknown option ${flag}`)
process.exit(1)
console.log(`Unknown option ${flag}`);
process.exit(1);
}
}
};
function to_bool(val) {
return val === 'false' ? false : Boolean(val)
return val === "false" ? false : Boolean(val);
}

@@ -1,5 +0,5 @@

const pkg = require('../package.json')
const opt = require('./config')
const pkg = require("../package.json");
const opt = require("./config");
const help =`
const help = `
${pkg.name} v${pkg.version}

@@ -31,3 +31,5 @@ -----------

- Override JIRA URL
> KBME_JIRA="https://jira.com/jira/rest/api/2" KBME_KEYS="SFC, KRK" ${pkg.name} --csv
> KBME_JIRA="https://jira.com/jira/rest/api/2" KBME_KEYS="SFC, KRK" ${
pkg.name
} --csv

@@ -48,4 +50,4 @@ Pay special attention to the query and endpoint options, they have special characters (%opt) that

> kbme --auto 15 --csv >> metrics.csv
`
`;
module.exports = help
module.exports = help;

@@ -1,4 +0,12 @@

const got = require('got')
const debug = require('debug')('jql');
const { pad, xtend, date_ago, days_between, num_reducer, replace, generate_dates } = require('./util')
const got = require("got");
const debug = require("debug")("jql");
const {
pad,
xtend,
date_ago,
days_between,
num_reducer,
replace,
generate_dates
} = require("./util");

@@ -13,3 +21,3 @@ module.exports = {

report
}
};

@@ -20,9 +28,9 @@ /**

*/
function request({jira, user, pass, endpoint}, req = got) {
return async (jql) =>
function request({ jira, user, pass, endpoint }, req = got) {
return async jql =>
debug(jql) ||
req(replace(endpoint, {jira, jql}), {
req(replace(endpoint, { jira, jql }), {
json: true,
auth: `${user}:${pass}`
}).then(res => res.body.issues)
}).then(res => res.body.issues);
}

@@ -41,7 +49,7 @@

*/
function lead({fields, changelog}, status) {
const start = new Date(fields.created)
const finish = new Date(date_to_status(status, changelog))
function lead({ fields, changelog }, status) {
const start = new Date(fields.created);
const finish = new Date(date_to_status(status, changelog));
return days_between(finish, start)
return days_between(finish, start);
}

@@ -61,10 +69,10 @@

*/
function cycle({fields, changelog}, todo, done) {
function cycle({ fields, changelog }, todo, done) {
// When issues are moved directly to 'done' status, without going
// thru 'todo', date_to_status() returns undefined, in this case
// fields.created will be used instead of 'todo' date
const start = new Date(date_to_status(todo, changelog) || fields.created)
const finish = new Date(date_to_status(done, changelog))
const start = new Date(date_to_status(todo, changelog) || fields.created);
const finish = new Date(date_to_status(done, changelog));
return days_between(finish, start)
return days_between(finish, start);
}

@@ -105,12 +113,12 @@

*/
function date_to_status(status, {histories}) {
function date_to_status(status, { histories }) {
const date = histories
.filter(
({ items }) => items.filter(
({ field, toString }) =>
(field === 'status' && toString === status)
).length > 0
({ items }) =>
items.filter(
({ field, toString }) => field === "status" && toString === status
).length > 0
)
.map(f => f.created)
.pop()
.pop();

@@ -126,12 +134,13 @@ return date;

function metrics(opts, issues) {
const leadTimeAvg = issues
.map(issue => lead(issue, opts.done))
.reduce(num_reducer, 0) / issues.length
const leadTimeAvg =
issues.map(issue => lead(issue, opts.done)).reduce(num_reducer, 0) /
issues.length;
const cycleTimeAvg = issues
.map(issue => cycle(issue, opts.todo, opts.done))
.reduce(num_reducer, 0) / issues.length
const cycleTimeAvg =
issues
.map(issue => cycle(issue, opts.todo, opts.done))
.reduce(num_reducer, 0) / issues.length;
const period = days_between(new Date(opts.start), new Date(opts.finish))
const throughput = (issues.length / period)
const period = days_between(new Date(opts.start), new Date(opts.finish));
const throughput = issues.length / period;

@@ -145,3 +154,3 @@ return {

finish: opts.finish
}
};
}

@@ -155,5 +164,5 @@

const api = request(opt, req);
const jql = replace(opt.query, opt)
const issues = await api(jql)
const res = metrics(opt, issues)
const jql = replace(opt.query, opt);
const issues = await api(jql);
const res = metrics(opt, issues);

@@ -168,7 +177,9 @@ return res;

async function report(opt, req = got) {
const dates = generate_dates(opt.interval, opt.period)
const dates = generate_dates(opt.interval, opt.period);
return Promise.all(dates
.map(date => xtend({}, opt, date))
.map(opts => query(opts, req).then(data => data)))
return Promise.all(
dates
.map(date => xtend({}, opt, date))
.map(opts => query(opts, req).then(data => data))
);
}

@@ -12,3 +12,3 @@ module.exports = {

generate_dates
}
};

@@ -20,3 +20,3 @@ /**

function pad(n) {
return ('0' + n).slice(-2)
return ("0" + n).slice(-2);
}

@@ -30,6 +30,6 @@

if (!Array.isArray(data)) {
return Object.values(data).join(', ')
return Object.values(data).join(", ");
}
return data.map(csv).join('\n')
return data.map(csv).join("\n");
}

@@ -42,3 +42,3 @@

function toJSON(data) {
return JSON.stringify(data, null, 2)
return JSON.stringify(data, null, 2);
}

@@ -51,5 +51,7 @@

function date_ago(days) {
const now = new Date()
const last = new Date(now.getTime() - (days * 24 * 60 * 60 * 1000))
return `${last.getFullYear()}-${pad(last.getMonth() + 1)}-${pad(last.getDate())}`;
const now = new Date();
const last = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
return `${last.getFullYear()}-${pad(last.getMonth() + 1)}-${pad(
last.getDate()
)}`;
}

@@ -63,3 +65,3 @@

function days_between(a, b) {
const diff = Math.abs(a.getTime() - b.getTime())
const diff = Math.abs(a.getTime() - b.getTime());
return Math.ceil(diff / (1000 * 3600 * 24));

@@ -74,4 +76,4 @@ }

function num_reducer(a, b) {
a += b
return a
a += b;
return a;
}

@@ -98,10 +100,10 @@

function generate_dates(interval = 15, period = 365) {
let top = 0
let bottom = 0
const dates = []
const limit = Math.ceil(period / interval)
let top = 0;
let bottom = 0;
const dates = [];
const limit = Math.ceil(period / interval);
for (let i = 0; i < limit; i++) {
top += interval
bottom = top - interval
top += interval;
bottom = top - interval;

@@ -111,12 +113,12 @@ dates.push({

finish: date_ago(bottom)
})
});
}
return dates
return dates;
}
function format(opt) {
if (opt.csv) return data => csv(data)
if (opt.csv) return data => csv(data);
return data => toJSON(data)
return data => toJSON(data);
}
{
"name": "kbme",
"version": "2.3.2",
"version": "2.4.0",
"description": "Gather kanban metrics from your JIRA instance",

@@ -28,3 +28,6 @@ "main": "lib/index.js",

"devDependencies": {
"jest": "^23.1.0"
"cz-conventional-changelog": "^2.1.0",
"husky": "^1.3.1",
"jest": "^23.1.0",
"lint-staged": "^8.1.0"
},

@@ -34,3 +37,5 @@ "scripts": {

"test:watch": "jest --watch",
"release": "npx semantic-release@beta"
"release": "npx semantic-release@beta",
"commit": "npx git-cz",
"format": "npx prettier --write **/*.js"
},

@@ -43,3 +48,19 @@ "release": {

]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": [
"npm run format",
"git add"
]
}
}

@@ -102,2 +102,4 @@ # kbme

**HEADS UP:** If you are not sure how write a commit message, make your changes in your feature branch and run `npm run commit` and follow the assistant.
### Releases

@@ -104,0 +106,0 @@

@@ -1,100 +0,120 @@

const { got } = require('./mocks')
const { date_ago } = require('../lib/util')
const { issues } = require('./mocks/issues.json')
const { request, lead, cycle, metrics, date_to_status, query, report } = require('../lib')
const { got } = require("./mocks");
const { date_ago } = require("../lib/util");
const { issues } = require("./mocks/issues.json");
const {
request,
lead,
cycle,
metrics,
date_to_status,
query,
report
} = require("../lib");
let options = {
done: 'Done',
todo: 'ToDo',
start: '2017-08-01',
finish: '2017-08-26',
jira: 'jira',
user: 'user',
pass: 'pass',
endpoint: '%jira/%jql',
query: 'test',
done: "Done",
todo: "ToDo",
start: "2017-08-01",
finish: "2017-08-26",
jira: "jira",
user: "user",
pass: "pass",
endpoint: "%jira/%jql",
query: "test",
interval: 2,
period: 4,
report: false
}
};
const makeOptions = opts => Object.assign({}, options, opts)
const makeOptions = opts => Object.assign({}, options, opts);
describe('lib', () => {
describe('#request', () => {
test('should return a function', () => {
expect(typeof request(options)).toBe('function')
})
describe("lib", () => {
describe("#request", () => {
test("should return a function", () => {
expect(typeof request(options)).toBe("function");
});
test('function returned should return a promise', async () => {
const api = request(options, got)
const res = await api()
expect(api() instanceof Promise).toBe(true)
expect(res.length).toBe(4)
expect(res).toHaveLength(4)
})
})
test("function returned should return a promise", async () => {
const api = request(options, got);
const res = await api();
expect(api() instanceof Promise).toBe(true);
expect(res.length).toBe(4);
expect(res).toHaveLength(4);
});
});
describe('#lead', () => {
test('should return return the lead time for given issue', () => {
expect(lead(issues[0], 'ToDo')).toBe(1)
expect(lead(issues[1], 'ToDo')).toBe(1)
expect(lead(issues[2], 'ToDo')).toBe(1)
expect(lead(issues[3], 'ToDo')).toBe(1)
})
})
describe("#lead", () => {
test("should return return the lead time for given issue", () => {
expect(lead(issues[0], "ToDo")).toBe(1);
expect(lead(issues[1], "ToDo")).toBe(1);
expect(lead(issues[2], "ToDo")).toBe(1);
expect(lead(issues[3], "ToDo")).toBe(1);
});
});
describe('#cycle', () => {
test('should return return the lead time for given issue', () => {
expect(cycle(issues[0], 'In Progress', 'ToDo')).toBe(1)
expect(cycle(issues[1], 'In Progress', 'ToDo')).toBe(2)
expect(cycle(issues[2], 'In Progress', 'ToDo')).toBe(1)
expect(cycle(issues[3], 'In Progress', 'ToDo')).toBe(8)
})
describe("#cycle", () => {
test("should return return the lead time for given issue", () => {
expect(cycle(issues[0], "In Progress", "ToDo")).toBe(1);
expect(cycle(issues[1], "In Progress", "ToDo")).toBe(2);
expect(cycle(issues[2], "In Progress", "ToDo")).toBe(1);
expect(cycle(issues[3], "In Progress", "ToDo")).toBe(8);
});
test('should use issue creation date when status transition date not available', () => {
expect(cycle(issues[3], 'NonExistent', 'ToDo')).toBe(1)
})
})
test("should use issue creation date when status transition date not available", () => {
expect(cycle(issues[3], "NonExistent", "ToDo")).toBe(1);
});
});
describe('#date_to_status', () => {
test('should return the date transition to a given status', () => {
expect(date_to_status('NonExistentSTatus', issues[0].changelog)).toBeUndefined()
expect(date_to_status('ToDo', issues[0].changelog)).toBe('2017-08-07T11:56:53.090+0000')
expect(date_to_status('In Progress', issues[0].changelog)).toBe('2017-08-07T11:56:57.293+0000')
})
})
describe("#date_to_status", () => {
test("should return the date transition to a given status", () => {
expect(
date_to_status("NonExistentSTatus", issues[0].changelog)
).toBeUndefined();
expect(date_to_status("ToDo", issues[0].changelog)).toBe(
"2017-08-07T11:56:53.090+0000"
);
expect(date_to_status("In Progress", issues[0].changelog)).toBe(
"2017-08-07T11:56:57.293+0000"
);
});
});
describe('#query', () => {
describe('should collect metrics for specific interval', () => {
it('should return correct output for given options', async () => {
const a = await query(options, got)
describe("#query", () => {
describe("should collect metrics for specific interval", () => {
it("should return correct output for given options", async () => {
const a = await query(options, got);
const b = await query(makeOptions({
start: '2017-08-01',
finish: '2017-08-03',
}), got)
const b = await query(
makeOptions({
start: "2017-08-01",
finish: "2017-08-03"
}),
got
);
const c = await query(makeOptions({
start: '2017-08-01',
finish: '2017-08-05',
}), got)
const c = await query(
makeOptions({
start: "2017-08-01",
finish: "2017-08-05"
}),
got
);
expect(a).toMatchObject({throughput: 0.16})
expect(b).toMatchObject({throughput: 2})
expect(c).toMatchObject({throughput: 1})
})
})
})
expect(a).toMatchObject({ throughput: 0.16 });
expect(b).toMatchObject({ throughput: 2 });
expect(c).toMatchObject({ throughput: 1 });
});
});
});
describe('#report', () => {
describe('should collect metrics for specific period & interval', () => {
it('should not be used when report set to false', async () => {
const a = await report(options, got)
const b = await report(makeOptions({period: 9, interval: 3}), got)
expect(a).toHaveLength(2)
expect(b).toHaveLength(3)
})
})
})
})
describe("#report", () => {
describe("should collect metrics for specific period & interval", () => {
it("should not be used when report set to false", async () => {
const a = await report(options, got);
const b = await report(makeOptions({ period: 9, interval: 3 }), got);
expect(a).toHaveLength(2);
expect(b).toHaveLength(3);
});
});
});
});

@@ -1,2 +0,12 @@

const { pad, csv, toJSON, date_ago, days_between, num_reducer, replace, generate_dates, format } = require('../lib/util')
const {
pad,
csv,
toJSON,
date_ago,
days_between,
num_reducer,
replace,
generate_dates,
format
} = require("../lib/util");
const pretty = `{

@@ -6,114 +16,118 @@ "a": "x",

"c": "z"
}`
}`;
describe('util', () => {
describe('#pad', () => {
test('should pad with zero when number is 1 digit', () => {
expect(pad(0)).toBe('00')
expect(pad(1)).toBe('01')
})
describe("util", () => {
describe("#pad", () => {
test("should pad with zero when number is 1 digit", () => {
expect(pad(0)).toBe("00");
expect(pad(1)).toBe("01");
});
test('should not pad with zero when number is 2 digits', () => {
expect(pad(10)).toBe('10')
expect(pad(20)).toBe('20')
})
})
test("should not pad with zero when number is 2 digits", () => {
expect(pad(10)).toBe("10");
expect(pad(20)).toBe("20");
});
});
describe('#csv', () => {
test('should output comma separated list of object values', () => {
expect(csv({})).toBe('')
expect(csv({a:'x', b: 'y', c: 'z'})).toBe('x, y, z')
})
describe("#csv", () => {
test("should output comma separated list of object values", () => {
expect(csv({})).toBe("");
expect(csv({ a: "x", b: "y", c: "z" })).toBe("x, y, z");
});
test('should handle arrays', () => {
expect(csv([{}])).toBe('')
expect(csv([{a:'x', b: 'y', c: 'z'}])).toBe('x, y, z')
})
})
test("should handle arrays", () => {
expect(csv([{}])).toBe("");
expect(csv([{ a: "x", b: "y", c: "z" }])).toBe("x, y, z");
});
});
describe('#toJSON', () => {
test('should output prettified json', () => {
expect(toJSON({a:'x', b: 'y', c: 'z'})).toBe(pretty)
})
})
describe("#toJSON", () => {
test("should output prettified json", () => {
expect(toJSON({ a: "x", b: "y", c: "z" })).toBe(pretty);
});
});
describe('#date_ago', () => {
const d = new Date()
const date = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`
const year = `${d.getFullYear()-1}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`
describe("#date_ago", () => {
const d = new Date();
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(
d.getDate()
)}`;
const year = `${d.getFullYear() - 1}-${pad(d.getMonth() + 1)}-${pad(
d.getDate()
)}`;
test('should return the date string of n days ago', () => {
expect(date_ago(0)).toBe(date)
expect(date_ago(365)).toBe(year)
})
test("should return the date string of n days ago", () => {
expect(date_ago(0)).toBe(date);
expect(date_ago(365)).toBe(year);
});
test('format should be YYYY-MM-DD', () => {
expect(date_ago(10)).toMatch(/^\d{4}-\d{2}-\d{2}$/)
})
})
test("format should be YYYY-MM-DD", () => {
expect(date_ago(10)).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
});
describe('#days_between', () => {
const today = new Date(date_ago(0))
const yesterday = new Date(date_ago(1))
const week = new Date(date_ago(7))
describe("#days_between", () => {
const today = new Date(date_ago(0));
const yesterday = new Date(date_ago(1));
const week = new Date(date_ago(7));
test('should return the date string of n days ago', () => {
expect(days_between(today, week)).toBe(7)
expect(days_between(today, today)).toBe(0)
expect(days_between(today, yesterday)).toBe(1)
})
})
test("should return the date string of n days ago", () => {
expect(days_between(today, week)).toBe(7);
expect(days_between(today, today)).toBe(0);
expect(days_between(today, yesterday)).toBe(1);
});
});
describe('#num_reducer', () => {
test('should return the date string of n days ago', () => {
expect([0].reduce(num_reducer, 0)).toBe(0)
expect([1,2,3].reduce(num_reducer, 0)).toBe(6)
})
})
describe("#num_reducer", () => {
test("should return the date string of n days ago", () => {
expect([0].reduce(num_reducer, 0)).toBe(0);
expect([1, 2, 3].reduce(num_reducer, 0)).toBe(6);
});
});
describe('#replace', () => {
describe("#replace", () => {
let result;
const string = 'the %name cat is %where %noreplaceme'
const string = "the %name cat is %where %noreplaceme";
const options = {
name: 'Felix',
where: 'in the kitchen'
}
name: "Felix",
where: "in the kitchen"
};
beforeEach(() => {
result = replace(string, options)
})
result = replace(string, options);
});
test('should replace %strings for object values', () => {
expect(result).toContain('Felix')
expect(result).toContain('in the kitchen')
})
test("should replace %strings for object values", () => {
expect(result).toContain("Felix");
expect(result).toContain("in the kitchen");
});
test('should replace only matching strings', () => {
expect(result).toContain('%noreplaceme')
})
})
test("should replace only matching strings", () => {
expect(result).toContain("%noreplaceme");
});
});
describe('#generate_dates', () => {
test('should create an array of dates for default interval and period', () => {
expect(generate_dates()).toHaveLength(25)
})
describe("#generate_dates", () => {
test("should create an array of dates for default interval and period", () => {
expect(generate_dates()).toHaveLength(25);
});
test('should create an array of dates for given interval and period', () => {
expect(generate_dates(30, 30)).toHaveLength(1)
expect(generate_dates(15, 20)).toHaveLength(2)
expect(generate_dates(15, 31)).toHaveLength(3)
expect(generate_dates(15, 365)).toHaveLength(25)
})
})
test("should create an array of dates for given interval and period", () => {
expect(generate_dates(30, 30)).toHaveLength(1);
expect(generate_dates(15, 20)).toHaveLength(2);
expect(generate_dates(15, 31)).toHaveLength(3);
expect(generate_dates(15, 365)).toHaveLength(25);
});
});
describe('#format', () => {
test('should return correct output based on input', () => {
const json = format({})
const comma = format({csv: true})
expect(typeof json).toBe('function')
expect(typeof comma).toBe('function')
expect(json({a:'x', b: 'y', c: 'z'})).toBe(pretty)
expect(comma({a: 1, b: 2})).toBe('1, 2')
})
})
})
describe("#format", () => {
test("should return correct output based on input", () => {
const json = format({});
const comma = format({ csv: true });
expect(typeof json).toBe("function");
expect(typeof comma).toBe("function");
expect(json({ a: "x", b: "y", c: "z" })).toBe(pretty);
expect(comma({ a: 1, b: 2 })).toBe("1, 2");
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc