Wednesday, 29 January 2014

Notes on accessing AtTask REST service for Project Status reporting

AtTask is used as our project management software. The REST service for it lives here:

https://[yourcompanynamehere].attask-ondemand.com/attask/api/

The documentation is here.

To get info on projects:

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?



To get info on tasks:

 https://[yourcompanynamehere].attask-ondemand.com/attask/api/task/search? will return task information.

To get all of the fields for a project you can add 'field=*' to the query string.

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?fields=*


To just get specific fields pass the field names as comma separated.

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?fields=actualCost,actualCompletionDate


To filter put the field name and the value you want:

https://[yourcompanynamehere].attask-ondemand.com/attask/api/project/search?status=CUR


From .net you can use the System.Net.Http.HttpClient object to call the above URLs. You need to tell the HttpClient object that it's cool to get back a JSON response so you need to add a handler to it, something like this:

var proxy = new WebProxy
                {
                    Address = new Uri(proxyUri),
                    Credentials = CredentialCache.DefaultNetworkCredentials
                };

var handler = new HttpClientHandler
                    {
                        PreAuthenticate = true,
                        ClientCertificateOptions = ClientCertificateOption.Automatic,
                        UseProxy = true,
                        Proxy = proxy
                    };

var client = new HttpClient(handler) {BaseAddress = new Uri(baseUrl)};

// make it so we accept json as a response
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

HttpResponseMessage response = client.GetAsync(url).Result;


Then use the System.Web.Script.Serialization.JavaScriptSerializer to map the JSON into .Net objects:

var serializer = new JavaScriptSerializer();

var projects = serializer.Deserialize<IList<Project>>(json);

Where your Project object contains all the fields you are expecting from the JSON

public class ProjectDto
{
    public string ID { get; set; }
    public string name { get; set; }
    public string objCode { get; set; }
    public string percentComplete { get; set; }
    public string plannedCompletionDate { get; set; }
    public string plannedStartDate { get; set; }
    public string priority { get; set; }
    public string projectedCompletionDate { get; set; }
    public string status { get; set; }

}


You can use the above data to make reports based on real time project status:



Sunday, 26 January 2014

A Chrome Extension to alter a pages HTML using JQuery on button click


This is a chrome extension that will add a button to the browser next to the URL bar; When the button is clicked it will search the content of the current page and make any tweets it finds on the page much better.

The code is here:
https://sites.google.com/site/kpdownloadz/downloads/tweet%20improver.zip?attredirects=0&d=1

How to use this extension: I am too cheap to pay for the developer license which lets you upload it to the Chrome Web Store so you have to download it and then load it into the browser manually.

  1. Download the above zip and unpack to your PC
  2. Open Chrome and put 'chrome://extensions/' into a browser window
  3. Make sure 'Developer mode' is ticked
  4. Press 'Load unpacked extension...' and select the folder where you unpacked the above zip
  5. A little TI icon will show up to the right of the URL bar which you can click on when visiting twitter sites



There's a bit of clowning around to get access to jquery, so this is my documentation for the process. NOTE: I was a BAD DEVELOPER and didn't read the documentation; this was all copy and paste and guess, so some of it is probably a bit wrong. Anyway, you'll end up with all of these files:



1. Start with the manifest.json. This describes the extension.


{
  "name": "Tweet Improver",
  "description": "This will fix all your dumb tweets",
  "version": "2.0",
  "permissions": [
    "activeTab"
  ],
  "background": {
    "scripts": ["jquery-2.1.0.min.js","background.js"],
    "persistent": false
  },
  "browser_action": {
    "default_title": "Improve your dumb tweets now",
"default_icon": {
      "19": "TI19.png",
      "38": "TI38.png"
    }
  },
  "icons": { "16": "TI16.png",
           "48": "TI48.png",
          "128": "TI128.png" },
  "manifest_version": 2,
  "content_scripts": [ {
    "js": [ "jquery-2.1.0.min.js", "background.js" ],
    "matches": [ "http://*/*", "https://*/*"]
  }]
}

For the background item we give it two scripts, we give it jquery first as apparently it loads them in that order.  Then we give it background.js, which is the main file being called by the browser action. These files need to be locally in your extension folder.

The browser_action bit is where we describe what our little button is going to look like.  We give it a couple of icons and some text that will show as a tool tip.

The icons bit describes the icons used for the chrome://extensions page and I think also the extensions downloads page (dunno, I never put mine up there).

The content_Scripts bit says that we want jquery injected into our background.js page, so in theory we should be able to use jquery when writing code in background.js.

2. Make a background.js


This is the js file our extension is hooked up to:

chrome.browserAction.onClicked.addListener(function(tab) {

  chrome.tabs.executeScript({
file: 'doittoit.js'
  });

});


Ok, there isn't much here. We're saying that when our browserAction icon thing is clicked we want to execute the js in the file doittoit.js.  

3. Your big mess of custom javascript to manipulate the DOM


You can have whatever you want in here - you've got access to all the jquery stuff.

  var tweetText = $(".tweet-text");

  tweetText.each(function() {
var newText = "";
var currentText = $(this).text();
if(currentText.length < 4) {
newText = "8=D";
} else {
newText = "8" + Array(currentText.length-1).join("=") + "D";
}
    $(this).text(newText);
  });

  $('.js-media-container').html('');

We do a search for any elements that have the class 'tweet-text' and then replace the text of that element to something that was better than the original. We also clear out any pictures (by setting the HTML of 'js-media-container' elements to nothing).




