Introducing VSTS Quick Reply: a Chrome extension that helps you collaborate quicker in VSTS discussion

Teams often leverage the discussion feature, in particular the ability to @mention team members, in VSTS for work items to collaborate on requirements, tasks and bugs. In large teams it is common to @mention several team members on messages to ensure that the relevant people are involved in the discussion.

VSTS however currently does not have a “Reply all” feature, which means you have to manually @mention each member in the previous message when replying to a discussion post.

I have developed a simple Chrome extension, VSTS Quick Reply, to help with this. This simple extension adds a Reply and Reply All links to each discussion message that have been posted against the current work item. When clicked, these links add all the @mentioned users and/or the original poster of the selected message to your new message entry textbox so that you don’t have to manually type them again.

Currently this extension only supports VSTS. You can install this extension from the Chrome Web Store.

That’s it! I hope this simple extension can give you a small productivity boost when collaborating in VSTS. I would love to hear from you with feedback and suggestions!

Advertisements
Posted in TFS, VSTS | Leave a comment

How to get current user info in VSTS web by JavaScript

I have been exploring extending the VSTS web interface and I needed to get the display name of the current user in JavaScript. It turns out there is a JavaScript variable that is available on all pages, namely __vssPageContext. This variable contains many contextual information. I was able to get what I needed from __vssPageContext.webContext.user:

Posted in TFS, VSTS | Leave a comment

Getting VSTS user ID by display name using REST

I have been exploring the VSTS REST API lately. While there are quite a few services that require the user ID as parameter, it isn’t very obvious how you can obtain that user ID for a given user in the first place.

For my requirement I needed to get the user ID for a given user by their display name. The only way I have found to achieve this (via REST) is to use the Graph service, which is still in preview. The steps are described below.

