Create a (subtask) link to an existing issue in YouTrack - youtrack

I'm writing an Action to create default tasks for a user story. This is how far I got:
var workflow = require('#jetbrains/youtrack-scripting-api/workflow');
var entities = require('#jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.action({
title: 'Create default subtasks',
command: 'tt-create-subtasks',
guard: function(ctx) {
// Condition that must be met to enable the custom command:
return ctx.issue.fields.Type.name == "User Story";
},
action: function(ctx) {
var issue = ctx.issue;
var newIssue = ctx.issue.copy(issue.project);
newIssue.summary = 'API spoofing';
newIssue.fields.Type = ctx.Type.Task;
// var link = newIssue.links.add(issue); ????
workflow.message('Default task created under issue ' && issue.description);
},
requirements: {
Type: {
type: entities.EnumField.fieldType,
Task: {}
},
}
});
How do I create a new link the makes newIssue a subtask of issue?
I've look through what code completions offers, the documentation for the Issue property, SO questions, the workflow code that already exists in YouTrack, but I'm stuck...

You should specify the issue link too. The following code should work (newIssue will be a subtask of issue):
newIssue.links['subtask of'].add(issue);
You can find the example here: https://blog.jetbrains.com/youtrack/2017/12/make-it-workflow-part-4-generating-new-issues/

Related

Automatically assign assignee when changing status of ticket

I created an "Agile-Board" in youtrack and I want every ticket that is moved to the column (which is mapped to the field Status) "In Produktivsetzung" to be automatically assigned to my user.
Like this:
How can this be done?
One can set it up with a custom workflow script as follows
var entities = require('#jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.onChange({
title: 'Set logged-in user as an assignee when they move it to In Produktivsetzung state',
guard: function(ctx) {
var issue = ctx.issue;
return issue.isReported &&
issue.fields.Assignee === null &&
issue.fields.becomes(ctx.State, ctx.State.InProgress) &&
!issue.fields.isChanged("project");
},
action: function(ctx) {
var isCurrentUserAssignee = false;
ctx.Assignee.values.forEach(function(it) {
if (it.login == ctx.currentUser.login) {
isCurrentUserAssignee = true;
}
});
if (isCurrentUserAssignee) {
ctx.issue.Assignee = ctx.currentUser;
}
},
requirements: {
Assignee: {
type: entities.User.fieldType
},
State: {
type: entities.State.fieldType,
InProgress: {
name: 'In Produktivsetzung'
}
}
}
});
I want to set assignee on every state change. After a couple hours trial & error (the documentation is really not that good) I had success:
var entities = require('#jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.onChange({
title: 'Assign issue to current user when state changes',
guard: function(ctx) {
return ctx.issue.fields.isChanged(ctx.State);
},
action: (ctx) => {
ctx.issue.fields.Assignee = ctx.currentUser;
},
requirements: {
Assignee: {
type: entities.User.fieldType
},
State: {
type: entities.State.fieldType
}
}
});
I don't really understand why I have to use a "guard function" - I could just use a conditional statement in the action and the whole "requirements" section doesn't make any sense to me but if it is necessary... I don't care. Finally works as expected... I hope that it works some years longer than the "legacy scripts" - I don't want to touch it again. 🙂
Based on the answer this is what I'm using now, I created multiple modules where I just had to change the two variables at the top of my code:
var entities = require('#jetbrains/youtrack-scripting-api/entities');
var assigneeLogin = '<some.login>';
var stateName = '<Some Statename, see possible values in console.log(ctx.State)>';
exports.rule = entities.Issue.onChange({
title: 'Set ' + assigneeLogin + ' as the assignee when ticket is moved to "'+ stateName + '"',
guard: function(ctx) {
var issue = ctx.issue;
return issue.fields.becomes(ctx.State, ctx.State.InProgress);
},
action: function(ctx) {
ctx.Assignee.values.forEach(function(it) {
if (it.login === assigneeLogin) {
ctx.issue.Assignee = it;
}
});
},
requirements: {
Assignee: {
type: entities.User.fieldType
},
State: {
type: entities.State.fieldType,
InProgress: {
name: stateName
}
}
}
});

Youtrack check if user has permissions

