Tuesday, December 22, 2015

Timesheet Work Item Control for TFS Web Access Source Code

This post is just the javascript code for timesheet work item plugin in tfs web portal, the entire instruction to make the plugin work is in my post here.

TimeSheetControl.WorkItemTimeSheetControl.debug.js

{
TFS.module("TimeSheetControl.WorkItemTimeSheetControl",
    [
        "TFS.WorkItemTracking.Controls",
        "TFS.WorkItemTracking",
        "TFS.Core"
    ],
    function () {

var WITOM = TFS.WorkItemTracking,
WITCONTROLS = TFS.WorkItemTracking.Controls,
delegate = TFS.Core.delegate;
var timesheet;
var timesheetXMLDoc;
var backupTimesheet = timesheet;
function WorkItemTimeSheetControl(container, options, workItemType) {
this.baseConstructor.call(this, container, options, workItemType);
}
WorkItemTimeSheetControl.inherit(WITCONTROLS.WorkItemControl, {
_control: null,
_init: function () {
this._base();
var dateHTML = "<label>Date:</label> <input type='text' id=\"date\" size='8' value='"+$.datepicker.formatDate('yy-mm-dd', new Date())+"'>";
var hourHTML = " Hours: <input type='number' id=\"hours\" value='1.00' size='3' min='0' max='24'>";
var commentsHTML = " Comments: <textarea rows='1' cols='25' id=\"comments\"></textarea>";
var addButtomHTML = " <input type='button' id='addButton' value='Add'>";
this._control = $(dateHTML + hourHTML + commentsHTML + addButtomHTML).appendTo(this._container);
$('#addButton').bind("click", delegate(this, this.addButtonClick));
this._control = $("<p><label id=\"messages\"></label></p>").appendTo(this._container);
$("<style type='text/css'> .ui-datepicker{ background-color: #FFF;} #timesheetTable{border-collapse: collapse;border: 1px solid #e6e6e6;} th {text-align: left;} #hours{width:50px;} #date{width:80px;}</style> ").appendTo("head");
$.getScript('//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js', function(){
$("#date").datepicker({dateFormat: "yy-mm-dd"});
});
},

invalidate: function (flushing) {
timesheet = this._workItem.getFieldValue("TimesheetRawData");
if(timesheet===""||timesheet==="[]")
timesheet = "<?xml version=\"1.0\"?><ArrayOfTimeSheetEntry xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"></ArrayOfTimeSheetEntry>";
try{
timesheetXMLDoc = $.parseXML(timesheet);
}
catch(err){
document.getElementById("messages").innerHTML = err.message;
}
this.printTable(timesheetXMLDoc);
    },

        clear: function () {
            timesheet = backupTimesheet;
        },

        addButtonClick: function () {
var todayDate = new Date();
var year = todayDate.getFullYear();
var day = todayDate.getDate();
var month = todayDate.getMonth()+1; 
var hour = (todayDate.getHours()<10)?"0"+todayDate.getHours():todayDate.getHours();
var minute = (todayDate.getMinutes()<10)?"0"+todayDate.getMinutes():todayDate.getMinutes();
var second = (todayDate.getSeconds()<10)?"0"+todayDate.getSeconds():todayDate.getSeconds();
var miliSecond = todayDate.getMilliseconds();
var createdDate = year+"-"+month+"-"+day+"T"+hour+":"+minute+":"+second+"."+miliSecond;
var TSDTmp = document.getElementById("date").value;
var timesheetDate = TSDTmp+"T00:00:00";
var minutes = document.getElementById("hours").value * 60;
if(minutes > 1440) minutes = 1440;
else if(minutes < 0) minutes = 0;
var comments = document.getElementById("comments").value;
var currentUser = this._workItem.store.getCurrentUser();
var newNode = timesheetXMLDoc.createElement("TimeSheetEntry");
newNode.setAttribute("CreatedDate", createdDate);
newNode.setAttribute("CreatedBy", currentUser);
newNode.setAttribute("TimeSheetDate", timesheetDate);
newNode.setAttribute("Minutes", minutes);
newNode.setAttribute("Comments", comments);
timesheetXMLDoc.documentElement.appendChild(newNode);
this._workItem.setFieldValue("TimesheetRawData",new XMLSerializer().serializeToString(timesheetXMLDoc));
var completedWork = this.getCompletedWork() + minutes/60;
var remainingWork = this.getRemainingWork()- minutes/60;
if (remainingWork < 0)
remainingWork = 0;
this.setRemainingWork(remainingWork);
this.setCompletedWork(completedWork);
document.getElementById("date").value=$.datepicker.formatDate('yy-mm-dd', new Date());
document.getElementById("comments").value="";
document.getElementById("hours").value="1.00";
        },

deleteButtonClick: function(event){
var index = event.data.indexParam;
if(index.length === 0 || index < 0 || index > (timesheetXMLDoc.documentElement.getElementsByTagName("TimeSheetEntry").length-1))
alert("Invalid item index!");
else{
var removeItem = timesheetXMLDoc.documentElement.getElementsByTagName("TimeSheetEntry")[index];
var minutes = removeItem.getAttribute("Minutes");
timesheetXMLDoc.documentElement.removeChild(removeItem);
var self = event.data.o;
self._workItem.setFieldValue("TimesheetRawData",new XMLSerializer().serializeToString(timesheetXMLDoc));
var completedWork = self.getCompletedWork() - minutes/60;
var remainingWork = self.getRemainingWork() + minutes/60;
if (completedWork < 0)
completedWork = 0;
self.setRemainingWork(remainingWork);
self.setCompletedWork(completedWork);
}
},
printTable :function(timesheetXMLDoc)
{
if(this._container.children("table")!=''){
this._container.children("table").remove();
}
var tableSource = "<table id=\"timesheetTable\"><col width='70'><col width='80'><col width='30'><col width='270'><col width='50'>";
tableSource += "<tr bgcolor='#cce0ff' align='left'><th>User</th><th>Date</th><th>Hour</th><th>Comments</th><th>Delete</th></tr>";
try {
var nodeList = timesheetXMLDoc.getElementsByTagName("TimeSheetEntry");
for (var i = 0; i < nodeList.length; i++) {
tr = ((i%2)===0)?"<tr>":"<tr bgcolor='#e5efff'>";
tr += "<td>" + nodeList[i].getAttribute("CreatedBy")+ "</td>";
var dateRaw = nodeList[i].getAttribute("TimeSheetDate");
tr += "<td>" + dateRaw.substring(5,7)+"/"+dateRaw.substring(8,10)+"/"+dateRaw.substring(0,4) + "</td>";
tr += "<td>" + nodeList[i].getAttribute("Minutes")/60 + "</td>";
tr += "<td>" + nodeList[i].getAttribute("Comments") + "</td>";
tr += "<td> <input type='button' id='dButton"+i+"' value='delete'></td></tr>";
tableSource += tr;
}
tableSource += "</table>";
this._control = $(tableSource).appendTo(this._container);
for (var i = 0; i < nodeList.length; i++) {
$('#dButton'+i).on("click", {indexParam:i, o:this}, this.deleteButtonClick);
}
}
catch(err) {
document.getElementById("messages").innerHTML = "No entry :"+err.message;
}
},
getRemainingWork: function(){
if(this._workItem.getField("Remaining Work")!=null && this._workItem.getFieldValue("Remaining Work")!=""){
return this._workItem.getFieldValue("Remaining Work");
}
else return 0;
},

getCompletedWork: function(){
if(this._workItem.getField("Completed Work")!=null && this._workItem.getFieldValue("Completed Work")!=""){
return this._workItem.getFieldValue("Completed Work");
}
else return 0;
},
setRemainingWork: function(remainingWork){
this._workItem.setFieldValue("Remaining Work", remainingWork);
},
setCompletedWork: function(completedWork){
this._workItem.setFieldValue("Completed Work", completedWork);
},
    });

    // Register this module as a work item custom control called "WorkItemTimeSheetControl"
    WITCONTROLS.registerWorkItemControl("WorkItemTimeSheetControl", WorkItemTimeSheetControl);

    return {
        WorkItemTimeSheetControl: WorkItemTimeSheetControl
    };
})}