Wednesday, 8 January 2014

Save attachments from an Exchange mail box using the EWS Managed API

So from C# I wanted be able to save email attachments being sent to an Exchange mailbox. I bet you're all like 'good luck dude, that POP/imap/mapi stuff is terrible and really I can't believe no one has sorted out a good way to do it yet'.  Yeah well screw you, have you heard about the Exchange Web Service Managed API? It's pretty sweet. It's easy to setup, easy to use & pretty intuitive. We get to use predefined objects AND I LOVE OBJECTS. This is how to do it.

The summary version:
You get a dll off microsoft, reference it in your project, give it some authentication details. It will work out where your exchange server is (i.e. you don't need to tell it the server name/ip or manually add the EWS as a service reference to your project).  Then you get to mess around with the supplied objects from that dll to pull and push data from the exchange server.  When pulling back objects from the server (e.g. an email) you often get the ability to pull back just a little bit of info (e.g. the id) or to get more details (e.g. base properties or even specific properties).  If you make a change to an object you need to push that change back to the server by calling a method on that object (e.g. message.Update(...)).

The longer version:

First you need to download the Microsoft dlls here that will give you the nifty objects and handle all the client/server communication:

http://www.microsoft.com/en-nz/download/details.aspx?id=35371

This is an MSI.  But whatevs, it puts a couple of DLLs on your system that you can then copy and reference from your project:



First you've got to create your service object.  Supply it the schema definition that you want to work with.  From what I understand this is backwards compatible; for example if you hook it up to Exchange2010 SP1, when you move to SP2 your code against the SP1 schema will still work fine, but you have the option of updating your code to run against SP2 which will have more functionality. The below code uses Microsoft Black Magic® to figure out where your exchange server is and how to talk to it's associated EWS.

static ExchangeService _service = new ExchangeService(ExchangeVersion.Exchange2010_SP2); 

static Program() 

    _service.Credentials = new WebCredentials("myusername", "password");
    _service.AutodiscoverUrl("my.email@somewhere.com"); 


Next, get a reference to a specific folder in the Exchange mailbox.  The below method takes a folder name and searches in the inbox for that folder. This uses the FindFolders method on the service object. To use it just give it a starting point (e.g. the inbox or the root) a search filter (an object that describes what we're looking for) and a view (an object that describes what we want returned e.g. what properties we require on the results objects).

public static Folder GetFolderByName(string folderName)
{
    FolderView view = new FolderView(10);
    view.PropertySet = new PropertySet(BasePropertySet.IdOnly); // just give us the id in the results object
    view.PropertySet.Add(FolderSchema.DisplayName); // on second thoughts, also give us the folder name
    view.Traversal = FolderTraversal.Deep; // search sub folders

    SearchFilter searchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, folderName);

    FindFoldersResults findFolderResults = _service.FindFolders(WellKnownFolderName.Inbox, searchFilter, view);
    if(findFolderResults.Count() > 1)
        throw new Exception(string.Format("Found {0} folders with title '{1}' - the name needs to be unique; don't know which one to use", findFolderResults.Count(), folderName));

    return findFolderResults.FirstOrDefault();
}



Once you've got a folder you can find the unread emails in that folder.  This runs a search similar to the above, calling the FindItems method on the service object.  Pass it the folder id you want to search in and a filter (describing what we're looking for) and a view (describing what we want returned).  Then we do some clowning around to get the message as an EmailMessage type with some specific properties loaded (.Attachments and .HasAttachments) rather than the generic Item type.


public static List<EmailMessage> GetUnreadEmailsInFolder(Folder folder)
{
    var messages = new List<EmailMessage>();

    // only get unread emails
    SearchFilter folderSearchFilter = new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false);
    // we just need the id in our results
    var itemView = new ItemView(10) {PropertySet = new PropertySet(BasePropertySet.IdOnly)};

    FindItemsResults<Item> findResults = _service.FindItems(folder.Id, folderSearchFilter, itemView);
         
    foreach (Item item in findResults.Items.Where(i => i is EmailMessage))
    {
        EmailMessage message = EmailMessage.Bind(_service, item.Id, new PropertySet(BasePropertySet.IdOnly, ItemSchema.Attachments, ItemSchema.HasAttachments));
        messages.Add(message);
    }

    return messages;
}


Now we have a list of emails.  Loop through them, get any attachments from the Exchange server and save them to disk.  When dealing with attachments it seems like you need to explicitly call the .Load() method of the attachment to pull it from Exchange.
To set the email as having been read just update the IsRead property and then call the .Update(...) method to push the change back to Exchange.

public static void ProcessEmail(EmailMessage message)
{
    string saveDir = ConfigurationManager.AppSettings["AttachmentSaveDirectory"];
    if (message.HasAttachments)
    {
        foreach (Attachment attachment in message.Attachments.Where(a=> a is FileAttachment))
        {
            FileAttachment fileAttachment = attachment as FileAttachment;
            fileAttachment.Load(); // populate the content property of the attachment

            using (FileStream fs = new FileStream(saveDir + attachment.Name, FileMode.Create))
            {
                using (BinaryWriter w = new BinaryWriter(fs))
                {
                    w.Write(fileAttachment.Content);
                }
            }
        }
    }
    message.IsRead = true;
    message.Update(ConflictResolutionMode.AutoResolve); // push changes back to server
}



Pack it up pack it in, jobs done.