I'm trying to create a Youtrack workflow where only a specific role is allowed to edit the Kanban state to ready-to-pull when the current issue is in backlog. I wasn't quite able to get it correctly working, keeps throwing exceptions but I'm unable to read the full exception.
I tried to create the current workflow code:
var entities = require('#jetbrains/youtrack-scripting-api/entities');
var workflow = require('#jetbrains/youtrack-scripting-api/workflow');
exports.rule = entities.Issue.onChange({
title: workflow.i18n('Block change in Kanban stage for issues that are in backlog'),
guard: function(ctx) {
return ctx.issue.isReported && ctx.issue.fields.isChanged(ctx.KanbanState);
},
action: function(ctx) {
var issue = ctx.issue;
if (!ctx.user.hasRole('project-admin', ctx.project)) {
workflow.message('U dont have the correct permissions to do this');
ctx.KanbanState = ctx.KanbanState.Blocked;
}
},
requirements: {
Stage: {
type: entities.State.fieldType
},
KanbanState: {
name: 'Kanban State',
type: entities.EnumField.fieldType,
ReadyToPull: {
name: 'Ready to pull'
},
Blocked: {}
}
}
});
Most of this is a copy from the Kanban change workflow that blocks moving the issue to a new stage when the kanban state isn't set to "Ready-to-Pull". I basically want the exact same, but I want to only allow project admins to change the kanban state to "ready-to-pull" when the current stage is "Backlog". The current code only checks the permissions at the moment, but I started getting stuck there already.
To implement this task, I suggest you use the workflow.check method, for example:
workflow.check(ctx.user.hasRole('project-admin', ctx.project), 'U dont have the correct permissions to do this');
I hope this helps.
Seeing as in our current situation we only need to disable a single person to not be able to change the Kanban states when new tickets are set, we have the following solution:
exports.rule = entities.Issue.onChange({
title: workflow.i18n('Block change in Kanban stage for issues in backlog stage'),
guard: function(ctx) {
var issue = ctx.issue;
return issue.fields.isChanged(ctx.KanbanState);//Runs when Kanban state changes
},
action: function(ctx) {
var issue = ctx.issue;
//Check if user has changed the kanban state to ready to pull while the current stage is backlog.
if (issue.fields.Stage.name == 'Backlog') {
//Current stage is backlog
if (issue.fields.KanbanState.name === ctx.KanbanState.ReadyToPull.name) {
//Kanban state was changed to ready to pull;
var target = '<useremail>';
workflow.check(ctx.currentUser.email == target,
workflow.i18n('No permissions to change the Kanban state.'));
issue.fields.KanbanState = ctx.KanbanState.Blocked;
}
}
},
requirements: {
Stage: {
type: entities.State.fieldType,
Backlog: {},
Development: {}
},
KanbanState: {
name: 'Kanban State',
type: entities.EnumField.fieldType,
ReadyToPull: {
name: 'Ready to pull'
},
Blocked: {}
}
}
});
However I checked the answer of #Oleg Larshun and his answer worked as well. replacing the email section with:
workflow.check(ctx.user.hasRole('project-admin', ctx.project), 'U dont have the correct permissions to do this');

How to create new TimeEntryValue in Rally

I'm fairly new to the Rally API and JS, and Stackoverflow for that matter. I have been using Stackoverflow to answer all of my questions so far, but I can't seem to find anything about adding new TimeEntryValues.
I am building an app that allows to add new TimeEntryValues. I can add or load TimeEntryItems but for TimeEntryValues, I ever only seem to post the Hours field when looking at the trace in the browser.
Here is a simplified code that exhibits the same problem.
launch: function(){
//For this example, pre-define Time Entry Reference, Date, and Hour value
var myTimeEntryItem = "/timeentryitem/1234";
var myDateValue = "2016-05-20T00:00:00.000Z";
var myHours = 2.5;
//Check if Time Entry Value (TEV) already exists
var TEVstore = Ext.create('Rally.data.WsapiDataStore', {
model: 'TimeEntryValue',
fetch: ['ObjectID','TimeEntryItem','Hours','DateVal'],
filters: [{
property: 'TimeEntryItem',
operator: '=',
value: myTimeEntryItem
},
{
property: 'DateVal',
operator: '=',
value: myDateValue
}],
autoLoad: true,
listeners: {
load: function(TEVstore, tevrecords, success) {
//No record found - TEV does not exist
if (tevrecords.length === 0) {
console.log("Creating new TEV record");
Rally.data.ModelFactory.getModel({
type: 'TimeEntryValue',
success: function(tevModel) {
var newTEV = Ext.create(tevModel, {
DateVal: myDateValue,
Hours: myHours,
TimeEntryItem: myTimeEntryItem
});
newTEV.save({
callback: function(result, operation) {
if(operation.wasSuccessful()) {
console.log("Succesful Save");
//Do something here
}
}
});
}
});
} else {
console.log("TEV Record exists.");
//Do something useful here
}
}
},
scope: this
});
}
Any hints what I am doing wrong are greatly appreciated.
Thanks
This is actually a longstanding defect in App SDK caused by a mismatch in the WSAPI attribute metadata and the client side models used for persisting data to the server.
Basically what's happening is the DateVal and TimeEntryItem fields are marked required and readonly, which doesn't make sense. Really, they need to be writable on create and then readonly after that.
So all you need to do in your app is before you try to save your new TimeEntryValue just mark the DateVal and TimeEntryItem fields as persistable and you should be good to go.
//workaround
tevModel.getField('DateVal').persist = true;
tevModel.getField('TimeEntryItem').persist = true;
//proceed as usual
var newTEV = Ext.create(tevModel, {
DateVal: myDateValue,
Hours: myHours,
TimeEntryItem: myTimeEntryItem
});
// ...

