Showing posts with label plugin. Show all posts
Showing posts with label plugin. Show all posts

Monday, April 30, 2012

Timesheets with TFS Continued…

So what were those screen shots?

Lets look at them again.

image

Screen shot is for TFS 2010, and what is that link saying manage timesheet? and I don’t get the reports tab!

Ok, Lets just talk about Timesheet tab, I will write about reports later. Its one more plugin. I will talk about the problem and solution that tab is catering to in next post.

Back to timesheet.

I wouldn’t want do discuss the steps to make it 2010 compatible, overall this requires some fixes in the master pages, content pages and javascript object model to make it to work. 

For the plug-in to work with multiple collections and projects, I created a table for collections tblCollections. And one more table for Projects. Here are some of the columns

  • ID -  called projectcollectionid as this is the combination of project id and collection id.
  • Project Name – Same as the TFS Project Name
  • Project Manager -  TFS project can have multiple admins, but in most of the cases there is a single manager who manages the tasks and can approve the timesheets.
  • Team Size – just a metadata information
  • CollectionId – so that you could map with collections
  • PGUID – project GUID that TFS maintains, can be handy in reports.

The link you see the screenshot above is admin link available only for super administrator who activates the timesheet feature for a project. This is the time when you capture most of the project metadata information. This is how this new screen looks.

image

I have used entity framework(.Net 4.0) as my ORM Data access tier. So my collection id determination looks like

public static int GetProjectCollectionId(string ProjectName, string collectionName)
{
using (TfsTimesheetEntities ctx = new TfsTimesheetEntities())
{
var project = ctx.Projects.Where(x => x.Collection.CollectionName == collectionName && x.ProjectName == ProjectName).FirstOrDefault();
if (project != null)
{
return project.Id;
}
}

return 0;
}



Similarly, using the project manager id we could determine if the logged in user is Project admin using following code



public static bool IsProjectAdmin(string logon, int projectCollectionId)
{
if (IsUserAdministrator(logon))
return true;

using(TfsTimesheetEntities ctx = new TfsTimesheetEntities())
{
var admin = ctx.Projects.Where(x => x.Id == projectCollectionId).FirstOrDefault();
if (admin != null)
{
if (String.Compare(logon, admin.ManagerId.Trim(), true) ==0)
{
return true;
}
}
}

return false;
}





While rendering the timesheet plugin, to determine if we have to enable any of the admin features, you call the above method to see if user is a project manager for this project.




if (TimeSheetHelper.IsProjectAdmin(Request.LogonUserIdentity.Name, this.ProjectCollectionId))
{
toolbar.Items.Add(new Microsoft.TeamFoundation.WebAccess.Controls.MenuItem(true));
toolbar.Items.Add(new Microsoft.TeamFoundation.WebAccess.Controls.MenuItem("Admin", "admin", ResolveUrl("~/Resources/Images/admin.gif")));
toolbar.Items[toolbar.Items.Count - 1].Action = "parent.location='Admin.aspx?pguid=" + this.Locator.ProjectUri.Segments[3]+"'";
}





Now, bigger part of the work remains is in the database side.



  • First you will have to include the project collection id in all calls. So that reference to all entries are related to project collection ids and not to project ids.


  • Next, you will have to pass the collection name in all the SPs that makes a cross DB joins to connect to appropriate database. For example…




SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF EXISTS(SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[usp_RS_AwaitingApproval]')
AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[usp_RS_AwaitingApproval]
GO
CREATE PROCEDURE [dbo].[usp_RS_AwaitingApproval]
@collectionName nvarchar(100),
@projectCollectionId int
AS
BEGIN
SET NOCOUNT ON

DECLARE @statement nvarchar(1000) = N'SELECT tc.sid, DisplayPart AS displayName, weekStarting
FROM TimesheetCompletions tc WITH(NOLOCK)
JOIN TFS_'
+ @collectionName +'.dbo.Constants WITH(NOLOCK)
ON tc.sid = Constants.sid
WHERE completed = 1 AND
tc.ProjectCollectionId ='
+ CAST(@projectCollectionId as nvarchar(8)) +' AND
approved IS NULL'


EXEC(@statement)

END
GO

Above snippet connects to TFS_<CollectionName> db to get he usernames.




This must have given you an idea on how the plugin can be extended to serve multiple collections. Next part you will be interested in is getting report of who has filled timesheet in your organization. I used SSRS report, following is the query that I used to get the result.




SELECT prj.id, 
MAX( [ProjectName]) AS ProjectName,
MAX(ManagerName) AS ManagerName,
MAX(WeekStarting) AS WeekStarting,
dbo.TFSGetUserName(tc.sid) as DisplayPart, -- this is function i created to get the username
-- refer to the constants table easily.
Completed as EnteredTimeSheet,
(approved )as Closed,
MAX(prj.TeamSize) as TeamSize,
SUM(te.hours) as HoursLogged
FROM timesheetcompletions tc
JOIN Projects prj ON tc.projectCollectionId=prj.id
JOIN TimesheetEntries te on tc.sid = te.sid and tc.ProjectCollectionId = te.ProjectCollectionId
AND (te.entryDate &gt; tc.weekStarting AND te.entryDate &lt; DATEADD(DAY,7, tc.weekStarting ) )
where DATEADD(dd, 0, DATEDIFF(dd, 0, WeekStarting)) =DATEADD(dd, 0, DATEDIFF(dd, 0, @Startdate))
GROUP BY prj.id
, tc.sid





Report would look something like…



image



There is another interesting report involved, that is to figure out which contributor hasn’t filed his/her timesheet. Which is bit complex report compared to this one, I will write about it some other time.

Friday, April 27, 2012

Timesheets with TFS

If you haven't taken look at http://tfstimesheet.codeplex.com/ already, please take a look at it. It’s a pretty interesting plugin developed and quite useful. If you are an organization who is worried about daily utilization and derive billing out of it, its pretty handy and solves many of the problems.

If you are thinking why do I need this, when I can update the task directly – here is the answer

Task Edit TFS

  • People are lazy – its not possible to expect everyone to update all the tasks they worked on, on a daily basis. If they miss one day; its not present in the TFS Cube daily snapshot.
  • If you are working on say 5 generic tasks(say daily standup, code reviews, helping QA etc) on regular basis; its pretty hard to open all talks and update the numbers.
  • No approvals – If the time logged is important for your business, like billing; you definitely want to have an step to review and approve the time logged.

All these problems can be addressed by a simple table view of the tasks and days, something like this.

image

Now its easy, you can enter effort for any day for the week, multiple tasks in one go, one single view.

All right, that answers all the problems we talked about.

But, but, this plugin

  • Doesn’t work with TFS 2010 server Sad smile
  • Doesn’t support working with multiple projects and collections
  • Doesn’t support approvals by projects(i.e. having one PM for each project)
  • Doesn’t come with reports

So how do you solve? Read on…

Saturday, June 12, 2010

Displaying FBJS dialog on top of Plug-in content

You cannot display any HTML Content on top of a plugin content. This is because of different window message loops running for browser and the plugin content. If you are working with Silverlight there is an option to specify to Silverlight to use browser windows message loops for paint messages. Set windowless=true to make use of this.

However this is not recommended as it will drastically slowdown your Silverlight application. Well, there is no point in using a slow Silverlight application.

If you have realized the limitations by now, lets work on the workaround. I have an application called ittiGator( a facebook game http://apps.facebook.com/ittigator ) where we are prompting with a publish dialog on completing the game.


What we will do is, we will create two DIVs sitting one top of other. We will host our Silverlight in one and publish button in other.
DIV – Publish button with some background.
DIV – Silverlight Content

Do you want to share your High Score with your friends?
We will call a JavaScript method called publish from Silverlight using
HtmlPage.Window.Invoke("publishScore", new string[] { UserScore.Text, UserName.Text });
In this publishscore method we will increase the height of ShowCase div to amount that is enough to push the plugin content just below the visible screen area.

function publishscore (score, usrname) 
{
document.getElementById("Showcase").style.height = '800px';
}
Reset the height on the publish dialog call back function
FB.Connect.streamPublish('', g_attachment, g_action_links, null, 'Post your high score and let the world follow you!', stream_callback_resizetheDiv);

This should work if you work with flash based application or even if you have an activex control embedded.

Leave a comment if you need any assistance in implementing this.

Tuesday, May 18, 2010

Dealing with browser repaints

Recently i wanted to hide a silverlight plugin content and display some html content in its place. As you might be knowing, plug-in content redrawing is a known problem all the time. So i decided to put both the contents in a table row and dynamically set the row height.



document.getElementById("Showcase").style.height = '0px';
               and
document.getElementById("Showcase").style.height = '500px';

Problem later i found that it works fine with IE, FF, Chrome but in opera its a problem. It requires you to scroll once for redrawing the content.  So you will still see the flash/Silverlight content and the new html content, untill you scroll.

This has been discussed in couple of places, but could not find any solution. So, i decided to put a scroll automatically. I tried scrolling it with 1 px, it worked!!! So i tried with 0px.... and ... it worked too!!!!

window.scroll(0, 0);
Try and let me know if it fixes your repain problem too...