TFS Web Access Work Item - Timesheet Plugin

Team Foundation Server Web Access - Timesheet Plugin

Visual Studio Version

For the Visual Studio Timesheet Plugin, there are some good posts: 
Step by step introduction about adding a work item control in visual studio: https://nickhoggard.wordpress.com/2009/11/12/tfs-2010-beta-2-custom-work-item-controls-step-1-getting-started/
Source code for Visual Studio 2010: https://tfstimesheets.codeplex.com/

I remember the source code in codeplex is for Visual Studio 2010, to work on other versions of Visual Studio, you need to replace some of the assemblies with the right version.

For example, to add the timesheet for Visual Studio 2015,
Assembly: Microsoft.TeamFoundation.WorkItemTracking.Client is located at
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.WorkItemTracking.Client.dll
Assembly: Microsoft.TeamFoundation.WorkItemTracking.Controls is located at 
C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\CommonExtensions\Microsoft\TeamFoundation\Team Explorer\Microsoft.TeamFoundation.WorkItemTracking.Controls.dll

To add those dlls, you may need to install TFS 2015 on your local machine.

There are many good posts on web, if you search a little bit, you will be able to get some idea or instruction to make it work. I won't show you how to work on the visual studio version on this post.

To debug your code, see my next post Debug timesheet plugin for tfs.

