/// <summary> /// Получить все <see cref="TeamSlice"/> для конкретного проекта с момента последней проверки. /// </summary> /// <param name="project"> /// Проект для которого необходимо получить все <see cref="TeamSlice"/>. /// </param> /// <returns> /// Все <see cref="TeamSlice"/> для указанного проекта с момента последней проверки. /// </returns> private List <TeamSlice> GetTeamSlices(TFSProject project) { ExternalLangDef languageDef = ExternalLangDef.LanguageDef; LoadingCustomizationStruct lcs = LoadingCustomizationStruct.GetSimpleStruct(typeof(TeamSlice), TeamSlice.Views.TeamSliceE); VariableDef startDate = new VariableDef(languageDef.DateTimeType, Information.ExtractPropertyName <TeamSlice>(n => n.StartDate)); VariableDef endDate = new VariableDef(languageDef.DateTimeType, Information.ExtractPropertyName <TeamSlice>(n => n.EndDate)); // Условие: либо посленяя дата проверки или текущая дата попадают в дипазон дат среза команды, либо диапазон дат среза команды содержится между ними. Function dateLimit = languageDef.GetFunction( languageDef.funcOR, languageDef.GetFunction(languageDef.funcBETWEEN, project.LastCheckDate, startDate, endDate), languageDef.GetFunction(languageDef.funcBETWEEN, _now, startDate, endDate), languageDef.GetFunction( languageDef.funcAND, languageDef.GetFunction(languageDef.funcL, endDate, _now), languageDef.GetFunction(languageDef.funcG, startDate, project.LastCheckDate))); string agregatorName = Information.ExtractPropertyName <TFSProjectForReview>(n => n.Team); string detailPath = Information.ExtractPropertyPath <TeamSlice>(n => n.Team); Function projectLimit = languageDef.GetFunction( languageDef.funcExist, new DetailVariableDef(languageDef.DetailsType, agregatorName, TFSProjectForReview.Views.TFSProjectForReviewE, agregatorName, detailPath), languageDef.GetFunction( languageDef.funcEQ, new VariableDef(languageDef.GuidType, Information.ExtractPropertyName <TFSProjectForReview>(n => n.TFSProject)), project.__PrimaryKey)); lcs.LimitFunction = languageDef.GetFunction(languageDef.funcAND, dateLimit, projectLimit); return(DataServiceProvider.DataService.LoadObjects(lcs).Cast <TeamSlice>().ToList()); }
/// <summary> /// Обработать конкретный проект из TFS. /// Выполняет генерацию задач на проверку кода для указанного прокта. /// </summary> /// <param name="tfsProject">Проект из TFS для которого необходимо сгенерировать задачи на проверку кода.</param> private void ProcessProject(TFSProject tfsProject) { // В проекте необходимо указывать последнию дату проверки иначе она будет проставлена в текущий день. if (tfsProject.LastCheckDate == null) { tfsProject.LastCheckDate = new NullableDateTime { Value = _now } } ; List <TeamSlice> teamSlices = GetTeamSlices(tfsProject); // Если нет команды в текущий период времени, то и CheckIn'ы нет смысла перебирать. if (teamSlices.Count == 0) { return; } string projectName = tfsProject.GetProjectName(); string tfsServerUrl = tfsProject.GetProjectCollectionUrl(); var projectCollection = new TfsTeamProjectCollection(new Uri(tfsServerUrl)); projectCollection.EnsureAuthenticated(); // Получаем changeset'ы, начиная с даты последней проверки по настоящий момент. IEnumerable <Changeset> changesets = GetChangesets(projectCollection, projectName, tfsProject.LastCheckDate.Value, _now); if (projectName.Contains('/')) { projectName = projectName.Split('/')[0]; } // Сервис для работы с пользователями tfs (будем получать пользователя по его логину полному). IIdentityManagementService identityManagementService = projectCollection.GetService <IIdentityManagementService>(); foreach (Changeset changeset in changesets) { var creationDate = changeset.CreationDate; var teamSlice = teamSlices.FirstOrDefault(n => n.StartDate <= creationDate && n.EndDate >= creationDate); if (teamSlice == null) { throw new InvalidOperationException("Невозможно подобрать команду под указанный диапазон дат."); } var identity = identityManagementService.ReadIdentity( IdentitySearchFactor.AccountName, changeset.Committer, MembershipQuery.None, ReadIdentityOptions.None); if (identity == null) { throw new InvalidOperationException( string.Format("Не удалось получить сведения по логину {0}.", changeset.Committer)); } /* Проведение имперсонализации: * создаём review request от имени того, кто коммитил (это позволит потом этому человеку закрыть review request). * Чтобы всё корректно проходило, у пользователя, от имени которого работает данный сервис, * должно быть разрешение “Make requests on behalf of others”. */ var impersonatedCollection = new TfsTeamProjectCollection(projectCollection.Uri, identity.Descriptor); impersonatedCollection.EnsureAuthenticated(); WorkItemStore impersonatedWorkitemStore = impersonatedCollection.GetService <WorkItemStore>(); Project impersonatedProject = impersonatedWorkitemStore.Projects[projectName]; // Программист, которому будет назначен чекин на проверку. TFSProgrammer programmer = GetRandomProgrammer(teamSlice, changeset.CommitterDisplayName); if (programmer == null) { throw new InvalidOperationException("Команда в подобранный диапазон дат вовсе не содержит программистов или содержит только автора набора изменений."); } var responseWorkItem = new WorkItem(impersonatedProject.WorkItemTypes[TFSCommonCollectionContsntans.ResponseWorkItemType]) { Title = CutWorkitemTitle(changeset.Comment) }; responseWorkItem.Fields["System.AssignedTo"].Value = programmer.Name; responseWorkItem.Fields["System.State"].Value = TFSCommonCollectionContsntans.StateWorkItemCreation; responseWorkItem.Fields["System.Reason"].Value = ReasonWorkItemCreation; responseWorkItem.Fields["System.AreaPath"].Value = tfsProject.WorkItemAreaPath ?? projectName; responseWorkItem.Fields["System.IterationPath"].Value = tfsProject.WorkItemIterationPath ?? projectName; SaveWorkItem(responseWorkItem, changeset.ChangesetId); var requestWorkItem = new WorkItem(impersonatedProject.WorkItemTypes[TFSCommonCollectionContsntans.RequestWorkItemType]) { Title = CutWorkitemTitle(changeset.Comment) }; requestWorkItem.Fields["System.AssignedTo"].Value = changeset.CommitterDisplayName; requestWorkItem.Fields["Microsoft.VSTS.CodeReview.ContextType"].Value = WorkItemContextTypeForChangeset; requestWorkItem.Fields["Microsoft.VSTS.CodeReview.Context"].Value = changeset.ChangesetId; requestWorkItem.Fields["System.AreaPath"].Value = tfsProject.WorkItemAreaPath ?? projectName; requestWorkItem.Fields["System.IterationPath"].Value = tfsProject.WorkItemIterationPath ?? projectName; requestWorkItem.Fields["System.State"].Value = TFSCommonCollectionContsntans.StateWorkItemCreation; requestWorkItem.Fields["System.Reason"].Value = ReasonWorkItemCreation; WorkItemLinkTypeEnd linkTypeEnd = impersonatedWorkitemStore.WorkItemLinkTypes.LinkTypeEnds[LinkChildWorkItem]; requestWorkItem.Links.Add(new RelatedLink(linkTypeEnd, responseWorkItem.Id)); SaveWorkItem(requestWorkItem, changeset.ChangesetId); // Актуализируем дату последней проверки проекта, проставляю в неё время последнего проверенного набора изменений. tfsProject.LastCheckDate = new NullableDateTime { Value = changeset.CreationDate }; } // В конце успешной проверки всех наборов изменений проставляем время последней проверки в текущее. tfsProject.LastCheckDate = new NullableDateTime { Value = _now }; }