/// <summary> /// Sorts the work items that were in the last run of a query. /// </summary> /// <remarks> /// Takes account of the physical order of the bookmarks in the document. /// </remarks> /// <param name="queryWorkItemsBefore">The query to work item association data from before the refresh.</param> /// <param name="queryWorkItemsAfter">The query to work item association data as a result of the refresh.</param> /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, given the work item id.</param> /// <param name="cancellationToken">Used to cancel the refresh.</param> private void SortExistingWorkItems(QueryWorkItems queryWorkItemsBefore, QueryWorkItems queryWorkItemsAfter, Func <int, string> bookmarkNamingFunction, CancellationToken cancellationToken) { this.logger.Log(TraceEventType.Verbose, "Sorting existing work items"); cancellationToken.ThrowIfCancellationRequested(); int[] currentIds = queryWorkItemsBefore.WorkItemIds.OrderBy((int id) => this.FindBookmark(bookmarkNamingFunction(id)).Start) .ToArray(); // sort into physical order int[] afterIds = queryWorkItemsAfter.WorkItemIds.Intersect(currentIds).ToArray(); int afterIdsLen = afterIds.Length; int currentIdsLen = currentIds.Length; for (int i = 0; i < afterIdsLen - 1; i++) { if (afterIds[i] != currentIds[i]) { this.wordDocument.MoveBookmarkAndContentToBefore(bookmarkNamingFunction(afterIds[i]), bookmarkNamingFunction(currentIds[i])); int oldPosOfMovedItem = -1; for (int k = i + 1; k < currentIdsLen; k++) { if (currentIds[k] == afterIds[i]) { oldPosOfMovedItem = k; break; } } for (int j = oldPosOfMovedItem; j > i; j--) { currentIds[j] = currentIds[j - 1]; } currentIds[i] = afterIds[i]; } } }
/// <summary> /// Adds the new work items that were not in the last run of a query. /// </summary> /// <remarks> /// If the current work items have been manually sorted then new items are just added to the end, otherwise they are added in the new sort order. /// </remarks> /// <param name="queryWorkItemsBefore">The query to work item association data from before the refresh.</param> /// <param name="queryWorkItemsAfter">The query to work item association data as a result of the refresh.</param> /// <param name="workItemLayout">The work item layout associated with the query.</param> /// <param name="customXMLPart">The custom XML part that contains the work item data.</param> /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, given the work item id.</param> /// <param name="isManuallySorted">Indicates if the current work items are manually sorted.</param> /// <param name="cancellationToken">Used to cancel the refresh.</param> private void AddNewWorkItems(QueryWorkItems queryWorkItemsBefore, QueryWorkItems queryWorkItemsAfter, IWorkItemLayout workItemLayout, CustomXMLPart customXMLPart, Func <int, string> bookmarkNamingFunction, bool isManuallySorted, CancellationToken cancellationToken) { this.logger.Log(TraceEventType.Verbose, "Adding new work items"); cancellationToken.ThrowIfCancellationRequested(); int[] newIds = queryWorkItemsAfter.WorkItemIds.Except(queryWorkItemsBefore.WorkItemIds).ToArray(); if (newIds.Length > 0) { // We have some new work items that were not present last time the query was executed. int[] beforeIds = queryWorkItemsBefore.WorkItemIds.ToArray(); int[] afterIds = queryWorkItemsAfter.WorkItemIds.ToArray(); WorkItemTreeNode[] nodes = queryWorkItemsAfter.WorkItemTreeNodes.ToArray(); Debug.Assert(afterIds.Length == nodes.Length, "Number of after nodes must match number of after ids."); if (isManuallySorted) { this.AddAllNewWorkItemsAfterLastExisting(newIds, this.GetLastPhysicalWorkItem(beforeIds, bookmarkNamingFunction), nodes, workItemLayout, customXMLPart, bookmarkNamingFunction); } else if (afterIds.Intersect(beforeIds).Count() > 0) { this.AddNewWorkItemsBeforeExisting(newIds, beforeIds, afterIds, nodes, workItemLayout, customXMLPart, (int id) => bookmarkNamingFunction(id)); this.AddNewWorkItemsAfterExisting(newIds, beforeIds, afterIds, nodes, workItemLayout, customXMLPart, (int id) => bookmarkNamingFunction(id)); } else { this.AddNewWorkItemsWhenNoneBefore(afterIds, nodes, workItemLayout, customXMLPart, (int id) => bookmarkNamingFunction(id)); } } }
/// <summary> /// Refreshes the work items in the document. /// </summary> /// <remarks> /// <para>Deletes work items that are no longer returned, adds work items that are now returned, and re-orders work items that have changed position in query results.</para> /// <para>This call will also update the rich text content controls which are not bound to the Custom XML Parts.</para> /// </remarks> /// <param name="refreshData">The data needed for a refresh.</param> /// <param name="bookmarkNamingFunction">A function to return the bookmark to use for the given work item, first parameter is query index, second is work item id.</param> /// <param name="cancellationToken">Used to cancel the refresh.</param> public void RefreshWorkItems(FormatterRefreshData refreshData, Func <int, int, string> bookmarkNamingFunction, CancellationToken cancellationToken) { if (refreshData == null) { throw new ArgumentNullException("refreshData"); } Debug.Assert(refreshData.QueryWorkItemsBefore.Count() == refreshData.QueryWorkItemsAfter.Count(), "Before and after data have different numbers of queries"); Debug.Assert(refreshData.QueryWorkItemsBefore.Count() == refreshData.Layouts.Count(), "Number of layouts does not match number of queries"); Debug.Assert(refreshData.QueryWorkItemsBefore.Count() == refreshData.QueryIsFlat.Count(), "Number of QueryIsFlat values does not match number of queries"); DateTime start = DateTime.Now; CustomXMLPart part = this.wordDocument.GetXmlPart(Constants.WorkItemNamespace); int n = refreshData.QueryWorkItemsBefore.Count(); QueryWorkItems[] queryWorkItemsBefore = refreshData.QueryWorkItemsBefore.ToArray(); QueryWorkItems[] queryWorkItemsAfter = refreshData.QueryWorkItemsAfter.ToArray(); bool[] queryIsFlat = refreshData.QueryIsFlat.ToArray(); LayoutInformation[] layouts = refreshData.Layouts.ToArray(); for (int queryIndex = 0; queryIndex < n; queryIndex++) { Func <int, string> queryBookmarkNamingFunction = (int id) => bookmarkNamingFunction(queryIndex, id); this.logger.Log(TraceEventType.Verbose, "Refreshing query {0}", queryIndex); // Compute the actual before work items that are still in the document. QueryWorkItems actualBeforeWorkItems = new QueryWorkItems(queryIndex, queryWorkItemsBefore[queryIndex].WorkItemIds.Where(id => this.FindBookmark(queryBookmarkNamingFunction(id)) != null).ToArray()); bool isManuallySorted = this.IsManuallySorted(queryWorkItemsBefore[queryIndex].WorkItemIds, queryBookmarkNamingFunction); if (!queryIsFlat[queryIndex] || (queryIsFlat[queryIndex] && !isManuallySorted)) { this.SortExistingWorkItems(actualBeforeWorkItems, queryWorkItemsAfter[queryIndex], queryBookmarkNamingFunction, cancellationToken); isManuallySorted = false; } IWorkItemLayout workItemLayout = new WorkItemLayout(layouts[queryIndex], this.teamProjectTemplate); this.AddNewWorkItems(actualBeforeWorkItems, queryWorkItemsAfter[queryIndex], workItemLayout, part, queryBookmarkNamingFunction, isManuallySorted, cancellationToken); } this.RefreshDeleteRemovedWorkItems(refreshData.QueryWorkItemsBefore.ToArray(), refreshData.QueryWorkItemsAfter.ToArray(), bookmarkNamingFunction, cancellationToken); this.RefreshRichTextContentControls(refreshData.WorkItemManager, cancellationToken); DateTime end = DateTime.Now; this.logger.Log(TraceEventType.Information, "Elapsed time to refresh was {0} seconds", (end - start).TotalSeconds); }
/// <summary> /// Loads the work items associated with a query. /// </summary> /// <param name="index">The query index.</param> /// <param name="xml">The XML for the work item ids for a particular query.</param> /// <returns>A <see cref="QueryWorkItems"/> object for the query and its associated work items.</returns> private QueryWorkItems LoadWorkItemsForQuery(int index, XElement xml) { QueryWorkItems ans = new QueryWorkItems(index, xml.Descendants(this.queryWorkItemAssociationIdXName).Select(e => int.Parse(e.Attribute("Id").Value, CultureInfo.InvariantCulture)).ToArray()); return(ans); }