Web Access Version

Instroduction

There are many posts about visual studio version of tfs work item control, but the web access version is limited.

Here are some good ones: http://www.ewaldhofman.nl/post/2010/08/10/Create-custom-work-item-control-for-TFS-Web-Access-2010-(TWA).aspx

But for "timesheet" plugin, it is a little bit complicated, because it needs to get data from the work item control, like "completed hours", "remaining hours", "timesheetRawData", it is not a simple button or text message. 

In this post, I would show you how to create a timesheet control for TFS 2013 Web Access. It will be looking like this:
For TFS that has version 2013 or higher, the control items for web are all written in javascript, no back end code involved. But in the Visual Studio version, it has a timesheetControl model created which handles all the actions and events from the control. This means we have to transform the C# code in the model to javascript to implement the same functionality.

Deploy Timesheet Control in TFS Web Access

The basic idea of Web Control for TFS 2013 is that you create a .zip file includes some description about your control and some javascript codes about your control, then in the tfs web interface, install the .zip file in control panel (tell the system, here is the code you need), and update the task.xml to include the layout of the timesheet.(Tell the system where I want to put my control in. In my example, I create the timesheet tab under "task"). In the installation part, you will need proper permission to install the extension, if you are not the admin of your tfs server, you can install tfs 2013 on your local, test the control item, and then send the zip file to your tfs server admin to let them install it for you.

Get Ready for the Code
There are totally three files need to be zipped in, 
1. manifest.xml
2. TimeSheetControl.WorkItemTimeSheetControl.debug.js
3. TimeSheetControl.WorkItemTimeSheetControl.min.js

1. manifest.xml

<WebAccess version="13.0">
  <plugin name="TimeSheet TFS 2013 Web Access Custom Control" vendor="BankrateTimesheet" moreinfo="http://www.bankrate.com/" version="1.0.0" >
    <modules>
      <module namespace="TimeSheetControl.WorkItemTimeSheetControl" kind="TFS.WorkItem.CustomControl"/>
    </modules>
  </plugin>
</WebAccess>

2. TimeSheetControl.WorkItemTimeSheetControl.debug.js


3. TimeSheetControl.WorkItemTimeSheetControl.min.js
For debug purpose, instead of create a min file, we copy, paste TimeSheetControl.WorkItemTimeSheetControl.debug.js and rename it as TimeSheetControl.WorkItemTimeSheetControl.min.js, which means .min.js and .js has the same content and it is easy for debugging.

When release, to make the min.js file, you can use any online free tools, I used http://fmarcia.info/jsmin/test.html, copy the code from TimeSheetControl.WorkItemTimeSheetControl.debug.js, and paste it on the website and click JSMin button, you will get the min version of js file. Copy the min code and create a new TimeSheetControl.WorkItemTimeSheetControl.min.js file and paste it.

Now you have three files

Select all the files and right click -> Send to -> Compressed(Zipped) folder, the name of the folder doesn't matter.

Install Timesheet Plugin

We have the source code ready, now we need to install it on tfs.

Open your tfs web portal, click the button on the right top corner
Go to Control Panel.

Go to tab Extensions, click "Install" button and choose the zip file we just created.
"Enable" the extension by click "Enable" under the control.

Modify Task.xml 

Now we are all set about the code and installation part. TFS now knows what is the timesheet, how it should be look like, what it does, but TFS dosen't know where it should appear. The next step is to update the task.xml file in tfs server, to tell TFS we want the timesheet to be a tab under "task" work item.

Run Command Prompt by click start -> Type in "cmd" in search box, hit enter.
Go and run the command below, /p:is your project name:
 
Then you will get your task.xml file under the folder: C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE

Open task.xml, add the high lighted following in proper location
<FIELD name="Completed Work" refname="Microsoft.VSTS.Scheduling.CompletedWork" type="Double" reportable="measure" formula="sum">
<FIELD name="TimesheetRawData" refname="Custom.Timesheets.TimesheetRawData" type="PlainText" />
<FIELD name="TimesheetMinDate" refname="Custom.Timesheets.TimesheetMinDate" type="DateTime" />
<FIELD name="TimesheetMaxDate" refname="Custom.Timesheets.TimesheetMaxDate" type="DateTime" />

