зеркало из https://github.com/mozilla/treeherder.git
Bug 1397436 - Improve actions.json actions editor
This commit is contained in:
Родитель
f0e92d27d7
Коммит
5c0c7eb5e2
|
@ -10,9 +10,11 @@
|
|||
"node": "8.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^5.2.2",
|
||||
"angular": "1.5.11",
|
||||
"angular-cookies": "1.5.11",
|
||||
"angular-local-storage": "0.5.2",
|
||||
"angular-marked": "^1.2.2",
|
||||
"angular-resource": "1.5.11",
|
||||
"angular-route": "1.5.11",
|
||||
"angular-sanitize": "1.5.11",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"json-e": "2.2.1",
|
||||
"json-schema-defaults": "0.3.0",
|
||||
"lodash": "4.17.4",
|
||||
"marked": "^0.3.6",
|
||||
"metrics-graphics": "2.11.0",
|
||||
"mousetrap": "1.6.1",
|
||||
"neutrino": "4.3.1",
|
||||
|
|
|
@ -22,6 +22,7 @@ require('mousetrap');
|
|||
require('js-yaml');
|
||||
require('ngreact');
|
||||
require('angular-ui-bootstrap');
|
||||
require('angular-marked');
|
||||
require('../../../ui/vendor/resizer.js');
|
||||
|
||||
const jsContext = require.context('../../../ui/js', true, /^\.\/.*\.jsx?$/);
|
||||
|
|
|
@ -352,3 +352,13 @@ kbd {
|
|||
height: 1px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fonts
|
||||
*
|
||||
* Styles for modifying basic font styles
|
||||
*/
|
||||
|
||||
.pre {
|
||||
font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ require('bootstrap/dist/js/bootstrap');
|
|||
require('angular-ui-bootstrap');
|
||||
require('mousetrap');
|
||||
require('js-yaml');
|
||||
require('angular-marked');
|
||||
require('react-dom');
|
||||
require('ngreact');
|
||||
require('jquery.scrollto');
|
||||
|
|
|
@ -90,13 +90,13 @@ treeherderApp.controller('ResultSetCtrl', [
|
|||
'thUrl', 'thServiceDomain', 'thResultStatusInfo', 'thDateFormat',
|
||||
'ThResultSetStore', 'thEvents', 'thJobFilters', 'thNotify',
|
||||
'thBuildApi', 'thPinboard', 'ThResultSetModel', 'dateFilter',
|
||||
'ThModelErrors', 'ThJobModel', 'ThTaskclusterErrors',
|
||||
'ThModelErrors', 'ThJobModel', 'ThTaskclusterErrors', '$uibModal',
|
||||
function ResultSetCtrl(
|
||||
$scope, $rootScope, $http, ThLog, $location,
|
||||
thUrl, thServiceDomain, thResultStatusInfo, thDateFormat,
|
||||
ThResultSetStore, thEvents, thJobFilters, thNotify,
|
||||
thBuildApi, thPinboard, ThResultSetModel, dateFilter, ThModelErrors,
|
||||
ThJobModel, ThTaskclusterErrors) {
|
||||
ThJobModel, ThTaskclusterErrors, $uibModal) {
|
||||
|
||||
$scope.getCountClass = function (resultStatus) {
|
||||
return thResultStatusInfo(resultStatus).btnClass;
|
||||
|
@ -190,6 +190,23 @@ treeherderApp.controller('ResultSetCtrl', [
|
|||
});
|
||||
};
|
||||
|
||||
$scope.customPushAction = function () {
|
||||
$uibModal.open({
|
||||
templateUrl: 'partials/main/tcjobactions.html',
|
||||
controller: 'TCJobActionsCtrl',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
job: () => null,
|
||||
repoName: function () {
|
||||
return $scope.repoName;
|
||||
},
|
||||
resultsetId: function () {
|
||||
return $scope.resultset.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.triggerMissingJobs = function (revision) {
|
||||
if (!window.confirm('This will trigger all missing jobs for revision ' + revision + '!\n\nClick "OK" if you want to proceed.')) {
|
||||
return;
|
||||
|
|
|
@ -4,13 +4,15 @@ treeherder.controller('TCJobActionsCtrl', [
|
|||
'$scope', '$http', '$uibModalInstance', 'ThResultSetStore',
|
||||
'ThJobDetailModel', 'thTaskcluster', 'ThTaskclusterErrors',
|
||||
'thNotify', 'job', 'repoName', 'resultsetId', 'tcactions',
|
||||
'jsyaml', 'Ajv', 'jsonSchemaDefaults',
|
||||
function ($scope, $http, $uibModalInstance, ThResultSetStore,
|
||||
ThJobDetailModel, thTaskcluster, ThTaskclusterErrors, thNotify,
|
||||
job, repoName, resultsetId, tcactions) {
|
||||
let jsonSchemaDefaults = require('json-schema-defaults');
|
||||
job, repoName, resultsetId, tcactions, jsyaml, Ajv, jsonSchemaDefaults) {
|
||||
const ajv = new Ajv({ format: 'full', verbose: true, allErrors: true });
|
||||
let decisionTaskId;
|
||||
let originalTaskId;
|
||||
let originalTask;
|
||||
let validate;
|
||||
$scope.input = {};
|
||||
|
||||
$scope.cancel = function () {
|
||||
|
@ -19,9 +21,13 @@ treeherder.controller('TCJobActionsCtrl', [
|
|||
|
||||
$scope.updateSelectedAction = function () {
|
||||
if ($scope.input.selectedAction.schema) {
|
||||
$scope.input.jsonPayload = JSON.stringify(jsonSchemaDefaults($scope.input.selectedAction.schema), null, 4);
|
||||
$scope.schema = jsyaml.safeDump($scope.input.selectedAction.schema);
|
||||
$scope.input.payload = jsyaml.safeDump(jsonSchemaDefaults($scope.input.selectedAction.schema));
|
||||
validate = ajv.compile($scope.input.selectedAction.schema);
|
||||
} else {
|
||||
$scope.input.jsonPayload = undefined;
|
||||
$scope.input.payload = undefined;
|
||||
$scope.schema = undefined;
|
||||
validate = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -30,6 +36,23 @@ treeherder.controller('TCJobActionsCtrl', [
|
|||
|
||||
let tc = thTaskcluster.client();
|
||||
|
||||
let input = null;
|
||||
if (validate && $scope.input.payload) {
|
||||
try {
|
||||
input = jsyaml.safeLoad($scope.input.payload);
|
||||
} catch (e) {
|
||||
$scope.triggering = false;
|
||||
thNotify.send(`YAML Error: ${e.message}`, 'danger');
|
||||
return;
|
||||
}
|
||||
const valid = validate(input);
|
||||
if (!valid) {
|
||||
$scope.triggering = false;
|
||||
thNotify.send(ajv.errorsText(validate.errors), 'danger');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let actionTaskId = tc.slugid();
|
||||
tcactions.submit({
|
||||
action: $scope.input.selectedAction,
|
||||
|
@ -37,11 +60,23 @@ treeherder.controller('TCJobActionsCtrl', [
|
|||
decisionTaskId,
|
||||
taskId: originalTaskId,
|
||||
task: originalTask,
|
||||
input: $scope.input.jsonPayload ? JSON.parse($scope.input.jsonPayload) : undefined,
|
||||
input,
|
||||
staticActionVariables: $scope.staticActionVariables,
|
||||
}).then(function () {
|
||||
$scope.$apply(thNotify.send("Custom action request sent successfully", 'success'));
|
||||
$scope.triggering = false;
|
||||
let message = 'Custom action request sent successfully:';
|
||||
let url = `https://tools.taskcluster.net/tasks/${actionTaskId}`;
|
||||
|
||||
// For the time being, we are redirecting specific actions to
|
||||
// specific urls that are different than usual. At this time, we are
|
||||
// only directing loaner tasks to the loaner UI in the tools site.
|
||||
// It is possible that we may make this a part of the spec later.
|
||||
const loaners = ['docker-worker-linux-loaner', 'generic-worker-windows-loaner'];
|
||||
if (_.includes(loaners, $scope.input.selectedAction.name)) {
|
||||
message = 'Visit Taskcluster Tools site to access loaner:';
|
||||
url = `${url}/connect`;
|
||||
}
|
||||
$scope.$apply(thNotify.send(message, 'success', true, 'Open in Taskcluster', url));
|
||||
$uibModalInstance.close('request sent');
|
||||
}, function (e) {
|
||||
$scope.$apply(thNotify.send(ThTaskclusterErrors.format(e), 'danger', true));
|
||||
|
|
|
@ -203,6 +203,16 @@ treeherder.factory('jsyaml', [
|
|||
return require('js-yaml');
|
||||
}]);
|
||||
|
||||
treeherder.factory('Ajv', [
|
||||
function () {
|
||||
return require('ajv');
|
||||
}]);
|
||||
|
||||
treeherder.factory('jsonSchemaDefaults', [
|
||||
function () {
|
||||
return require('json-schema-defaults');
|
||||
}]);
|
||||
|
||||
treeherder.factory('thExtendProperties', [
|
||||
/* Version of _.extend that works with property descriptors */
|
||||
function () {
|
||||
|
|
|
@ -17,6 +17,7 @@ treeherder.factory('tcactions', [
|
|||
taskId,
|
||||
task,
|
||||
input,
|
||||
ownTaskId: actionTaskId,
|
||||
}, staticActionVariables));
|
||||
|
||||
return queue.task(decisionTaskId).then((decisionTask) => {
|
||||
|
@ -39,48 +40,50 @@ treeherder.factory('tcactions', [
|
|||
'public/actions.json'
|
||||
);
|
||||
|
||||
return $http.get(actionsUrl).then((response) => {
|
||||
let originalTaskId;
|
||||
let originalTaskPromise = $q.resolve(null);
|
||||
if (job) {
|
||||
if (job.taskcluster_metadata) {
|
||||
originalTaskId = job.taskcluster_metadata.task_id;
|
||||
} else {
|
||||
// This is a bbb job in this case. We'll try our best.
|
||||
const match = job.reason.match(/Created by BBB for task (.{22})/);
|
||||
if (match) {
|
||||
originalTaskId = match[1];
|
||||
}
|
||||
}
|
||||
originalTaskPromise = $http.get('https://queue.taskcluster.net/v1/task/' + originalTaskId).then(response => response.data);
|
||||
}
|
||||
|
||||
return $q.all([
|
||||
$http.get(actionsUrl),
|
||||
originalTaskPromise,
|
||||
]).then(([response, originalTask]) => {
|
||||
if (!response.data) {
|
||||
// This is a push with no actions.json so we should
|
||||
// allow an implementer to fall back to actions.yaml
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response.data.version !== 1) {
|
||||
thNotify.send("Wrong version of actions.json, can't continue", "danger", true);
|
||||
return;
|
||||
}
|
||||
|
||||
let originalTaskPromise = $q.resolve(null);
|
||||
let originalTaskId;
|
||||
if (job) {
|
||||
if (job.taskcluster_metadata) {
|
||||
originalTaskId = job.taskcluster_metadata.task_id;
|
||||
} else {
|
||||
// This is a bbb job in this case. We'll try our best.
|
||||
const match = job.reason.match(/Created by BBB for task (.{22})/);
|
||||
if (match) {
|
||||
originalTaskId = match[1];
|
||||
}
|
||||
}
|
||||
originalTaskPromise = $http.get('https://queue.taskcluster.net/v1/task/' + originalTaskId).then(
|
||||
function (response) {
|
||||
return response.data;
|
||||
});
|
||||
}
|
||||
return originalTaskPromise.then(originalTask => ({
|
||||
// The filter in the value of the actions key is an implementation
|
||||
// of the specification for action context in
|
||||
// https://docs.taskcluster.net/manual/using/actions/spec#action-context
|
||||
// It decides if the specific action is applicable for this task.
|
||||
return {
|
||||
originalTask,
|
||||
originalTaskId,
|
||||
staticActionVariables: response.data.variables,
|
||||
actions: response.data.actions.filter(function (action) {
|
||||
return action.kind === 'task' && (!originalTask || (
|
||||
!action.context.length || _.some((action.context).map(function (actionContext) {
|
||||
return !Object.keys(actionContext).length || _.every(_.map(actionContext, function (v, k) {
|
||||
return (originalTask.tags[k] === v);
|
||||
}));
|
||||
}))));
|
||||
}),
|
||||
}));
|
||||
actions: response.data.actions.filter(action => action.kind === 'task' &&
|
||||
(!action.context.length && !originalTask) ||
|
||||
originalTask && action.context.some(ctx => Object.keys(ctx).every(tag => (
|
||||
originalTask.tags[tag] && originalTask.tags[tag] === ctx[tag]
|
||||
)))
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
var treeherderApp = angular.module('treeherder.app',
|
||||
['treeherder', 'ui.bootstrap', 'ngRoute',
|
||||
'mc.resizer', 'angular-toArrayFilter', 'react']);
|
||||
'mc.resizer', 'angular-toArrayFilter', 'react',
|
||||
'hc.marked']);
|
||||
|
||||
treeherderApp.config(['$compileProvider', '$routeProvider', '$httpProvider',
|
||||
'$logProvider', '$resourceProvider', 'localStorageServiceProvider',
|
||||
|
|
|
@ -10,13 +10,18 @@
|
|||
<label>Action</label>
|
||||
<select aria-describedby="selectedActionHelp" class="form-control" ng-model="input.selectedAction" ng-options="action.title for action in actions" ng-change="updateSelectedAction()">
|
||||
</select>
|
||||
<p id="selectedActionHelp" class="help-block">{{input.selectedAction.description}}</p>
|
||||
<p id="selectedActionHelp" class="help-block" marked="input.selectedAction.description"></p>
|
||||
</div>
|
||||
<div class="form-group" ng-if="input.selectedAction.schema">
|
||||
<label>JSON Payload</label>
|
||||
<textarea ng-model="input.jsonPayload" class="form-control" rows="5" spellcheck=false/>
|
||||
<div class="row">
|
||||
<div class="col-s-12 col-md-6 form-group" ng-if="input.selectedAction.schema">
|
||||
<label>Payload</label>
|
||||
<textarea ng-model="input.payload" class="form-control pre" rows="10" spellcheck=false/>
|
||||
</div>
|
||||
<div class="col-s-12 col-md-6 form-group" ng-if="input.selectedAction.schema">
|
||||
<label>Schema</label>
|
||||
<textarea class="form-control pre" rows="10" readonly>{{schema}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
{{input.selectedAction.jsonPayload}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-if="user.loggedin" class="btn btn-primary" ng-click="triggerAction()" ng-attr-title="{{user.loggedin ? 'Trigger this action' : 'Not logged in'}}" ng-disabled="triggering">
|
||||
|
|
|
@ -35,6 +35,10 @@
|
|||
<li><a target="_blank" data-ignore-job-clear-on-click
|
||||
href="https://bugherder.mozilla.org/?cset={{::resultset.revision}}&tree={{::repoName}}"
|
||||
title="Use Bugherder to mark the bugs in this push">Mark with Bugherder</a></li>
|
||||
<li><a target="_blank" data-ignore-job-clear-on-click
|
||||
href=""
|
||||
ng-click="customPushAction()"
|
||||
title="View/Edit/Submit Action tasks for this push">Custom Push Action...</a></li>
|
||||
<li><a target="_blank" title="Prototype of a Treeherder UI centered on tests, rather than jobs"
|
||||
href="{{testBasedUIHost}}?repo={{repoName}}&revision={{resultset.revision}}"><strong>Experimental:</strong> Test-Centric UI</a></li>
|
||||
<li><a target="_blank"
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -56,7 +56,7 @@ ajv@^4.7.0, ajv@^4.9.1:
|
|||
co "^4.6.0"
|
||||
json-stable-stringify "^1.0.1"
|
||||
|
||||
ajv@^5.0.0:
|
||||
ajv@^5.0.0, ajv@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39"
|
||||
dependencies:
|
||||
|
@ -98,6 +98,12 @@ angular-local-storage@0.5.2:
|
|||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/angular-local-storage/-/angular-local-storage-0.5.2.tgz#7079beb0aa5ca91386d223125efefd13ca0ecd0c"
|
||||
|
||||
angular-marked@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/angular-marked/-/angular-marked-1.2.2.tgz#031f81015dad5fb01adf8d48922c52feb3588de5"
|
||||
dependencies:
|
||||
marked "^0.3.3"
|
||||
|
||||
angular-mocks@1.5.11:
|
||||
version "1.5.11"
|
||||
resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.5.11.tgz#a0e1dd0ea55fd77ee7a757d75536c5e964c86f81"
|
||||
|
@ -4316,6 +4322,10 @@ map-obj@^1.0.0, map-obj@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
|
||||
|
||||
marked@^0.3.3, marked@^0.3.6:
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7"
|
||||
|
||||
math-expression-evaluator@^1.2.14:
|
||||
version "1.2.17"
|
||||
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
||||
|
|
Загрузка…
Ссылка в новой задаче