Skip to content
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

Support forwarding Android mappings to App Center Diagnostics #10586

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"loc.helpMarkDown": "For help with this task, visit the Visual Studio App Center [support site](https://aka.ms/appcentersupport/).",
"loc.description": "Distribute app builds to testers and users via Visual Studio App Center",
"loc.instanceNameFormat": "Deploy $(app) to Visual Studio App Center",
"loc.releaseNotes": "Added support for multiple destinations.",
"loc.releaseNotes": "Added support for forwarding Android mapping to App Center Diagnostics.",
"loc.group.displayName.symbols": "Symbols",
"loc.input.label.serverEndpoint": "App Center service connection",
"loc.input.help.serverEndpoint": "Select the service connection for Visual Studio App Center. To create one, click the Manage link and create a new service connection.",
Expand Down
10 changes: 10 additions & 0 deletions Tasks/AppCenterDistributeV3/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,14 @@ describe('AppCenterDistribute L0 Suite', function () {
tr.run();
assert(tr.succeeded, 'task should have succeeded');
});

it('Positive path: upload Android mapping txt to diagnostics', function () {
this.timeout(4000);

let tp = path.join(__dirname, 'L0AndroidMappingTxtProvided.js');
let tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);

tr.run();
assert(tr.succeeded, 'task should have succeeded');
});
});
169 changes: 169 additions & 0 deletions Tasks/AppCenterDistributeV3/Tests/L0AndroidMappingTxtProvided.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@

import ma = require('vsts-task-lib/mock-answer');
import tmrm = require('vsts-task-lib/mock-run');
import path = require('path');
import fs = require('fs');
import azureBlobUploadHelper = require('../azure-blob-upload-helper');

var Readable = require('stream').Readable
var Writable = require('stream').Writable
var Stats = require('fs').Stats

var nock = require('nock');

let taskPath = path.join(__dirname, '..', 'appcenterdistribute.js');
let tmr: tmrm.TaskMockRunner = new tmrm.TaskMockRunner(taskPath);

tmr.setInput('serverEndpoint', 'MyTestEndpoint');
tmr.setInput('appSlug', 'testuser/testapp');
tmr.setInput('app', '/test/path/to/my.apk');
tmr.setInput('releaseNotesSelection', 'releaseNotesInput');
tmr.setInput('releaseNotesInput', 'my release notes');
tmr.setInput('symbolsType', 'AndroidProguard');
tmr.setInput('mappingTxtPath', 'a/**/mapping.txt');

/*
Mapping folder structure:
a
mapping.txt
*/

//prepare upload
nock('https://example.test')
.post('/v0.1/apps/testuser/testapp/release_uploads')
.reply(201, {
upload_id: 1,
upload_url: 'https://example.upload.test/release_upload'
});

//upload
nock('https://example.upload.test')
.post('/release_upload')
.reply(201, {
status: 'success'
});

//finishing upload, commit the package
nock('https://example.test')
.patch('/v0.1/apps/testuser/testapp/release_uploads/1', {
status: 'committed'
})
.reply(200, {
release_id: '1',
release_url: 'my_release_location'
});

//make it available
nock('https://example.test')
.post('/v0.1/apps/testuser/testapp/releases/1/groups', {
id: "00000000-0000-0000-0000-000000000000"
})
.reply(200);



nock('https://example.test')
.put('/v0.1/apps/testuser/testapp/releases/1', JSON.stringify({
release_notes: 'my release notes'
}))
.reply(200);

nock('https://example.test')
.get('/v0.1/apps/testuser/testapp/releases/1')
.reply(200, {
short_version: "1.0",
version: "1"
});

//begin symbol upload
nock('https://example.test')
.post('/v0.1/apps/testuser/testapp/symbol_uploads', {
symbol_type: "AndroidProguard",
file_name: "mapping.txt",
version: "1.0",
build: "1"
})
.reply(201, {
symbol_upload_id: 100,
upload_url: 'https://example.upload.test/symbol_upload',
expiration_date: 1234567
});

//finishing symbol upload, commit the symbol
nock('https://example.test')
.patch('/v0.1/apps/testuser/testapp/symbol_uploads/100', {
status: 'committed'
})
.reply(200);

// provide answers for task mock
let a: ma.TaskLibAnswers = <ma.TaskLibAnswers>{
'checkPath' : {
'/test/path/to/my.apk': true,
'a': true,
'a/mapping.txt': true
},
'findMatch' : {
'a/**/mapping.txt': [
'a/mapping.txt'
],
'/test/path/to/my.apk': [
'/test/path/to/my.apk'
]
}
};
tmr.setAnswers(a);