Issue to display data from a module into another

I'm a beginner to MEAN.js and have an issue with displaying some datas from one module into another.
I've successfully created a new CRUD module "Jobs" which contains a list of jobs for my company.
I would like to associate a job from this list to a user (which is another crud module contained in the sample code)
When i'm editing a user on the admin panel i would like to be able to choose a job from a list and set it to the user in the database.
Here is what i've from now on :
user.server.model.js
var UserSchema = new Schema({
//username, firstname etc.
job: {
type: Schema.ObjectId,
ref: 'job'
}
});
job.server.model.js
var JobSchema = new Schema({
name:{
type: String
}
});
My html where i try to link them both :
edit-user.client.view.html
//Set a job for a user
<select ng-options="job.name for job in vm.jobs"></select> // <--- this doesn't work
Thank you for your help :) !
edit :
user.client.controller.js
angular.module('users.admin').controller('UserController', ['$scope', '$state', 'Authentication', 'userResolve',function ($scope, $state, Authentication, userResolve) {
$scope.authentication = Authentication;
$scope.user = userResolve; //...
job.client.controller.js
angular
.module('jobs')
.controller('JobsController', JobsController);
JobsController.$inject = ['$scope', '$state', 'Authentication', 'jobResolve'];
function RolesController ($scope, $state, Authentication, job) {
var vm = this;
vm.authentication = Authentication;
vm.job = job; //...
list-job.client.controller.js
angular
.module('jobs')
.controller('JobsListController', JobsListController);
JobsListController.$inject = ['JobsService'];
function JobsListController(JobsService) {
var vm = this;
vm.roles = JobsService.query();
}
Resolved, injecting my jobs service into my user controller did it:
angular.module('users.admin').controller('UserController', ['$scope', '$state', 'Authentication', 'JobsService', 'userResolve',
function ($scope, $state, Authentication, JobsService, userResolve) {
$scope.authentication = Authentication;
$scope.user = userResolve;
$scope.jobs = JobsService.query();
This have to be done in your server side endpoint.
Suppose you have a route like that:
router.post('/users/', function(req, res, next) {
var user = new User(req.body);
user.job = req.job;
user.save(function(err, user) {
if(err) { return next(err); }
res.json(user);
});
});

find subdocument without start from parent

Pretty new to mongoose. Can I findOne off of a subdocument model?
I have a subdoc called deliverables which is a child of projects
What I'd like to do is FIND on my deliverables model so I don't have to find on the project as
{project.child.child.child.deliverables._id: req.id}
Is that possible or do I have to start from the project model each time? Below is a sample setup I'm using along with my example findOne.
'use strict';
//////////////model/////////////////
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var deliverablesSchema = new Schema({
title:{
type:String,
}
})
var ProjectSchema = new Schema({
name: {
type: String,
},
deliverables: [deliverablesSchema],
});
mongoose.model('Deliverable', deliverablesSchema);
mongoose.model('Project', ProjectSchema);
//////////////controller/////////////////
var mongoose = require('mongoose'),
Project = mongoose.model('Project'),
Deliverable = mongoose.model('Deliverable'),
_ = require('lodash');
exports.findDeliverable = function(req, res) {
Deliverable.findOne({'_id': req.params.deliverableId}).exec(function(err, deliverable) {
if(deliverable){
//return
}
});
};
You can find subdocuments within a document only if you have declared their schema http://mongoosejs.com/docs/subdocs.html
Here is an example taken from here:
Project.findOne({
'_id': myid
}).exec(function (err, p) {
if (p) {
//return
var deriv = p.deliverables.filter(function (oneP) {
return oneP._id === 'myderivableid';
}).pop();
}
});
If your subdocuments are just nested objects in an array you may use Lodash to retrieve that data using _ .where or _.find
Kept digging and found this:
https://stackoverflow.com/a/28952991/2453687
You still have to pull the master document first but it's easy to drill down to the particular object you're looking for and make a quick update, then just update the whole master document in one shot.
Dead simple. Works like a charm.