Skip to content
This repository has been archived by the owner on Jun 11, 2020. It is now read-only.

Return pending build requests from getSnapBuilds #1154

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ To search for snap builds:
GET /api/launchpad/builds?snap_link=:snap
Accept: application/json

On success, returns the following, where the items in `builds` are
On success, returns the following, where the items in `builds` are either
[snap\_build entries](https://launchpad.net/+apidoc/devel.html#snap_build)
or
[snap\_build\_request entries](https://launchpad.net/+apidoc/devel.html#snap_build_request)
as returned by the Launchpad API:

HTTP/1.1 200 OK
Expand Down
21 changes: 21 additions & 0 deletions migrations/20180803154038_build-request-annotations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
exports.up = function(knex, Promise) {
return Promise.all([
knex.schema.createTable('build_request_annotation', function(table) {
table.integer('request_id').notNullable();
table.text('reason');
table.timestamps();
table.primary('request_id');
}).table('build_annotation', function(table) {
table.integer('request_id')
.references('build_request_annotation.request_id');
})
]);
};

exports.down = function(knex, Promise) {
return Promise.all([
knex.schema.table('build_annotation', function(table) {
table.dropColumn('request_id');
}).dropTable('build_request_annotation')
]);
};
13 changes: 11 additions & 2 deletions src/common/containers/builds.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ export class Builds extends Component {
renderHelpBoxes() {
const { snap } = this.props;
const { builds } = this.props.snapBuilds;
const isPublished = builds.some((build) => build.isPublished);
const isPublished = builds.some(
(build) => !build.isRequest && build.isPublished
);

if (snap && snap.storeName && isPublished) {
return (
Expand Down Expand Up @@ -123,7 +125,14 @@ export class Builds extends Component {
getLatestAndPreviousBuilds() {
return this.props.snapBuilds.builds.reduce((builds, build) => {
let { latest, previous } = builds;
if (latest.filter(b => b.architecture === build.architecture).length === 0) {
if (build.isRequest && build.statusMessage === 'Building soon') {
latest.push(build);
} else if (
!build.isRequest &&
latest.filter(
b => !b.isRequest && b.architecture === build.architecture
).length === 0
) {
latest.push(build);
} else {
previous.push(build);
Expand Down
12 changes: 9 additions & 3 deletions src/common/helpers/build_annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ const BUILD_TRIGGERED_BY_POLLER = 'triggered-by-poller';
const BUILD_TRIGGER_UNKNOWN = 'trigger-unknown';


function getBuildId(build) {
return parseInt(build.self_link.split('/').pop(), 10);
function getLinkId(link) {
return parseInt(link.split('/').pop(), 10);
}


function getSelfId(entry) {
return getLinkId(entry.self_link);
}


Expand All @@ -20,5 +25,6 @@ export {
BUILD_TRIGGERED_BY_WEBHOOK,
BUILD_TRIGGERED_BY_POLLER,
BUILD_TRIGGER_UNKNOWN,
getBuildId
getLinkId,
getSelfId
};
7 changes: 5 additions & 2 deletions src/server/db/models/build_annotation.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@

export default function register(db) {
/* Schema:
* build_id: Launchpad build ID (unique)
* reason: reason why the build was triggered.
* reason: reason why the build was triggered
* request: associated build request
*/
db.model('BuildAnnotation', {
tableName: 'build_annotation',
idAttribute: 'build_id',
request: function() {
return this.belongsTo('BuildRequestAnnotation', 'request_id');
},
hasTimestamps: true
});
}
15 changes: 15 additions & 0 deletions src/server/db/models/build_request_annotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function register(db) {
/* Schema:
* request_id: Launchpad build request ID (unique)
* reason: reason why the build request was issued
* builds: all builds triggered as a result of this build request
*/
db.model('BuildRequestAnnotation', {
tableName: 'build_request_annotation',
idAttribute: 'request_id',
builds: function() {
return this.hasMany('BuildAnnotation', 'request_id');
},
hasTimestamps: true
});
}
2 changes: 2 additions & 0 deletions src/server/db/models/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import registerBuildAnnotation from './build_annotation';
import registerBuildRequestAnnotation from './build_request_annotation';
import registerGitHubUser from './github-user';
import registerRepository from './repository';

export default function register(db) {
registerBuildAnnotation(db);
registerBuildRequestAnnotation(db);
registerGitHubUser(db);
registerRepository(db);
}
78 changes: 68 additions & 10 deletions src/server/handlers/launchpad.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { normalize } from 'normalizr';

import {
BUILD_TRIGGERED_MANUALLY,
getBuildId
getSelfId
} from '../../common/helpers/build_annotation';
import { parseGitHubRepoUrl } from '../../common/helpers/github-url';
import db from '../db';
Expand Down Expand Up @@ -684,10 +684,27 @@ export const authorizeSnap = async (req, res) => {
}
};

export async function internalGetSnapBuilds(snap, start = 0, size = 10) {
export async function internalGetSnapBuilds(
snap, start = 0, size = 10, options = {}
) {
const builds = [];
let gotItems = 0;

if (options.withRequests && gotItems < size) {
const buildRequests = await getLaunchpad().get(
snap.pending_build_requests_collection_link, { start, size }
);
for await (const buildRequest of buildRequests) {
builds.push(buildRequest);
}
// XXX cjwatson 2018-10-01: We should also include previously-failed
// build requests, since they might fail for reasons that the developer
// needs to fix (e.g. malformed snapcraft.yaml). Doing this in a
// pagination-friendly way requires further work on the Launchpad APIs
// we're using.
gotItems += buildRequests.total_size;
}

if (gotItems < size) {
const pendingBuilds = await getLaunchpad().get(
snap.pending_builds_collection_link, { start, size: size - gotItems }
Expand All @@ -710,18 +727,50 @@ export async function internalGetSnapBuilds(snap, start = 0, size = 10) {
return builds;
}

async function internalGetBuildAnnotations(builds) {
const db_annotations = await db.model('BuildAnnotation')
.where('build_id', 'IN', builds.map((b) => { return getBuildId(b); }))
async function internalGetBuildRequestAnnotations(builds) {
if (builds.length === 0) {
return {};
}

let build_request_annotations = {};

const db_annotations = await db.model('BuildRequestAnnotation')
.where('request_id', 'IN', builds.map(getSelfId))
.fetchAll();

let build_annotations = {};
for (const m of db_annotations.models) {
build_annotations[m.get('build_id')] = {
build_request_annotations[m.get('request_id')] = {
reason: m.get('reason')
};
}

return build_request_annotations;
}

async function internalGetBuildAnnotations(builds) {
if (builds.length === 0) {
return {};
}

let build_annotations = {};

const db_annotations = await db.model('BuildAnnotation')
.where('build_id', 'IN', builds.map(getSelfId))
.fetchAll({ withRelated: ['request'] });

for (const m of db_annotations.models) {
let reason = m.get('reason');
if (reason === null) {
const request_annotation = m.related('request');
if (request_annotation) {
reason = request_annotation.get('reason');
}
}
if (reason !== null) {
build_annotations[m.get('build_id')] = { reason };
}
}

return build_annotations;
}

Expand All @@ -742,15 +791,23 @@ export const getSnapBuilds = async (req, res) => {
const snap = await getLaunchpad().get(snapUrl);

const builds = await internalGetSnapBuilds(
snap, req.query.start, req.query.size
snap, req.query.start, req.query.size, { withRequests: true }
);
const build_request_annotations = await internalGetBuildRequestAnnotations(
builds.filter(
build => build.resource_type_link.endsWith('#snap_build_request')
)
);
const build_annotations = await internalGetBuildAnnotations(
builds.filter(build => build.resource_type_link.endsWith('#snap_build'))
);
const build_annotations = await internalGetBuildAnnotations(builds);

return res.status(200).send({
status: 'success',
payload: {
code: 'snap-builds-found',
builds,
build_request_annotations,
build_annotations
}
});
Expand Down Expand Up @@ -799,7 +856,7 @@ export const internalRequestSnapBuilds = async (snap, owner, name, reason) => {
// is not transactional).
const build_annotations = builds.map((b) => {
return {
build_id: getBuildId(b),
build_id: getSelfId(b),
reason: reason
};
});
Expand Down Expand Up @@ -835,6 +892,7 @@ export const requestSnapBuilds = async (req, res) => {
payload: {
code: 'snap-builds-requested',
builds: builds,
build_request_annotations: {},
build_annotations: build_annotations
}
});
Expand Down