fs.createReadStream = (s: string) => {
let stream = new Readable;
stream.push(s);
stream.push(null);

return stream;
};

fs.createWriteStream = (s: string) => {
let stream = new Writable;

stream.write = () => {};

return stream;
};

fs.readdirSync = (folder: string) => {
let files: string[] = [];

if (folder === 'a') {
files = [
'mapping.txt'
]
}

return files;
};

fs.statSync = (s: string) => {
const stat = new Stats;

stat.isFile = () => {
return s.endsWith('.txt');
}

stat.isDirectory = () => {
return !s.endsWith('.txt');
}

stat.size = 100;

return stat;
}

azureBlobUploadHelper.AzureBlobUploadHelper.prototype.upload = async () => {
return Promise.resolve();
}

tmr.registerMock('azure-blob-upload-helper', azureBlobUploadHelper);
tmr.registerMock('fs', fs);

tmr.run();

54 changes: 47 additions & 7 deletions Tasks/AppCenterDistributeV3/appcenterdistribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ const DestinationTypeParameter = {
[DestinationType.Store]: "stores"
}

interface Release {
version: string;
short_version: string;
}

type SymbolType = "Apple" | "AndroidProguard" | "UWP";

function getEndpointDetails(endpointInputFieldName) {
var errorMessage = tl.loc("CannotDecodeEndpoint");
var endpoint = tl.getInput(endpointInputFieldName, true);
Expand Down Expand Up @@ -248,6 +255,27 @@ function updateRelease(apiServer: string, apiVersion: string, appSlug: string, r
return defer.promise;
}

function getRelease(apiServer: string, apiVersion: string, appSlug: string, releaseId: string, token: string, userAgent: string): Q.Promise<Release> {
tl.debug("-- Getting release.");
let defer = Q.defer<Release>();
let getReleaseUrl: string = `${apiServer}/${apiVersion}/apps/${appSlug}/releases/${releaseId}`;
tl.debug(`---- url: ${getReleaseUrl}`);

let headers = {
"X-API-Token": token,
"User-Agent": userAgent,
"internal-request-source": "VSTS"
};

request.get({ url: getReleaseUrl, headers: headers }, (err, res, body) => {
responseHandler(defer, err, res, body, () => {
defer.resolve(JSON.parse(body));
});
})

return defer.promise;
}

function getBranchName(ref: string): string {
const gitRefsHeadsPrefix = 'refs/heads/';
if (ref) {
Expand Down Expand Up @@ -278,7 +306,7 @@ function prepareSymbols(symbolsPaths: string[]): Q.Promise<string> {

utils.createZipFile(zipStream, zipPath).
then(() => {
tl.debug(`---- symbols arechive file: ${zipPath}`)
tl.debug(`---- symbols archive file: ${zipPath}`)
defer.resolve(zipPath);
});
} else {
Expand All @@ -289,7 +317,7 @@ function prepareSymbols(symbolsPaths: string[]): Q.Promise<string> {
return defer.promise;
}

function beginSymbolUpload(apiServer: string, apiVersion: string, appSlug: string, symbol_type: string, token: string, userAgent: string): Q.Promise<SymbolsUploadInfo> {
function beginSymbolUpload(apiServer: string, apiVersion: string, appSlug: string, symbol_type: SymbolType, token: string, userAgent: string, version?: string, build?: string): Q.Promise<SymbolsUploadInfo> {
tl.debug("-- Begin symbols upload")
let defer = Q.defer<SymbolsUploadInfo>();

Expand All @@ -302,7 +330,13 @@ function beginSymbolUpload(apiServer: string, apiVersion: string, appSlug: strin
"internal-request-source": "VSTS"
};

let symbolsUploadBody = { "symbol_type": symbol_type };
const symbolsUploadBody = { "symbol_type": symbol_type };

if (symbol_type === "AndroidProguard") {
symbolsUploadBody["file_name"] = "mapping.txt";
symbolsUploadBody["version"] = version;
symbolsUploadBody["build"] = build;
}

request.post({ url: beginSymbolUploadUrl, headers: headers, json: symbolsUploadBody }, (err, res, body) => {
responseHandler(defer, err, res, body, () => {
Expand Down Expand Up @@ -439,18 +473,17 @@ async function run() {
let appFilePattern: string = tl.getInput('app', true);

/* The task has support for different symbol types but App Center server only support Apple currently, add back these types in the task.json when support is available in App Center.
"AndroidJava": "Android (Java)",
"AndroidNative": "Android (native C/C++)",
"Windows": "Windows 8.1",
"UWP": "Universal Windows Platform (UWP)"
*/
let symbolsType: string = tl.getInput('symbolsType', false);
const symbolsType = tl.getInput('symbolsType', false) as SymbolType;
let symbolVariableName = null;
switch (symbolsType) {
case "Apple":
symbolVariableName = "dsymPath";
break;
case "AndroidJava":
case "AndroidProguard":
symbolVariableName = "mappingTxtPath";
break;
case "UWP":
Expand Down Expand Up @@ -526,7 +559,14 @@ async function run() {

if (symbolsFile) {
// Begin preparing upload symbols
let symbolsUploadInfo = await beginSymbolUpload(effectiveApiServer, effectiveApiVersion, appSlug, symbolsType, apiToken, userAgent);
let version: string;
let build: string;
if (symbolsType === "AndroidProguard") {
const release = await getRelease(effectiveApiServer, effectiveApiVersion, appSlug, releaseId, apiToken, userAgent);
version = release.short_version;
build = release.version;
}
const symbolsUploadInfo = await beginSymbolUpload(effectiveApiServer, effectiveApiVersion, appSlug, symbolsType, apiToken, userAgent, version, build);

// upload symbols
await uploadSymbols(symbolsUploadInfo.upload_url, symbolsFile);
Expand Down
14 changes: 8 additions & 6 deletions Tasks/AppCenterDistributeV3/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
"author": "Microsoft Corporation",
"version": {
"Major": 3,
"Minor": 152,
"Patch": 1
"Minor": 153,
matthiaswenz marked this conversation as resolved.
Show resolved Hide resolved
"Patch": 0
},
"releaseNotes": "Added support for multiple destinations.",
"releaseNotes": "Added support for forwarding Android mapping to App Center Diagnostics.",
"groups": [
{
"name": "symbols",
Expand Down Expand Up @@ -63,7 +63,8 @@
"defaultValue": "Apple",
"groupName": "symbols",
"options": {
"Apple": "Apple"
"Apple": "Apple",
"AndroidProguard": "Android"
matthiaswenz marked this conversation as resolved.
Show resolved Hide resolved
}
},
{
Expand Down Expand Up @@ -110,7 +111,7 @@
"groupName": "symbols",
"required": false,
"helpMarkDown": "Relative path from the repo root to Android's mapping.txt file.",
"visibleRule": "symbolsType = AndroidJava"
"visibleRule": "symbolsType = AndroidProguard"
},
{
"name": "packParentFolder",
Expand All @@ -121,7 +122,8 @@
"label": "Include all items in parent folder",
"groupName": "symbols",
"required": false,
"helpMarkDown": "Upload the selected symbols file or folder and all other items inside the same parent folder. This is required for React Native apps."
"helpMarkDown": "Upload the selected symbols file or folder and all other items inside the same parent folder. This is required for React Native apps.",
"visibleRule": "symbolsType = Apple"
},
{
"name": "releaseNotesSelection",
Expand Down
12 changes: 7 additions & 5 deletions Tasks/AppCenterDistributeV3/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"author": "Microsoft Corporation",
"version": {
"Major": 3,
"Minor": 152,
"Patch": 1
"Minor": 153,
matthiaswenz marked this conversation as resolved.
Show resolved Hide resolved
"Patch": 0
},
"releaseNotes": "ms-resource:loc.releaseNotes",
"groups": [
Expand Down Expand Up @@ -63,7 +63,8 @@
"defaultValue": "Apple",
"groupName": "symbols",
"options": {
"Apple": "Apple"
"Apple": "Apple",
"AndroidProguard": "Android"
}
},
{
Expand Down Expand Up @@ -110,7 +111,7 @@
"groupName": "symbols",
"required": false,
"helpMarkDown": "ms-resource:loc.input.help.mappingTxtPath",
"visibleRule": "symbolsType = AndroidJava"
"visibleRule": "symbolsType = AndroidProguard"
matthiaswenz marked this conversation as resolved.
Show resolved Hide resolved
},
{
"name": "packParentFolder",
Expand All @@ -121,7 +122,8 @@
"label": "ms-resource:loc.input.label.packParentFolder",
"groupName": "symbols",
"required": false,
"helpMarkDown": "ms-resource:loc.input.help.packParentFolder"
"helpMarkDown": "ms-resource:loc.input.help.packParentFolder",
"visibleRule": "symbolsType = Apple"
},
{
"name": "releaseNotesSelection",
Expand Down