Youtrack check if user has permissions - youtrack

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');

Related

Create a (subtask) link to an existing issue in 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/

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
}
}
}
});

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
});
// ...

Alfresco Share Aikau PathTree to show documents not just folders

I am working on an Aikau Share Page where I a side bar that is using the Alfresco Share document library tree picker. The picker allows me to publish the nodeRef to another widget which will display information. I would like to use the tree view but i'm having trouble showing the documents and it is only showing the containers/folders. Anyone have any idea on what I need to do in order to solve this?
Here is the Aikua code i am using:
{
align: "sidebar",
name: "alfresco/layout/Twister",
config: {
label: "twister.library.label",
additionalCssClasses: "no-borders",
widgets: [
{
name: "alfresco/navigation/PathTree",
config: {
showRoot: true,
rootLabel: "Repository",
rootNode: "/app:company_home",
publishTopic: "ALF_ITEM_SELECTED"
}
}
]
}
}
I am wondering if I need to write an extension to the CoreXhr or what the steps would be in order to make this work.
Any help would be appreciated
I was able to figure this out. The problem comes from the repository script in the alfresco explorer side "treenode.get.js". The solution was to do the following
Create a new webscript in alfresco explorer and copy treenode.get.js code into the new webscript. I ended up calling mine customtreenode.get.js.
Remove the logic check for IsContainer in the newly created webscript
Create new Aikau file that extends PathTree. Here is the code below
define(["dojo/_base/declare",
"alfresco/navigation/PathTree",
"alfresco/documentlibrary/_AlfDocumentListTopicMixin",
"service/constants/Default",
"dojo/_base/lang",
"dojo/_base/array",
"dojo/dom-class",
"dojo/query",
"dojo/NodeList-dom"],
function(declare, PathTree, _AlfDocumentListTopicMixin, AlfConstants, lang, array, domClass, query) {
return declare([PathTree, _AlfDocumentListTopicMixin], {
useHash: true,
getTargetUrl: function alfresco_navigation_Tree__getTargetUrl() {
var url = null;
if (this.siteId && this.containerId)
{
url = AlfConstants.PROXY_URI + "slingshot/doclib/treenodeCustom/site/" + this.siteId + "/documentlibrary";
}
else if (this.rootNode)
{
url = AlfConstants.PROXY_URI + "slingshot/doclib/treenodeCustom/node/alfresco/company/home";
}
else if (!this.childRequestPublishTopic)
{
this.alfLog("error", "Cannot create a tree without 'siteId' and 'containerId' or 'rootNode' attributes", this);
}
return url;
}
});
});
Change your code to use the new CustomPathTree
{
align: "sidebar",
name: "alfresco/layout/Twister",
config: {
label: "twister.library.label",
additionalCssClasses: "no-borders",
widgets: [
{
name: "CustomWidgets/widgets/CustomTreeNode",
config: {
showRoot: true,
rootLabel: "Repository",
rootNode: "/app:company_home",
publishTopic: "ALF_ITEM_SELECTED"
}
}
]
}
}
It works after that. I still need to change the icons from folders to documents.

How can I dynamically change the visibility of a Durandal Route?

I have my Durandal routes configured as below.
var routes = [
....... More Routes Here.....
{
url: 'login',
moduleId: 'viewmodels/login',
name: 'Log In',
visible: true,
caption: 'Log In'
}, {
url: 'logout',
moduleId: 'viewmodels/logout',
name: 'Log Out',
visible: false,
caption: 'Log Out'
}, {
url: 'register',
moduleId: 'viewmodels/register',
name: 'Register',
visible: false,
caption: 'Register'
}];
And everything is working as expected. I would like to be able to activate the Logout Route in my navigation when I log in and my log in button to become invisible. I have tried the following code and despite not throwing any errors it does not change the visibility of anything in the interface.
var isLoggedIn = ko.observable(false);
isLoggedIn.subscribe(function (newValue) {
var routes = router.allRoutes();
if (newValue == true) {
for (var k = 0; k < routes.length; k++) {
if (routes[k].url == 'logout') {
routes[k].visible = true;
}
if (routes[k].url == 'login') {
routes[k].visible = false;
}
}
} else {
for (var i = 0; i < routes.length; i++) {
if (routes[i].url == 'logout') {
routes[i].visible = false;
}
if (routes[i].url == 'login') {
routes[i].visible = true;
}
}
}
});
I believe this doesn't work because visible is not an observable, isActive is a computed with no write capability so it does not work either. How can I dynamically change the visibility of my routes in the nav menu?
Here is what I ended up doing.
//ajax call to log user in
.done(function (recievedData) {
if (recievedData == true) {
router.deactivate();
return router.map(config.routesLoggedIn);
} else {
router.deactivate();
return router.map(config.routes);
}
}).done(function() {
router.activate('frames');
return router.navigateTo('#/frames');
});
Essentially created two routing profiles in my config object. One for logged in and one for not. There is one caveat. The router.deactivate() method is a very new method and is not in the NuGet package yet. I copied the code of the new router from the master branch of the GitHub repository for Durandal. There is some discussion on this new function on the Durandal User Group. Ultimately for security reasons I might feed the logged in routes from my server. But for the time being this should work just fine.
Another approach is to make all routes available but use bound expressions to compose either the content or the login page into the container view specified by the route.
Instead of supplying a literal view name, bind the compose parameter to a ternary expression that chooses between the name of the login view and the name of the content view. The controlling expression would be an observable such as app.isAuthenticated() the value of which must be set when the user succeeds in logging in or out.
This approach is robust in the face of deep linking because it does away with the notion of a path through the application. Without explicit redirection logic, it will authenticate the user and then show the requested resource.
It can be extended to more than two possible states using a function instead of a ternary expression. This is handy when different UI must be delivered according to user permission.