@beauraines/node-helpers 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/index.js +2 -0
- package/package.json +2 -1
- package/src/ado.js +114 -0
- package/src/database.js +3 -3
- package/src/database.test.js +3 -2
- package/src/helpers.js +18 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.6.0](https://github.com/beauraines/node-helpers/compare/v2.5.2...v2.6.0) (2023-06-14)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* Adds Azure DevOps helpers ([#36](https://github.com/beauraines/node-helpers/issues/36)) ([3bf0658](https://github.com/beauraines/node-helpers/commit/3bf065851e2ebbeb17de4d010a8e5e25323f8ecf))
|
|
11
|
+
|
|
12
|
+
### [2.5.2](https://github.com/beauraines/node-helpers/compare/v2.5.0...v2.5.2) (2023-06-03)
|
|
13
|
+
|
|
5
14
|
## [2.5.0](https://github.com/beauraines/node-helpers/compare/v2.4.3...v2.5.0) (2023-06-03)
|
|
6
15
|
|
|
7
16
|
|
|
8
17
|
### Features
|
|
9
18
|
|
|
10
19
|
* new config module ([#32](https://github.com/beauraines/node-helpers/issues/32)) ([514df50](https://github.com/beauraines/node-helpers/commit/514df509ca33527a8b18e5efe43d1b772963e879))
|
|
20
|
+
* supports cross OS file paths ([#33](https://github.com/beauraines/node-helpers/issues/33)) ([27f5e27](https://github.com/beauraines/node-helpers/commit/27f5e27d02408bff07220c3e82bd90186023b324)), closes [#24](https://github.com/beauraines/node-helpers/issues/24)
|
|
11
21
|
|
|
12
22
|
|
|
13
23
|
### Bug Fixes
|
|
14
24
|
|
|
25
|
+
* adds export of new config module ([#34](https://github.com/beauraines/node-helpers/issues/34)) ([81c7a27](https://github.com/beauraines/node-helpers/commit/81c7a27696da3d9a0b6fb3a215418185c9b1d153))
|
|
15
26
|
* **deps:** bump @azure/storage-queue from 12.12.0 to 12.13.0 ([45acd3d](https://github.com/beauraines/node-helpers/commit/45acd3d808ab064c1dbc4b492a013c7b60ebf5b2))
|
|
16
27
|
* **deps:** bump dayjs from 1.11.7 to 1.11.8 ([2208985](https://github.com/beauraines/node-helpers/commit/22089855cd90758273bac28fa1d8669d21f661b5))
|
|
17
28
|
* **deps:** bump node-fetch from 2.6.9 to 2.6.11 ([d9fbed2](https://github.com/beauraines/node-helpers/commit/d9fbed2e6ade418caada9cc68f6ac05ba4d3159d))
|
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
const AzureStorage = require("./src/azure")
|
|
3
|
+
const config = require('./src/config.js')
|
|
3
4
|
const credentials = require("./src/credentials.js");
|
|
4
5
|
const database = require("./src/database");
|
|
5
6
|
const helpers = require("./src/helpers");
|
|
@@ -7,6 +8,7 @@ const jira = require("./src/jira");
|
|
|
7
8
|
|
|
8
9
|
module.exports = {
|
|
9
10
|
AzureStorage,
|
|
11
|
+
config,
|
|
10
12
|
credentials,
|
|
11
13
|
database,
|
|
12
14
|
helpers,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beauraines/node-helpers",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Collection of node helpers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"license": "ISC",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@azure/storage-queue": "^12.11.0",
|
|
15
|
+
"azure-devops-node-api": "^12.0.0",
|
|
15
16
|
"azure-storage": "^2.10.7",
|
|
16
17
|
"dayjs": "^1.11.7",
|
|
17
18
|
"node-fetch": "^2.6.7",
|
package/src/ado.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const azdev = require('azure-devops-node-api')
|
|
2
|
+
const {getResourceId} = require('./helpers.js')
|
|
3
|
+
const {readConfig } = require('./config.js')
|
|
4
|
+
const fetch = require('node-fetch');
|
|
5
|
+
|
|
6
|
+
// get Current iteration
|
|
7
|
+
// Get current iteration work items
|
|
8
|
+
// get Teams
|
|
9
|
+
// get workitems
|
|
10
|
+
// update work items
|
|
11
|
+
|
|
12
|
+
async function getAdoApi(configFile) {
|
|
13
|
+
const config = await readConfig(configFile)
|
|
14
|
+
|
|
15
|
+
const orgUrl = config.org;
|
|
16
|
+
// let token: string = process.env.AZURE_PERSONAL_ACCESS_TOKEN;
|
|
17
|
+
const token= config.token
|
|
18
|
+
const teamContext = { project: config.project, team: config.team};
|
|
19
|
+
|
|
20
|
+
const authHandler = azdev.getPersonalAccessTokenHandler(token);
|
|
21
|
+
const connection = new azdev.WebApi(orgUrl, authHandler);
|
|
22
|
+
|
|
23
|
+
const workAPI = await connection.getWorkApi();
|
|
24
|
+
|
|
25
|
+
const workItemAPI = await connection.getWorkItemTrackingApi();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
workAPI,
|
|
29
|
+
workItemAPI,
|
|
30
|
+
teamContext,
|
|
31
|
+
config
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getChildWorkItems(workItemAPI, id) {
|
|
36
|
+
let parentWorkItem = await workItemAPI.getWorkItem(id, undefined, undefined, 'Relations');
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
const childWorkItemIds = parentWorkItem.relations.filter(x => x.attributes.name == 'Child').map((x) => {
|
|
40
|
+
return getResourceId(x.url)
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let childWorkItems = childWorkItemIds.map(id => workItemAPI.getWorkItem(id, undefined, undefined));
|
|
44
|
+
childWorkItems = await Promise.all(childWorkItems);
|
|
45
|
+
|
|
46
|
+
return childWorkItems
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Gets array of distinct parent items when passed an array of work-item IDs
|
|
51
|
+
*
|
|
52
|
+
* @param {WorkItemAPI} workItemAPI
|
|
53
|
+
* @param {Array} ids The array of work-items of which to find the distinct parents
|
|
54
|
+
* @returns WorkItems[] Array of distinct parent work itesm
|
|
55
|
+
*/
|
|
56
|
+
async function getDistinctParentWorkItems(workItemAPI, ids) {
|
|
57
|
+
let workItems = ids.map(id => {
|
|
58
|
+
return workItemAPI.getWorkItem(id, undefined, undefined, 'Relations')
|
|
59
|
+
})
|
|
60
|
+
workItems = await Promise.all(workItems)
|
|
61
|
+
|
|
62
|
+
let parentIds = workItems.map(wi => {
|
|
63
|
+
const parent = wi?.relations?.filter(x => x.attributes.name == 'Parent')[0]
|
|
64
|
+
return parent ? getResourceId(parent.url) : null
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
let parentWorkItems = parentIds.map(parentId => workItemAPI.getWorkItem(parentId,undefined, undefined, 'Relations'))
|
|
68
|
+
parentWorkItems = await Promise.all(parentWorkItems)
|
|
69
|
+
parentWorkItems = parentWorkItems.filter(x => x!=null)
|
|
70
|
+
|
|
71
|
+
let distinctParentWorkItemIds = [...new Set(parentWorkItems.map(wi => wi?.id))];
|
|
72
|
+
|
|
73
|
+
let distinctParentWorkItems = distinctParentWorkItemIds.map(distinctParentId => workItemAPI.getWorkItem(distinctParentId,undefined, undefined, 'Relations'))
|
|
74
|
+
distinctParentWorkItems = await Promise.all(distinctParentWorkItems)
|
|
75
|
+
|
|
76
|
+
return distinctParentWorkItems
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
// TODO do something different about how to Auth
|
|
81
|
+
async function callRestApi(url, username, token) {
|
|
82
|
+
// console.log(url) // Only display this in verbose mode
|
|
83
|
+
// Bearer token format for ADO
|
|
84
|
+
// eslint-disable-next-line no-undef
|
|
85
|
+
let bearerToken = Buffer.from(`${username}:${token}`).toString('base64');
|
|
86
|
+
|
|
87
|
+
let response;
|
|
88
|
+
try {
|
|
89
|
+
response = await fetch(url, {
|
|
90
|
+
method: 'GET',
|
|
91
|
+
headers: {
|
|
92
|
+
'Authorization': `Basic ${bearerToken}`,
|
|
93
|
+
'Accept': 'application/json',
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (response.ok) {
|
|
99
|
+
return response.json();
|
|
100
|
+
} else {
|
|
101
|
+
throw new Error(response.statusText);
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(error.message)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
getAdoApi,
|
|
111
|
+
getChildWorkItems,
|
|
112
|
+
getDistinctParentWorkItems,
|
|
113
|
+
callRestApi
|
|
114
|
+
}
|
package/src/database.js
CHANGED
|
@@ -2,15 +2,15 @@ const {homedir} = require('os');
|
|
|
2
2
|
const sqlite = require('sqlite');
|
|
3
3
|
const sqlite3 = require('sqlite3');
|
|
4
4
|
const {fileExists} = require('./helpers');
|
|
5
|
+
const path = require('path');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Opens the BurnDownStatus SQLite3 Database
|
|
8
|
-
* @param file file name of the SQLite3 DB. If not provided, defaults to
|
|
9
|
+
* @param file file name of the SQLite3 DB. If not provided, defaults to BurnDownStatus.db in the users home
|
|
9
10
|
* @returns SQLite database connection
|
|
10
11
|
*/
|
|
11
12
|
async function getDBConnection(file) {
|
|
12
|
-
|
|
13
|
-
file = file ? file : `${homeDir}/BurnDownStatus.db`;
|
|
13
|
+
file = file ? file : path.join(homedir(),'BurnDownStatus.db');
|
|
14
14
|
if (! await fileExists(file)){
|
|
15
15
|
console.error(`${file} not found`);
|
|
16
16
|
// ! Separation of concerns - this should probably not be doing the exiting, but it is.
|
package/src/database.test.js
CHANGED
|
@@ -3,6 +3,7 @@ const sqlite = require('sqlite');
|
|
|
3
3
|
const sqlite3 = require('sqlite3');
|
|
4
4
|
const helpers = require('./helpers');
|
|
5
5
|
const { getDBConnection } = require('./database');
|
|
6
|
+
const path = require('path');
|
|
6
7
|
|
|
7
8
|
jest.mock('sqlite');
|
|
8
9
|
jest.mock('os');
|
|
@@ -32,7 +33,7 @@ describe('database module', () => {
|
|
|
32
33
|
helpers.fileExists.mockReturnValue(true);
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
const expectedDefaultFile =
|
|
36
|
+
const expectedDefaultFile = path.join(os.homedir(),'BurnDownStatus.db')
|
|
36
37
|
|
|
37
38
|
const file = undefined;
|
|
38
39
|
// call function with null file
|
|
@@ -56,7 +57,7 @@ describe('database module', () => {
|
|
|
56
57
|
os.homedir.mockReturnValue(expectedHomeDir);
|
|
57
58
|
helpers.fileExists.mockReturnValue(true);
|
|
58
59
|
|
|
59
|
-
const expectedDefaultFile =
|
|
60
|
+
const expectedDefaultFile = path.join(os.homedir(),'BurnDownStatus.db')
|
|
60
61
|
|
|
61
62
|
const db = await getDBConnection(expectedDefaultFile)
|
|
62
63
|
|
package/src/helpers.js
CHANGED
|
@@ -121,8 +121,26 @@ function sparkline(data,label,options) {
|
|
|
121
121
|
return `${label} [${minValue},${maxValue}] ${sparkly(data.map( x=> x- minValue))} ${lastValue}`
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Given a RESTful url e.g. https://www.example.com/app/#list/44719910/959889147?id=12234&363636=334'
|
|
126
|
+
* this function will return the resource ID, ignoring the query parameters
|
|
127
|
+
*
|
|
128
|
+
* @param {string} url The URL to find the resource or last part of the routing
|
|
129
|
+
* @returns string
|
|
130
|
+
*/
|
|
131
|
+
const getResourceId = (url) => {
|
|
132
|
+
let queryStringStart = url.lastIndexOf('?')
|
|
133
|
+
if (queryStringStart == -1) {
|
|
134
|
+
queryStringStart = url.length
|
|
135
|
+
}
|
|
136
|
+
const lastSlash = url.lastIndexOf('/')
|
|
137
|
+
const id = url.substring(lastSlash+1,queryStringStart)
|
|
138
|
+
return id
|
|
139
|
+
}
|
|
140
|
+
|
|
124
141
|
module.exports = {
|
|
125
142
|
getEpochMillis,
|
|
143
|
+
getResourceId,
|
|
126
144
|
fileExists,
|
|
127
145
|
groupAndSum,
|
|
128
146
|
readFile,
|