diff --git a/Osiris.AssociateRecentWorkItems/NewWorkItemsSection.cs b/Osiris.AssociateRecentWorkItems/NewWorkItemsSection.cs new file mode 100644 index 0000000..6c35df1 --- /dev/null +++ b/Osiris.AssociateRecentWorkItems/NewWorkItemsSection.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.TeamFoundation.Controls; +using Microsoft.TeamFoundation.VersionControl.Client; +using Microsoft.TeamFoundation.WorkItemTracking.Client; + +namespace Osiris.AssociateRecentWorkItems +{ + [TeamExplorerSection(SectionId, TeamExplorerPageIds.PendingChanges, 34)] + public class NewWorkItemsSection : WorkItemsSectionBase + { + public const string SectionId = "433130C1-5E7B-48A3-A142-B1F1567B2AB3"; + private const string SectionTitle = "My Newest Work Items"; + + public NewWorkItemsSection() + : base(SectionTitle, false) { } + + protected override IEnumerable GetWorkItems(WorkItemCheckinInfo[] currentlyAssociatedWorkItems) + { + var context = CurrentContext; + if (context == null || !context.HasCollection || !context.HasTeamProject) + return Enumerable.Empty(); + + var store = context.TeamProjectCollection.GetService(); + if (store == null) + return Enumerable.Empty(); + + var workItems = new List(); + + var results = store.Query(@" +Select [ID], [Title] +From WorkItems +Where [System.TeamProject] = '" + context.TeamProjectName + @"' AND [Assigned to] = @Me +Order By [Created Date] desc"); + + foreach (WorkItem workItem in results) + { + if (currentlyAssociatedWorkItems.All(w => w.WorkItem.Id != workItem.Id)) + { + workItems.Add(new WorkItemInfo(workItem.Id, workItem.Title, workItem.Type.Name, workItem.State, "@Me")); + + if (workItems.Count == 5) + break; + } + } + + return workItems; + } + } +} diff --git a/Osiris.AssociateRecentWorkItems/Osiris.AssociateRecentWorkItems.csproj b/Osiris.AssociateRecentWorkItems/Osiris.AssociateRecentWorkItems.csproj index 23a6156..d1843a8 100644 --- a/Osiris.AssociateRecentWorkItems/Osiris.AssociateRecentWorkItems.csproj +++ b/Osiris.AssociateRecentWorkItems/Osiris.AssociateRecentWorkItems.csproj @@ -126,12 +126,16 @@ + + + + True True @@ -139,8 +143,8 @@ - - RecentWorkItemsView.xaml + + WorkItemsView.xaml @@ -184,7 +188,7 @@ - + Designer MSBuild:Compile MSBuild:Compile diff --git a/Osiris.AssociateRecentWorkItems/RecentWorkItemsSection.cs b/Osiris.AssociateRecentWorkItems/RecentWorkItemsSection.cs index d149acb..beb32d1 100644 --- a/Osiris.AssociateRecentWorkItems/RecentWorkItemsSection.cs +++ b/Osiris.AssociateRecentWorkItems/RecentWorkItemsSection.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.ObjectModel; -using System.ComponentModel; +using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Controls; using Microsoft.TeamFoundation.VersionControl.Client; -using Microsoft.TeamFoundation.VersionControl.Controls.Extensibility; -using Microsoft.VisualStudio.TeamFoundation.WorkItemTracking; -using Osiris.TeamExplorer.Extensions.Common; namespace Osiris.AssociateRecentWorkItems { @@ -16,155 +9,42 @@ namespace Osiris.AssociateRecentWorkItems /// Selected file info section. /// [TeamExplorerSection(SectionId, TeamExplorerPageIds.PendingChanges, 35)] - public class RecentWorkItemsSection : TeamExplorerBaseSection + public class RecentWorkItemsSection : WorkItemsSectionBase { public const string SectionId = "A7D7E0F2-6847-439F-834A-7CDB508FEBFA"; private const string SectionTitle = "Recently Associated Work Items"; - private ObservableCollection recentWorkItems = new ObservableCollection(); - /// /// Constructor. /// public RecentWorkItemsSection() - : base() - { - this.Title = SectionTitle; - this.IsExpanded = true; - this.IsBusy = false; - this.SectionContent = new RecentWorkItemsView(); - this.View.ParentSection = this; - } - - /// - /// Get the view. - /// - protected RecentWorkItemsView View - { - get { return this.SectionContent as RecentWorkItemsView; } - } - - /// - /// Initialize override. - /// - public override void Initialize(object sender, SectionInitializeEventArgs e) - { - base.Initialize(sender, e); - - // Find the Pending Changes extensibility service and sign up for - // property change notifications - var pcExt = this.GetService(); - if (pcExt != null) - { - pcExt.PropertyChanged += pcExt_PropertyChanged; - } - - var ds = this.GetService(); - this.View.Context = this.CurrentContext; - this.View.DocumentService = ds; - this.RefreshAsync(); - } + : base(SectionTitle, true) { } - /// - /// Dispose override. - /// - public override void Dispose() + protected override IEnumerable GetWorkItems(WorkItemCheckinInfo[] currentlyAssociatedWorkItems) { - base.Dispose(); + var context = CurrentContext; + if (context == null || !context.HasCollection || !context.HasTeamProject) + return Enumerable.Empty(); - var pcExt = this.GetService(); - if (pcExt != null) - { - pcExt.PropertyChanged -= pcExt_PropertyChanged; - } - } - - /// - /// Pending Changes Extensibility PropertyChanged event handler. - /// - private void pcExt_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case "WorkItems": - Refresh(); - break; - } - } + var vcs = context.TeamProjectCollection.GetService(); + if (vcs == null) + return Enumerable.Empty(); - /// - /// Refresh override. - /// - public async override void Refresh() - { - base.Refresh(); - await RefreshAsync(); - } + var workItems = new List(); - /// - /// Refresh the changeset data asynchronously. - /// - private async Task RefreshAsync() - { - try + string path = "$/" + context.TeamProjectName; + foreach (Changeset changeset in vcs.QueryHistory(path, VersionSpec.Latest, 0, RecursionType.Full, vcs.AuthorizedUser, null, null, 10, false, true)) { - var pc = GetService(); - var currentlyAssociatedWorkItems = pc.WorkItems; - - // Set our busy flag and clear the previous data - this.IsBusy = true; - this.RecentWorkItems.Clear(); - - var workItems = new ObservableCollection(); - - // Make the server call asynchronously to avoid blocking the UI - await Task.Run(() => + foreach (var wi in changeset.AssociatedWorkItems) { - ITeamFoundationContext context = this.CurrentContext; - if (context != null && context.HasCollection && context.HasTeamProject) + if (workItems.All(w => w.Id != wi.Id) && currentlyAssociatedWorkItems.All(w => w.WorkItem.Id != wi.Id)) { - var vcs = context.TeamProjectCollection.GetService(); - if (vcs != null) - { - string path = "$/" + context.TeamProjectName; - foreach (Changeset changeset in vcs.QueryHistory(path, VersionSpec.Latest, 0, RecursionType.Full, - vcs.AuthorizedUser, null, null, 10, false, true)) - { - foreach (var wi in changeset.AssociatedWorkItems) - { - if (workItems.All(w => w.Id != wi.Id) && currentlyAssociatedWorkItems.All(w => w.WorkItem.Id != wi.Id)) - { - workItems.Add(wi); - } - } - } - } + workItems.Add(new WorkItemInfo(wi.Id, wi.Title, wi.WorkItemType, wi.State, wi.AssignedTo)); } - }); - - // Now back on the UI thread, update the bound collection and section title - this.RecentWorkItems = new ObservableCollection(workItems.Take(5)); - this.Title = this.RecentWorkItems.Count > 0 ? String.Format(" {0} ({1})", SectionTitle, this.RecentWorkItems.Count) - : SectionTitle; - } - catch (Exception ex) - { - ShowNotification(ex.Message, NotificationType.Error); - } - finally - { - // Always clear our busy flag when done - this.IsBusy = false; + } } - } - public ObservableCollection RecentWorkItems - { - get { return recentWorkItems; } - protected set { recentWorkItems = value; RaisePropertyChanged("RecentWorkItems"); } + return workItems; } - } - - } diff --git a/Osiris.AssociateRecentWorkItems/WorkItemInfo.cs b/Osiris.AssociateRecentWorkItems/WorkItemInfo.cs new file mode 100644 index 0000000..259b142 --- /dev/null +++ b/Osiris.AssociateRecentWorkItems/WorkItemInfo.cs @@ -0,0 +1,24 @@ +namespace Osiris.AssociateRecentWorkItems +{ + public class WorkItemInfo + { + public int Id { get; private set; } + + public string Title { get; private set; } + + public string WorkItemType { get; private set; } + + public string State { get; private set; } + + public string AssignedTo { get; private set; } + + public WorkItemInfo(int id, string title, string workItemType, string state, string assignedTo) + { + Id = id; + Title = title; + WorkItemType = workItemType; + State = state; + AssignedTo = assignedTo; + } + } +} \ No newline at end of file diff --git a/Osiris.AssociateRecentWorkItems/WorkItemsSectionBase.cs b/Osiris.AssociateRecentWorkItems/WorkItemsSectionBase.cs new file mode 100644 index 0000000..335cc70 --- /dev/null +++ b/Osiris.AssociateRecentWorkItems/WorkItemsSectionBase.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.TeamFoundation.Controls; +using Microsoft.TeamFoundation.VersionControl.Client; +using Microsoft.TeamFoundation.VersionControl.Controls.Extensibility; +using Microsoft.VisualStudio.TeamFoundation.WorkItemTracking; +using Osiris.TeamExplorer.Extensions.Common; + +namespace Osiris.AssociateRecentWorkItems +{ + public abstract class WorkItemsSectionBase : TeamExplorerBaseSection + { + private ObservableCollection recentWorkItems = new ObservableCollection(); + private readonly string baseTitle; + + /// + /// Constructor. + /// + protected WorkItemsSectionBase(string title, bool expanded) + : base() + { + baseTitle = title; + this.Title = baseTitle; + this.IsExpanded = expanded; + this.IsBusy = false; + this.SectionContent = new WorkItemsView(); + this.View.ParentSection = this; + } + + /// + /// Get the view. + /// + protected WorkItemsView View + { + get { return this.SectionContent as WorkItemsView; } + } + + /// + /// Initialize override. + /// + public override void Initialize(object sender, SectionInitializeEventArgs e) + { + base.Initialize(sender, e); + + // Find the Pending Changes extensibility service and sign up for + // property change notifications + var pcExt = this.GetService(); + if (pcExt != null) + { + pcExt.PropertyChanged += pcExt_PropertyChanged; + } + + var ds = this.GetService(); + this.View.Context = this.CurrentContext; + this.View.DocumentService = ds; + this.RefreshAsync(); + } + + /// + /// Dispose override. + /// + public override void Dispose() + { + base.Dispose(); + + var pcExt = this.GetService(); + if (pcExt != null) + { + pcExt.PropertyChanged -= pcExt_PropertyChanged; + } + } + + /// + /// Pending Changes Extensibility PropertyChanged event handler. + /// + private void pcExt_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case "WorkItems": + Refresh(); + break; + } + } + + /// + /// Refresh override. + /// + public async override void Refresh() + { + base.Refresh(); + await RefreshAsync(); + } + + /// + /// Refresh the changeset data asynchronously. + /// + private async Task RefreshAsync() + { + try + { + var pc = GetService(); + var currentlyAssociatedWorkItems = pc.WorkItems; + + // Set our busy flag and clear the previous data + this.IsBusy = true; + this.RecentWorkItems.Clear(); + + WorkItemInfo[] workItems = null; + + // Make the server call asynchronously to avoid blocking the UI + await Task.Run(() => + { + workItems = GetWorkItems(currentlyAssociatedWorkItems).Take(5).ToArray(); + }); + + // Now back on the UI thread, update the bound collection and section title + this.RecentWorkItems = new ObservableCollection(workItems); + this.Title = this.RecentWorkItems.Count > 0 ? String.Format(" {0} ({1})", baseTitle, this.RecentWorkItems.Count) + : baseTitle; + } + catch (Exception ex) + { + ShowNotification(ex.Message, NotificationType.Error); + } + finally + { + // Always clear our busy flag when done + this.IsBusy = false; + } + } + + protected abstract IEnumerable GetWorkItems(WorkItemCheckinInfo[] currentlyAssociatedWorkItems); + + public ObservableCollection RecentWorkItems + { + get { return recentWorkItems; } + protected set { recentWorkItems = value; RaisePropertyChanged("RecentWorkItems"); } + } + } +} diff --git a/Osiris.AssociateRecentWorkItems/RecentWorkItemsView.xaml b/Osiris.AssociateRecentWorkItems/WorkItemsView.xaml similarity index 98% rename from Osiris.AssociateRecentWorkItems/RecentWorkItemsView.xaml rename to Osiris.AssociateRecentWorkItems/WorkItemsView.xaml index 7b6451b..3113194 100644 --- a/Osiris.AssociateRecentWorkItems/RecentWorkItemsView.xaml +++ b/Osiris.AssociateRecentWorkItems/WorkItemsView.xaml @@ -1,4 +1,4 @@ - /// Parent section. /// - public RecentWorkItemsSection ParentSection + public WorkItemsSectionBase ParentSection { - get { return (RecentWorkItemsSection)GetValue(ParentSectionProperty); } + get { return (WorkItemsSectionBase)GetValue(ParentSectionProperty); } set { SetValue(ParentSectionProperty, value); } } @@ -33,7 +32,7 @@ public RecentWorkItemsSection ParentSection public DocumentService DocumentService { get; set; } public static readonly DependencyProperty ParentSectionProperty = - DependencyProperty.Register("ParentSection", typeof(RecentWorkItemsSection), typeof(RecentWorkItemsView)); + DependencyProperty.Register("ParentSection", typeof(WorkItemsSectionBase), typeof(WorkItemsView)); private void workItemList_MouseDoubleClick(object sender, MouseButtonEventArgs e) @@ -47,7 +46,7 @@ private void OnOpenWorkItem(object sender, RoutedEventArgs e) if (item == null) return; - var selectedWorkItem = item as AssociatedWorkItemInfo; + var selectedWorkItem = item as WorkItemInfo; if (selectedWorkItem == null) return; @@ -75,7 +74,7 @@ private void OnAddWorkItem(object sender, RoutedEventArgs e) if (item == null) return; - var selectedWorkItem = item as AssociatedWorkItemInfo; + var selectedWorkItem = item as WorkItemInfo; if (selectedWorkItem == null) return;