-
Notifications
You must be signed in to change notification settings - Fork 0
Init implementation #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| parser: babel-eslint | ||
|
|
||
| parserOptions: | ||
| ecmaVersion": 6, | ||
| sourceType: module | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,96 @@ Install | |
| $ npm install --save teamcity-build-queue | ||
| ``` | ||
|
|
||
| Usage | ||
| ----- | ||
|
|
||
| ```js | ||
| const queueInfo = require('teamcity-build-queue'); | ||
|
|
||
| queueInfo('http://teamcity.domain.com', { | ||
| projectPattern: 'project :: Pull requests :: *', | ||
| ignoreDependencies: true, // ignore builds with dependencies that have not been built yet | ||
| ignoreIncompatibleAgents: true // ignore builds without compatible agents | ||
| }) | ||
| .then(queue => { | ||
| console.log(queue.builds); | ||
| console.log(queue.size); | ||
| }); | ||
| ``` | ||
|
|
||
| API | ||
| --- | ||
|
|
||
| ### queueInfo(url[, options]) | ||
|
|
||
| Returns a Promise, that resolves to object with builds from Build Queue. | ||
|
|
||
| #### url | ||
|
|
||
| Type: `string` | ||
|
|
||
| The URL to TeamCity host. | ||
|
|
||
| #### options | ||
|
|
||
| Type: `object` | ||
|
|
||
| #### options.projectPattern | ||
|
|
||
| Type: `string` | ||
|
|
||
| The pattern of project name to filter builds. | ||
|
|
||
| If pattern is not specified, then all builds will be in the result. | ||
|
|
||
| **Wildcards** | ||
|
|
||
| ```js | ||
| queueInfo('http://teamcity.domain.com', { | ||
| projectPattern: 'project :: Pull requests :: *' | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А как правильно написать, чтобы все подпроекты учитывались? Две звёздочки?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Одна звёздочка матчится на все подпроекты. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Понятно, давай может добавим такой случай в пример ниже? |
||
| }); | ||
|
|
||
| // Will be taken into account builds the following assemblies: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "assemblies" какой-то не ТС термин. Projects? Configurations? |
||
|
|
||
| // project :: Pull requests :: build | ||
| // project :: Pull requests :: tests :: unit | ||
| // project :: Pull requests :: tests :: e2e | ||
| // project :: Pull requests :: docs | ||
| // project :: Pull requests :: deploy | ||
| // ... | ||
| ``` | ||
|
|
||
| **Brace Expansion** | ||
|
|
||
| ```js | ||
| queueInfo('http://teamcity.domain.com', { | ||
| projectPattern: 'project :: {Pull requests, dev} :: *' | ||
| }); | ||
|
|
||
| // Will be taken into account builds the following configurations: | ||
| // | ||
| // project :: Pull requests :: build | ||
| // project :: Pull requests :: tests | ||
| // ... | ||
| // project :: dev :: build | ||
| // project :: dev :: tests | ||
| // ... | ||
| ``` | ||
|
|
||
| Read more about it in [micromatch](https://github.com/jonschlinkert/micromatch#features) package. | ||
|
|
||
| #### options.ignoreDependencies | ||
|
|
||
| Type: `string` Default: `false` | ||
|
|
||
| To ignore builds with dependencies that have not been built yet. | ||
|
|
||
| #### options.ignoreIncompatibleAgents | ||
|
|
||
| Type: `string` Default: `false` | ||
|
|
||
| To ignore builds without compatible agents. | ||
|
|
||
| License | ||
| ------- | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| 'use strict'; | ||
|
|
||
| const assert = require('assert'); | ||
| const url = require('url'); | ||
|
|
||
| const isUrl = require('is-url'); | ||
| const got = require('got'); | ||
|
|
||
| const load = require('./lib/load-build'); | ||
| const filterBuilds = require('./lib/filter-builds'); | ||
|
|
||
| const TEAMCITY_BUILD_QUEUE_PATHNAME = 'guestAuth/app/rest/buildQueue'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Пугает guestAuth, особенно в контексте отказа от него.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Меня тоже :) Давай обсудим голосом. |
||
|
|
||
| /** | ||
| * Loads info about TeamCity Build Queue. | ||
| * | ||
| * @param {string} teamcityUrl - the URL to TeamCity host. | ||
| * @param {object} options - the options. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. THE options!
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Линтер обязывает |
||
| * @param {string} options.projectPattern - the pattern of project name to filter builds. | ||
| * @param {boolean} options.ignoreDependencies - to ignore builds with dependencies that have not been built yet. | ||
| * @param {boolean} options.ignoreIncompatibleAgents - to ignore builds without compatible agents. | ||
| * @returns {{builds: object[], size: number}} | ||
| */ | ||
| module.exports = (teamcityUrl, options) => { | ||
| assert(isUrl(teamcityUrl), 'You should specify url to TeamCity'); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. А я обычно юзаю console.assert. Есть какая-то разница?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Меня смущает, что поведение There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Какое другое? Не знал.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| return got(url.resolve(teamcityUrl, TEAMCITY_BUILD_QUEUE_PATHNAME), { json: true }) | ||
| .then(response => { | ||
| const body = response.body; | ||
| const builds = body && body.build || []; | ||
|
|
||
| return Promise.all(builds.map(build => { | ||
| const buildUrl = url.resolve(teamcityUrl, build.href); | ||
|
|
||
| return load(buildUrl); | ||
| })); | ||
| }) | ||
| .then(builds => filterBuilds(builds, options)) | ||
| .then(builds => ({ | ||
| builds: builds, | ||
| size: builds.length | ||
| })); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| const mm = require('micromatch'); | ||
|
|
||
| /** | ||
| * Removes slashes. | ||
| * | ||
| * @param {string} str - some string. | ||
| * @returns {string} | ||
| */ | ||
| function clearSlash(str) { | ||
| return str.replace(/\//g, ''); | ||
| } | ||
|
|
||
| /** | ||
| * Returns `true` if the project name of build match the project pattern. | ||
| * | ||
| * @param {object} build - the build info. | ||
| * @param {string} projectPattern - the pattern of project name to filter builds. | ||
| * @returns {boolean} | ||
| */ | ||
| function isMatchProjectName(build, projectPattern) { | ||
| const buildType = build.buildType; | ||
| const projectName = buildType && buildType.projectName; | ||
|
|
||
| return mm.isMatch(clearSlash(projectName), clearSlash(projectPattern), { nocase: true }); | ||
| } | ||
|
|
||
| /** | ||
| * Returns `true` the build has dependency builds. | ||
| * | ||
| * @param {object} build - the build info. | ||
| * @returns {boolean} | ||
| */ | ||
| function hasDependencies(build) { | ||
| return build.waitReason === 'Build dependencies have not been built yet'; | ||
| } | ||
|
|
||
| /** | ||
| * Returns `true` the build in queue and has compatible agents. | ||
| * | ||
| * @param {object} build - the build info. | ||
| * @returns {boolean} | ||
| */ | ||
| function isQueuedWithCompatibleAgents(build) { | ||
| const compatibleAgents = build.compatibleAgents; | ||
| const isQueued = build.state === 'queued'; | ||
| const hasCompatibleAgents = compatibleAgents && compatibleAgents.length !== 0; | ||
|
|
||
| return isQueued ? hasCompatibleAgents : true; | ||
| } | ||
|
|
||
| /** | ||
| * Filters builds by projectPattern and ignores inappropriate builds. | ||
| * | ||
| * @param {object[]} builds - the objects with build info. | ||
| * @param {string} options - the options. | ||
| * @param {string} options.projectPattern - the pattern of project name to filter builds. | ||
| * @param {boolean} options.ignoreDependencies - to ignore builds with dependencies that have not been built yet. | ||
| * @param {boolean} options.ignoreIncompatibleAgents - to ignore builds without compatible agents. | ||
| * @returns {object[]} | ||
| */ | ||
| module.exports = (builds, options) => { | ||
| const projectPattern = options.projectPattern; | ||
|
|
||
| return builds.filter(build => { | ||
| if (projectPattern && !isMatchProjectName(build, projectPattern) | ||
| || options.ignoreDependencies && hasDependencies(build) | ||
| || options.ignoreIncompatibleAgents && !isQueuedWithCompatibleAgents(build) | ||
| ) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| }); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| const got = require('got'); | ||
|
|
||
| /** | ||
| * Loads info about compatible agents of build. | ||
| * | ||
| * @param {string} buildUrl - the relative url to build. | ||
| * @returns {object} | ||
| */ | ||
| function loadBuild(buildUrl) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Смущает, что сюда приходит ссылка, а не buildId, например. Что ссылка формируется где-то в другом месте. Размазывание логики, кмк. Но решай сам.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Одного Но формирование урла зависит от переданного |
||
| return got(buildUrl, { json: true }) | ||
| .then(response => response.body); | ||
| } | ||
|
|
||
| /** | ||
| * Loads info about compatible agents of build. | ||
| * | ||
| * @param {string} buildUrl - the relative url to build. | ||
| * @returns {object} | ||
| */ | ||
| function loadCompatibleAgents(buildUrl) { | ||
| const agentsUrl = `${buildUrl}/compatibleAgents`; | ||
|
|
||
| return got(agentsUrl, { json: true }) | ||
| .then(response => response.body.agent) | ||
| .catch(() => ([])); // If build had time to start, compatible agents url returns error. | ||
| } | ||
|
|
||
| /** | ||
| * Loads info about build with it compatible agents. | ||
| * | ||
| * @param {string} buildUrl - the relative url to build. | ||
| * @returns {object} | ||
| */ | ||
| module.exports = (buildUrl) => { | ||
| return Promise.all([ | ||
| loadBuild(buildUrl), | ||
| loadCompatibleAgents(buildUrl) | ||
| ]).then(data => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. там нету spread?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Нет, в Promise.all([...]).then([build, agents] => {});Но в 4й ноде его нет. |
||
| const build = data[0]; | ||
| const compatibleAgents = data[1]; | ||
|
|
||
| build.compatibleAgents = compatibleAgents; | ||
|
|
||
| return build; | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| 'use strict'; | ||
|
|
||
| const test = require('ava'); | ||
| const proxyquire = require('proxyquire'); | ||
| const sinon = require('sinon'); | ||
|
|
||
| test.beforeEach(t => { | ||
| t.context.gotStub = sinon.stub().resolves({ body: {} }); | ||
| t.context.loadStub = sinon.stub().resolves([]); | ||
| t.context.filterStub = sinon.stub().returns([]); | ||
|
|
||
| t.context.run = proxyquire('../index', { | ||
| got: t.context.gotStub, | ||
| './lib/load-build': t.context.loadStub, | ||
| './lib/filter-builds': t.context.filterStub | ||
| }); | ||
| }); | ||
|
|
||
| test('should get info by url', async t => { | ||
| await t.context.run('http://tc.url'); | ||
|
|
||
| t.true(t.context.gotStub.calledWith('http://tc.url/guestAuth/app/rest/buildQueue')); | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Где же async/await? :) |
||
|
|
||
| test('should returns builds of queue', async t => { | ||
| const builds = [{ id: '1' }]; | ||
|
|
||
| t.context.filterStub.returns(builds); | ||
|
|
||
| const queue = await t.context.run('http://tc.url'); | ||
|
|
||
| t.deepEqual(queue.builds, builds); | ||
| }); | ||
|
|
||
| test('should returns queue size', async t => { | ||
| const builds = [{ id: '1' }]; | ||
|
|
||
| t.context.filterStub.returns(builds); | ||
|
|
||
| const queue = await t.context.run('http://tc.url'); | ||
|
|
||
| t.is(queue.size, builds.length); | ||
| }); | ||
|
|
||
| test('should load build info', async t => { | ||
| t.context.gotStub.resolves({ | ||
| body: { | ||
| href: '/guestAuth/app/rest/buildQueue', | ||
| count: 1, | ||
| build: [{ href: 'build-url-1' }] | ||
| } | ||
| }); | ||
|
|
||
| await t.context.run('http://tc.url'); | ||
|
|
||
| t.true(t.context.loadStub.calledWith('http://tc.url/build-url-1')); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| 'use strict'; | ||
|
|
||
| const test = require('ava'); | ||
|
|
||
| const queueInfo = require('../index'); | ||
|
|
||
| test('should throw error if url is not speсified', t => { | ||
| t.throws( | ||
| () => queueInfo(), | ||
| 'You should specify url to TeamCity' | ||
| ); | ||
| }); | ||
|
|
||
| test('should throw error if url is not url', t => { | ||
| t.throws( | ||
| () => queueInfo('bla'), | ||
| 'You should specify url to TeamCity' | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это как-то автоматически генерируется?
Меня всегда смущало, что имена методов и параметров в такой доке пишутся не
моноширинным шрифтом. Типа, таким образом иллюстрируя, что это код.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я руками пишу.
И так понятно что это про код. А использовать двойные выделения (заголовок уже выделен) плохой тон.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно генерить чем-то вроде verb.