<Tab Label="Attachments">
                <Control Type="AttachmentsControl" Label="" LabelPosition="Top" />
 </Tab>
<Tab Label="TimeSheets">
<Control Type="WorkItemTimeSheetControl" LabelPosition="Top" FieldName="Custom.Timesheets.TimesheetRawData"/>
</Tab>

Save the file and run the command:
If you get any error, you probably don't have the right permission to update the file or you have wrong format in your file.

Now go to any task:

If there is any error messages show up, you can debug it yourself, see my next post: Debug timeshee plugin for TFS






Debugging Timesheet WorkItem Control in TFS Web Access

Debugging Timesheet WorkItem Control in TFS Web Access

After installing the plugin on your tfs, you can debug it if there is any problem or bug. The coding and installation part is in my previous post.

Web Access

You can use any browser's debug tool, here use Chrome as example.

Remember there are three files in the zip, one is .min.js which is used by the browser. For debug purpose, instead of create a min file, we copy, paste TimeSheetControl.WorkItemTimeSheetControl.debug.js and rename it as TimeSheetControl.WorkItemTimeSheetControl.min.js, which means .min.js and .js has the same content and it is easy for debugging.

Open Chrome, go to a task of your project. Press F12 or go to developer tools.
Go to Sources tab, open the js file as shown in the picture below.
Set the break point anywhere you want to debug. Note that workItem can only be accessed when any action was taken, like add and delete. You can check the values of workItem by setting a break point in add or delete functions.

Visual Studio

To debug the timesheet plugin in visual studio 2015, go to the Solution Explore of your timesheet source code.

Right click on your solution, choose properties.

In Property Pages, go to Configuration Properties, Configuration choose Active(Debug).

Remove all the break point in your code, right click on the solution and choose clean solution, and rebuild solution.

Now navigate to your project's bin folder, copy all the files specially including the .pdb files which are the debug files, to C:\ProgramData\Microsoft\Team Foundation\Work Item Tracking\Custom Controls\14.0. The above location is where your timesheet plugin installed for Visual Studio 2015.

Set break points in your source code, and open another instance of visual studio, go to Team Explore, open any task's timesheet tab. You will be able to debug your timesheet code.

Wednesday, November 25, 2015

Windows SetUp Project in VS

Windows set up project.

When I open a project from TFS which include a setup project in the solution, an error pops up telling me visual studio cannot open it correctly.

The solution is to install an extension for visual studio:
https://visualstudiogallery.msdn.microsoft.com/f1cc3f3e-c300-40a7-8797-c509fb8933b9

Thursday, July 23, 2015

Enable and disable codes between #if #endif

When we want to comment out some code in specific condition, we can use #if  and #endif in C#  instead of comment and uncomment things.

#if Local
   do something1
#endif
 
#if Test
    do something2
#endif

If you want to enable do something1 and do something2:
Under Project -> your project property
Build tab -> Conditional compilation symbols: Local; Test

Enable the modes separated by semicolon.

Wednesday, July 1, 2015

Hide/Unhide Table in Microsoft SQL

Part 1: Hide A Table

Suppose you want to hide a table which has important items that you don't want others to see from SQL Server Management Studio, but still want to use the data within this table, like select something from it as normal, you can run the following query:

use Test; --Database name
EXEC sp_addextendedproperty
@name = N'microsoft_database_tools_support',
@value = '<Hide? , sysname, 1>',
@level0type ='schema',
@level0name ='dbo',  --give the schema name here. if it is dbo give 'dbo'
@level1type = 'table',
@level1name = 'Meeting'  --give the table name which you want to hide.

In this example, I hide the "dbo.Meeting" table in database Test.
By running the above, table dbo.Meeting will be listed under System Tables subfolder.
But I can still use select * from dbo.Meeting to get the data from table Meeting.

Another more simple way of doing this is to modify the property of the Table:
1. right click on the table you want to hide
2. select Properties

3.In Table Properties window, select Extended Properties
Add a new entry with
Name: microsoft_database_tools_support
Value: <Hide? , sysname, 1>
Click OK

4. Refresh the Table,
You will see Table Meeting is under System Tables.

Part 2: Unhide A Table
Do the same thing as 1 and 2 in part one.
Delete the Extended Properties we just added.
The table will come back to the original place.