First, we need to call the List all users service (https://[account].vssps.visualstudio.com/_apis/graph/users). This will return a list of all users. Each user record has the following structure:

{
	"subjectKind": "user",
	"domain": "Windows Live ID",
	"principalName": "[value removed]",
	"mailAddress": "[value removed]",
	"origin": "msa",
	"originId": "[value removed]",
	"displayName": "[value removed]",
	"_links": {
		"self": { "href": "https://[account].vssps.visualstudio.com/_apis/Graph/Users/msa...[value removed]" },
		"memberships": { "href": "https://[account].vssps.visualstudio.com/_apis/Graph/Memberships/msa...[value removed]" },
		"membershipState": { "href": "https://[account].vssps.visualstudio.com/_apis/Graph/MembershipStates/msa...[value removed]" },
		"storageKey": { "href": "https://[account].vssps.visualstudio.com/_apis/Graph/StorageKeys/msa...[value removed]" },
		"avatar": { "href": "https://[account].visualstudio.com/_apis/GraphProfile/MemberAvatars/msa...[value removed]" }
	},
	"url": "https://[account].vssps.visualstudio.com/_apis/Graph/Users/msa...[value removed]",
	"descriptor": "msa...[value removed]"
}

Using the displayName property we can locate the user record that we are after. We then need to invoke the service identified by the _links.storageKey property to retrieve the ID for this user. The URL of this service looks like this (value has been changed):

https://[account].vssps.visualstudio.com/_apis/Graph/StorageKeys/msa.XWI1NTUyXYZtMTzyZi09OTZhXYZmMDktXYkwZjA9NzQxZTK9

The above service returns a structure as follow:

{
	"value": "[GUID]",
	"_links": {
		"self": { "href": "https://[account].vssps.visualstudio.com/_apis/Graph/StorageKeys/msa...[value removed]" },
		"descriptor": { "href": "https://[account].vssps.visualstudio.com/_apis/Graph/Descriptors/[GUID]" 
		}
	}
}

The ID of the user can be found in the value property.

So there you have it…

Unfortunately currently it requires two calls to get the ID of a user given their display name. This might change in the future though as Microsoft’s documentation is indicating that the ability to search for user by UPN or display name is coming soon to the Graph service.

Posted in TFS, VSTS | Leave a comment

How to edit related entity in CRM Portal web form step

In CRM Portal, different web form steps of a web form can operate on different entities. Imagine you have an Enrolment web form that allows students to enrol in courses. The first step of this form might capture student details (Student entity). The second step might capture the enrolment details (Course Enrolment entity). The Student entity would have a lookup to the Course Enrolment entity in this particular example.

Each web form step of the web form must have a Mode specified, which can be Insert, Edit or ReadOnly. If you want to allow users to be able to edit the form (e.g. save and resume), then you would typically need (at a minimum) a web form where all the steps are set to Edit mode.

As the 2nd step operates on a related record, and not the main record that the user clicked on to launch the form however, there is no OOTB way to configure this as an Edit step.

This is because for Edit mode, we need to provide the step with the source record. OOTB however, the available options are:

  1. Query String
  2. Current Portal User
  3. Result From Previous Step
  4. Record Associated to Current Portal User

Since our record is not related to the current Portal User, option 2 and 4 above are no good. Since the record that our 2nd step operates on is different to that of the 1st step, option 3 is also no good. The only viable option for us is the first option, Query String.

The problem here is that CRM Portal does not include the ID of the related record (the one our 2nd step operates on) in the URL when the user navigates to the 2nd step of the form.

So how do we workaround this?

The workaround…

The workaround, at a high level, is to use JavaScript to manipulate the URL to pass the ID of the related record in the query string to our 2nd step. This JavaScript will run on load of the 1st step, and update the action property of the form HTML element. This effectively changes the URL that is used to navigate to the 2nd step.

Perform the following:

  • Add the lookup field to the related entity (e.g. new_courseenrolmentid) to the backing CRM form of the 1st step. This enables our JavaScript on the web form step to read this value.
  • Make the lookup field above read-only on the backing CRM form so that users cannot update this field on the Portal.
  • Configure the following JavaScript for the 1st web form step. This script passes the ID of the related record to the 2nd web form step using the ‘relatedid‘ query string parameter. Please review the in line comment.
function appendRelatedEntityIdToQueryString() {
	//Append the ID of the related record to the form's post URL using the 'relatedid' query string parameter.
	//If this query string param already exists in the URL then it is updated. Else, it is appended to the end of the URL.

	var relatedEntityId = $("#new_courseenrolmentid").val();

	var $form = $("form");
	var formActionUrl = $form.prop("action");

	if (formActionUrl.indexOf("&relatedid=") == -1) {
		//The URL does not contain our param so just add it.
		formActionUrl += "&relatedid=" + relatedEntityId;
		$form.prop("action", formActionUrl);
		return;
	}

	//Find the existing param in the URL and replace its value.
	var queryStringStartIndex = formActionUrl.indexOf("?") + 1;
	var queryString = queryStringStartIndex == 0 || queryStringStartIndex == formActionUrl.length
		? ""
		: formActionUrl.substring(queryStringStartIndex);

	var queryPairs = queryString.split("&");

	var newQueryString = "";

	for (var i = 0; i < queryPairs.length; i++) {
		/*Find the relatedid and replace it.*/

		if (queryPairs[i].indexOf("relatedid=") == 0) {
			queryPairs[i] = "relatedid=" + relatedEntityId;
		}

		if (newQueryString == "") {
			newQueryString = queryPairs[i];
		} else {
			newQueryString += "&" + queryPairs[i];
		}
	}

	formActionUrl = formActionUrl.substring(0, queryStringStartIndex) + newQueryString;
	$form.prop("action", formActionUrl);
}

$(function () {
	appendRelatedEntityIdToQueryString();
});
  • Configure the 2nd web form step as follow:
    • Set Target Entity Logical Name to the related entity (e.g. Course Enrolment)
    • Set Mode to Edit
    • For Record Source:
      • Set Source Type to Query String
      • Set Primary Key Query String Parameter Name to relatedid
      • Set Primary Key Attribute Logical Name to the lookup field (e.g. new_courseenrolmentid) on the primary entity

That’s it!

Now you should be able to edit the related entity on your 2nd web form step.

By the way…

Do a lot of JavaScript, CSS, HTML and Liquid coding with CRM Portal? Want to source-control these artefacts and work together with your team without overwriting each other’s code? Then check out my Visual Studio extension CRMQuickDeploy! This simple extension allows you to work with these artefacts in Visual Studio and quickly deploy them to CRM!

Posted in CRM, CRM Portal | Leave a comment

Loading entity form dynamically from JavaScript in CRM Portal

This has been tested on xRM Portal CE only.

There may be times where you need to load and display an entity form by JavaScript in CRM Portal. For example, you may have a content page with a link that when clicked, should display an entity form in a modal dialog. You want the modal dialog to display the entity form without any of the site’s branding, navigation, footer, etc.

You can achieve this by leveraging an OOTB service that renders the entity form. This is the same service that is used by the OOTB entity lists and sub-grids.

The service…

The URL for this service is:

[rootWebSite]/_portal/modal-form-template-path/[portalId]?id=[recordId]&entityformid=[entityFormId]

  • portalId: The service seems to be happy with any GUID. We used an empty GUID (00000000-0000-0000-0000-000000000000) and it was fine.
  • recordId: Guid of the record you want to display
  • entityFormId: Guid of the entity form record you want to use

This service returns an HTML document containing the specified entity form loaded with the specified record. The site’s branding, navigation, footer, etc. are excluded, which means the HTML document is ready to be used in a modal dialog via an iframe.

The modal dialog…

CRM Portal uses Bootstrap, so we will use Bootstrap’s modal to load an iframe that invokes the service above and displays the entity form. Again, this is the same approach used by the OOTB entity lists and sub-grids.

Below is the HTML that should be added to the page. The first part (the div) is the normal page content. The second part (the section) is the modal dialog that stays hidden until the user clicks the link in our content. The code for this second part was adapted from the modal element used by the OOTB entity list. Please review the inline comments below.

<!--This is our main content-->

<div>
	Hello world. <a href="javascript:loadEntityFormAsModal();">Click me!</a>
</div>


<!--Code adapted from modal element of entity list. The ID of the section element is used in the JavaScript below.-->

<section id="myModalDialog" aria-hidden="true" aria-label="<span class='fa fa-info-circle' aria-hidden='true'></span> My Modal Title" class="modal fade modal-form modal-form-details" data-backdrop="static" role="dialog" tabindex="-1" style="display: none;">
	<div class="modal-lg modal-dialog">
		<div class="modal-content">
			<div class="modal-header">
				<button aria-label="Close" class="close" data-dismiss="modal" tabindex="0" title="Close" type="button">
					<span aria-hidden="true">×</span><span class="sr-only">Close</span>
				</button>
				<h1 class="modal-title h4" title="My Modal Title">My Modal Title</h1>
			</div>
			<div class="modal-body">
				<div class="form-loading" style="display: none;">
					<span class="fa fa-spinner fa-spin fa-4x" aria-hidden="true"></span>
				</div>

				<iframe src=""></iframe>
			</div>
		</div>
	</div>
</section>

Next we need to add the below JavaScript to the page. If embedded on the same page, then should be wrapped in <script> tags. This script was adapted from the OOTB entity-grid.js script. Please review the inline comments below.

function loadEntityFormAsModal() {
	//Code adapted from entityGrid.prototype.addDetailsActionLinkClickEventHandlers in \js\entity-grid.js.

	//Setup the record and entity form you want to display
	var portalId = "00000000-0000-0000-0000-000000000000";
	var recordId = "B3FEC1EB-7F68-E811-859D-B86B23AC9F7C";
	var entityFormId = "396293DD-9D46-E811-8551-B86B23AC9F7C";

	//Form the URL to the service
	var url = "/_portal/modal-form-template-path/" + portalId + "?id=" + recordId + "&entityformid=" + entityFormId;

	//This ID needs to match the ID of the modal section element.
	var $modal = $("#myModalDialog");

	//Retrieve the iframe of the modal and set it load the service
	var $iFrame = $modal.find("iframe");

	$iFrame.attr("src", url);

	//Hook in handler to hide loader image when done loading
	$iFrame.on("load", function () {
		$modal.find(".form-loading").hide();
		$modal.find("iframe").contents().find("#EntityFormControl").show();
	});

	//Show the loader image at start
	$modal.find(".form-loading").show();
	$modal.find("iframe").contents().find("#EntityFormControl").hide();

	$modal.on('show.bs.modal', function () {
		$modal.attr("aria-hidden", "false");
	});

	$modal.off("hidden.bs.modal.entitygrid").on("hidden.bs.modal.entitygrid", function () {
		$modal.attr("aria-hidden", "true");
	});

	$modal.modal();
}

That’s it!

And there you have it, you can now dynamically launch an entity form in JavaScript. Leveraging the OOTB service means that any entity permissions you may have setup will be honoured, and any JavaScript attached to the entity form itself will also be executed.

By the way…

Do a lot of HTLM, JavaScript, CSS and Liquid coding in CRM Portal? You should check out my Visual Studio extension CRMQuickDeploy. This allows you to work with HTML, JavaScript, CSS and Liquid in Visual Studio and quickly deploy them to CRM Portal. Not only this improves your productivity, but also allows you to source control these artefacts and facilitates scenarios where multiple developers work on the same code base.

Posted in CRM, CRM Portal | Leave a comment

Get current entity logical name and record ID in CRM Portal forms using JavaScript

Use the following JavaScript to retrieve the current entity logical name and record ID in CRM Portal forms:

For Entity Form:

Entity logical name: $(“#EntityFormControl_EntityFormView_EntityName”).val()

Record ID: $(“#EntityFormControl_EntityFormView_EntityID”).val()

For Web Form Step:

Entity logical name: $(“#EntityFormView_EntityName”).val()

Record ID: $(“#EntityFormView_EntityID”).val()

Tested on xRM Portal CE.

Posted in CRM, CRM Portal | Leave a comment

Leveraging built-in services in CRM Portal

On a recent project we decided to build a custom entity list using jQuery DataTables as the OOTB entity list did not meet our requirements. As part of this we needed to build a delete list item functionality that should work similarly to the OOTB counterpart. To achieve this we needed a service that we can call from JavaScript to delete a given record (i.e. a list item).

After some investigation, we discovered that there are several built-in services in CRM Portal that we can invoke using JavaScript. These are the same services used by the OOTB components. Using these services means that we did not have to develop/deploy a custom service to support our custom UI component. It also means that the entity permissions we have configured in CRM are automatically honoured, which is a huge bonus.

In this post I will write about these built-in services, in particular the ‘delete list item’ service, and how you can call them from your JavaScript.

Important: this investigation was done against xRM Portals Community Edition (i.e. the on-prem version of CRM Portal). The findings may or may not apply to CRM Portal on cloud that is hosted by Microsoft. References to source code refer to the source code of XRM Portals Community Edition, which can be downloaded from https://github.com/Adoxio/xRM-Portals-Community-Edition).

The built-in ‘delete list item’ service

This service lives at [portalUrl]/_services/entity-grid-delete/[recordId to delete]. You can invoke this service by performing a POST to this URL. The service accepts an EntityReference parameter to identify the record to delete, and it expects to find this in the POST’s body as JSON, e.g.:

{
"Id":"[guid]",
"LogicalName":"new_myentity"
}

This particular service route maps to the Delete method of the class Site.Areas.Portal.Controllers.EntityGridController, which is defined in the file \Areas\Portal\Controllers\EntityGridController.cs of the Portal project.

A side note: other built-in services

The route mapping of the ‘delete list item’ service is defined in the RegisterArea method of the Site.Areas.Portal.PortalAreaRegistration class, which is defined in the file \Areas\Portal\PortalAreaRegistration.cs of the Portal project.

Reviewing this file quickly shows that there are actually many more built-in services in CRM Portal that you should be able to invoke from JavaScript. Below is a screenshot to illustrate:

Invoking the ‘delete list item’ service from JavaScript

There is a slight complication in invoking this service from JS, and that is it has anti-forgery enabled. This means that our requests to the service must contain a __RequestVerificationToken header with a valid token value.

Fortunately CRM Portal has a built-in JavaScript object that helps us with this. Every page in the Portal automatically includes a reference to the JS file \js\antiforgerytoken.js. This JS defines a global object called shell, which has a method called ajaxSafePost.

ajaxSafePost is essentially a wrapper around jQuery’s ajax method, and is responsible for ensuring the request contains a valid __RequestVerificationToken header before sending it off to the server. This method returns a promise object as it may relies on an async request to retrieve the token (more details later).

Below is an example of how you could use the ajaxSafePost method to invoke the ‘delete list item’ service.

var recordId = "4ca2e5d1-d0fe-4700-b383-59cee9890392";

var entityReference = {};
entityReference.LogicalName = "new_myentity";
entityReference.Id = "4ca2e5d1-d0fe-4700-b383-59cee9890392";

var ajaxPromise = shell.ajaxSafePost({
	type: "POST",
	contentType: "application/json",
	url: "/_services/entity-grid-delete/" + recordId,
	data: JSON.stringify(entityReference)
});

ajaxPromise.done(function () {
	alert("Record deleted!");
});

ajaxPromise.fail(function () {
	alert("Something has gone wrong...");
});

How does ajaxSafePost retrieve the token?

This method first attempts to retrieve the token from an input field on the page. If one is not found, it makes an async request to the URL [portalUrl]/_layout/tokenhtml to request a token.

So… is this supported?

Strictly speaking, since it’s not documented (as far as I’m aware), I’d say this is unsupported. Since the service is fairly loosely coupled to our code however, switching over to a custom service when the need arises should not incur much overhead. You however need to make good judgement based on your own scenario.

So there you have it…

The built-in services are hidden gems that could really streamline your customisation efforts. There are built-in support facilities that make invoking them relatively easy. You however need to consider your scenario carefully as these are not documented, and therefore may change without notice.

I have not had a chance to test whether they also work on CRM Portal online. That is a to-do for the near future.

 

Posted in Adxstudio, CRM, CRM Portal | 2 Comments