From 3cf6d5475d8f64e4629d097e0d4a9c2c784590b6 Mon Sep 17 00:00:00 2001 From: Gary Sharp Date: Tue, 22 Apr 2014 13:55:46 +1000 Subject: [PATCH] Bug Fixes: enrolment, assignment and search order --- Disco.BI/BI/DeviceBI/Enrol.cs | 13 +- Disco.BI/BI/DeviceBI/Importing/Import.cs | 10 +- Disco.BI/BI/Extensions/DeviceExtensions.cs | 3 + .../Searching/DeviceSearchResultItem.cs | 31 ++++- .../Services/Searching/ISearchResultItem.cs | 2 +- .../Services/Searching/JobSearchResultItem.cs | 29 ++++- .../Searching/UserSearchResultItem.cs | 17 ++- Disco.Services/Extensions/StringExtensions.cs | 15 +++ .../ADTaskUpdateNetworkLogonDates.cs | 2 +- .../Jobs/JobLists/JobTableExtensions.cs | 11 +- Disco.Services/Searching/Search.cs | 113 ++++++++++++------ Disco.Services/Users/UserService.cs | 25 ++-- .../Areas/API/Controllers/DeviceController.cs | 13 +- .../Controllers/DocumentTemplateController.cs | 5 +- .../Areas/API/Controllers/SearchController.cs | 24 +++- .../Areas/API/Controllers/UserController.cs | 6 - Disco.Web/Controllers/SearchController.cs | 16 +-- Disco.Web/T4MVC.cs | 62 +++++----- Disco.Web/Views/Device/AddOffline.cshtml | 2 +- .../Views/Device/AddOffline.generated.cs | 4 +- .../Views/Device/DeviceParts/_Subject.cshtml | 4 +- .../Device/DeviceParts/_Subject.generated.cs | 30 ++--- 22 files changed, 310 insertions(+), 127 deletions(-) diff --git a/Disco.BI/BI/DeviceBI/Enrol.cs b/Disco.BI/BI/DeviceBI/Enrol.cs index c0b83200..4b1f56a0 100644 --- a/Disco.BI/BI/DeviceBI/Enrol.cs +++ b/Disco.BI/BI/DeviceBI/Enrol.cs @@ -362,7 +362,11 @@ namespace Disco.BI.DeviceBI { if (!authenticatedToken.Has(Claims.ComputerAccount)) throw new EnrolSafeException(string.Format("Connection not correctly authenticated (SN: {0}; Auth User: {1})", Request.DeviceSerialNumber, authenticatedToken.User.UserId)); - if (!authenticatedToken.User.UserId.Equals(string.Format("{0}$", Request.DeviceComputerName), System.StringComparison.OrdinalIgnoreCase)) + + if (domain == null) + domain = ActiveDirectory.Context.GetDomainByName(Request.DeviceDNSDomainName); + + if (!authenticatedToken.User.UserId.Equals(string.Format(@"{0}\{1}$", domain.NetBiosName, Request.DeviceComputerName), System.StringComparison.OrdinalIgnoreCase)) throw new EnrolSafeException(string.Format("Connection not correctly authenticated (SN: {0}; Auth User: {1})", Request.DeviceSerialNumber, authenticatedToken.User.UserId)); } } @@ -418,10 +422,13 @@ namespace Disco.BI.DeviceBI else EnrolmentLog.LogSessionDevice(sessionId, Request.DeviceSerialNumber, deviceModel.Id); + if (domain == null) + domain = ActiveDirectory.Context.GetDomainByName(Request.DeviceDNSDomainName); + RepoDevice = new Device { SerialNumber = Request.DeviceSerialNumber, - DeviceDomainId = Request.DeviceComputerName, + DeviceDomainId = string.Format(@"{0}\{1}", domain.NetBiosName, Request.DeviceComputerName), DeviceProfile = deviceProfile, DeviceModel = deviceModel, AllowUnauthenticatedEnrol = false, @@ -507,7 +514,7 @@ namespace Disco.BI.DeviceBI } else { - RepoDevice.DeviceDomainId = adMachineAccount.Name; + RepoDevice.DeviceDomainId = adMachineAccount.Id.Trim('$'); response.DeviceComputerName = adMachineAccount.Name; response.DeviceDomainName = adMachineAccount.Domain.NetBiosName; diff --git a/Disco.BI/BI/DeviceBI/Importing/Import.cs b/Disco.BI/BI/DeviceBI/Importing/Import.cs index 28bb1994..7b4b129c 100644 --- a/Disco.BI/BI/DeviceBI/Importing/Import.cs +++ b/Disco.BI/BI/DeviceBI/Importing/Import.cs @@ -2,6 +2,7 @@ using Disco.Data.Repository; using Disco.Models.BI.Device; using Disco.Models.Repository; +using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Users; using System; using System.Collections.Generic; @@ -207,8 +208,13 @@ namespace Disco.BI.DeviceBI.Importing csvAssignedUserId = record[4]; if (string.IsNullOrWhiteSpace(csvAssignedUserId)) csvAssignedUserId = null; // Not Assigned - else if (csvAssignedUserId.Length > 50) - errors.Add("AssignedUserId", "The assigned user must be less than or equal to 50 characters"); + else + { + if (csvAssignedUserId.Length > 50) + errors.Add("AssignedUserId", "The assigned user must be less than or equal to 50 characters"); + else if (!csvAssignedUserId.Contains('\\')) // Assume Primary Domain + csvAssignedUserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, csvAssignedUserId); + } if (csvFieldCount > 5) { diff --git a/Disco.BI/BI/Extensions/DeviceExtensions.cs b/Disco.BI/BI/Extensions/DeviceExtensions.cs index 0c2d6aee..fb4574ff 100644 --- a/Disco.BI/BI/Extensions/DeviceExtensions.cs +++ b/Disco.BI/BI/Extensions/DeviceExtensions.cs @@ -142,6 +142,9 @@ namespace Disco.BI.Extensions Database.Devices.Add(d2); if (!string.IsNullOrEmpty(d.AssignedUserId)) { + if (!d.AssignedUserId.Contains('\\')) + d.AssignedUserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, d.AssignedUserId); + User u = UserService.GetUser(d.AssignedUserId, Database, true); d2.AssignDevice(Database, u); } diff --git a/Disco.Models/Services/Searching/DeviceSearchResultItem.cs b/Disco.Models/Services/Searching/DeviceSearchResultItem.cs index 0ccf2016..d947243b 100644 --- a/Disco.Models/Services/Searching/DeviceSearchResultItem.cs +++ b/Disco.Models/Services/Searching/DeviceSearchResultItem.cs @@ -9,11 +9,17 @@ namespace Disco.Models.Services.Searching public class DeviceSearchResultItem : ISearchResultItem { private const string type = "Device"; + private Lazy LazyScoreValue; + + public DeviceSearchResultItem() + { + this.LazyScoreValue = new Lazy(BuildScoreValues, false); + } public string Id { get; set; } public string Type { get { return type; } } public string Description { get { return string.Format("{0} ({1})", this.Id, this.ComputerName); } } - public string ScoreValue { get { return string.Format("{0} {1} {2} {3} {4}", this.Id, this.AssignedUserId != null ? this.AssignedUserId.Substring(0, this.AssignedUserId.IndexOf('\\')) : null, this.AssignedUserId, this.AssignedUserDisplayName, this.AssetNumber); } } + public string[] ScoreValues { get { return LazyScoreValue.Value; } } public string AssetNumber { get; set; } public string AssignedUserDescription @@ -37,5 +43,28 @@ namespace Disco.Models.Services.Searching public string DeviceProfileDescription { get; set; } public int JobCount { get; set; } public DateTime? DecommissionedDate { get; set; } + + private string[] BuildScoreValues() + { + if (this.AssignedUserId == null) + { + return new string[] { + this.Id, + this.AssetNumber, + this.ComputerName + }; + } + else + { + return new string[] { + this.Id, + this.AssetNumber, + this.ComputerName, + this.AssignedUserId.Substring(this.AssignedUserId.IndexOf('\\') + 1), + this.AssignedUserId, + this.AssignedUserDisplayName + }; + } + } } } diff --git a/Disco.Models/Services/Searching/ISearchResultItem.cs b/Disco.Models/Services/Searching/ISearchResultItem.cs index d42da955..41e5127b 100644 --- a/Disco.Models/Services/Searching/ISearchResultItem.cs +++ b/Disco.Models/Services/Searching/ISearchResultItem.cs @@ -11,6 +11,6 @@ namespace Disco.Models.Services.Searching string Id { get; set; } string Type { get; } string Description { get; } - string ScoreValue { get; } + string[] ScoreValues { get; } } } diff --git a/Disco.Models/Services/Searching/JobSearchResultItem.cs b/Disco.Models/Services/Searching/JobSearchResultItem.cs index 4cf96f4a..16ad600c 100644 --- a/Disco.Models/Services/Searching/JobSearchResultItem.cs +++ b/Disco.Models/Services/Searching/JobSearchResultItem.cs @@ -9,16 +9,43 @@ namespace Disco.Models.Services.Searching public class JobSearchResultItem : ISearchResultItem { private const string type = "Job"; + private Lazy LazyScoreValue; + + public JobSearchResultItem() + { + this.LazyScoreValue = new Lazy(BuildScoreValues, false); + } public virtual string Id { get; set; } public string Type { get { return type; } } public string Description { get { return string.Format("{0} ({1}; {2})", this.Id, this.UserId, this.DeviceSerialNumber); } } - public string ScoreValue { get { return string.Format("{0} {1} {2} {3} {4}", this.Id, this.UserId.Substring(0, this.UserId.IndexOf('\\')), this.UserId, this.DeviceSerialNumber, this.UserDisplayName); } } + public string[] ScoreValues { get { return LazyScoreValue.Value; } } public string DeviceSerialNumber { get; set; } public string UserId { get; set; } public string UserFriendlyId { get; set; } public string UserDisplayName { get; set; } + + private string[] BuildScoreValues() + { + if (this.UserId == null) + { + return new string[] { + this.Id, + this.DeviceSerialNumber + }; + } + else + { + return new string[] { + this.Id, + this.UserId.Substring(this.UserId.IndexOf('\\') + 1), + this.UserId, + this.UserDisplayName, + this.DeviceSerialNumber + }; + } + } } } diff --git a/Disco.Models/Services/Searching/UserSearchResultItem.cs b/Disco.Models/Services/Searching/UserSearchResultItem.cs index 05843f81..5fab3b33 100644 --- a/Disco.Models/Services/Searching/UserSearchResultItem.cs +++ b/Disco.Models/Services/Searching/UserSearchResultItem.cs @@ -9,16 +9,31 @@ namespace Disco.Models.Services.Searching public class UserSearchResultItem : ISearchResultItem { private const string type = "User"; + private Lazy LazyScoreValue; + + public UserSearchResultItem() + { + this.LazyScoreValue = new Lazy(BuildScoreValues, false); + } public string Id { get; set; } public string Type { get { return type; } } public string Description { get { return string.Format("{0} ({1})", this.DisplayName, this.Id); } } - public string ScoreValue { get { return string.Format("{0} {1} {2}", this.Id.Substring(0, this.Id.IndexOf('\\')), this.Id, this.DisplayName); } } + public string[] ScoreValues { get { return LazyScoreValue.Value; } } public int AssignedDevicesCount { get; set; } public string DisplayName { get; set; } public string GivenName { get; set; } public int JobCount { get; set; } public string Surname { get; set; } + + private string[] BuildScoreValues() + { + return new string[] { + this.Id.Substring(this.Id.IndexOf('\\') + 1), + this.Id, + this.DisplayName + }; + } } } diff --git a/Disco.Services/Extensions/StringExtensions.cs b/Disco.Services/Extensions/StringExtensions.cs index 34c000cf..98ea1b5a 100644 --- a/Disco.Services/Extensions/StringExtensions.cs +++ b/Disco.Services/Extensions/StringExtensions.cs @@ -103,5 +103,20 @@ namespace Disco return final_score; } + /// + /// A fuzzy string search algorithm. + /// + /// Based on: ScoreSharp (https://github.com/bltavares/scoresharp) + /// Based on: string_score from Joshaven Potter (https://github.com/joshaven/string_score) + /// + /// MIT License + /// + public static double Score(this IEnumerable Sources, string Test, double Fuzziness = 0) + { + return Sources + .Where(s => s != null) + .Select(s => s.Score(Test, Fuzziness)) + .Average(); + } } } diff --git a/Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs b/Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs index 2a368fae..e96602b5 100644 --- a/Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs +++ b/Disco.Services/Interop/ActiveDirectory/ADTaskUpdateNetworkLogonDates.cs @@ -65,7 +65,7 @@ namespace Disco.Services.Interop.ActiveDirectory System.DateTime? lastLogon = null; - if (!string.IsNullOrEmpty(Device.DeviceDomainId)) + if (!string.IsNullOrEmpty(Device.DeviceDomainId) && Device.DeviceDomainId.Contains('\\')) { var context = ActiveDirectory.Context; var deviceSamAccountName = UserExtensions.SplitUserId(Device.DeviceDomainId).Item2 + "$"; diff --git a/Disco.Services/Jobs/JobLists/JobTableExtensions.cs b/Disco.Services/Jobs/JobLists/JobTableExtensions.cs index 57a16d58..b2939a47 100644 --- a/Disco.Services/Jobs/JobLists/JobTableExtensions.cs +++ b/Disco.Services/Jobs/JobLists/JobTableExtensions.cs @@ -243,9 +243,18 @@ namespace Disco.Services return items; } - public static void Fill(this JobTableModel model, DiscoDataContext Database, IQueryable Jobs, bool FilterAuthorization) + public static JobTableModel Fill(this JobTableModel model, DiscoDataContext Database, IQueryable Jobs, bool FilterAuthorization) { model.Items = model.DetermineItems(Database, Jobs, FilterAuthorization); + + return model; + } + + public static JobTableModel Score(this JobTableModel model, string Test, double Fuzziness = 0) + { + model.Items = model.Items.OrderByDescending(i => i.ScoreValues.Score(Test)).ToList(); + + return model; } public static double? SlaPrecentageRemaining(this IEnumerable queueItems) diff --git a/Disco.Services/Searching/Search.cs b/Disco.Services/Searching/Search.cs index 7b54bf86..8158ed2f 100644 --- a/Disco.Services/Searching/Search.cs +++ b/Disco.Services/Searching/Search.cs @@ -16,7 +16,7 @@ namespace Disco.Services.Searching { #region Jobs - public static List SearchJobs(DiscoDataContext Database, string Term, int? LimitCount = null) + public static List SearchJobs(DiscoDataContext Database, string Term, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { int termInt = default(int); @@ -56,10 +56,10 @@ namespace Disco.Services.Searching DeviceSerialNumber = i.DeviceSerialNumber, UserId = i.UserId, UserDisplayName = i.UserDisplayName - }).ToList(); + }).OrderByDescending(i => i.ScoreValues.Score(Term)).ToList(); } - public static JobTableModel SearchJobsTable(DiscoDataContext Database, string Term, int? LimitCount = null, bool IncludeJobStatus = true, bool SearchDetails = false) + public static JobTableModel SearchJobsTable(DiscoDataContext Database, string Term, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit, bool IncludeJobStatus = true, bool SearchDetails = false) { int termInt = default(int); @@ -126,8 +126,9 @@ namespace Disco.Services.Searching if (LimitCount.HasValue) query = query.Take(LimitCount.Value); - JobTableModel model = new JobTableModel() { ShowStatus = IncludeJobStatus }; - model.Fill(Database, query, true); + JobTableModel model = new JobTableModel() { ShowStatus = IncludeJobStatus } + .Fill(Database, query, true) + .Score(Term); return model; } @@ -139,48 +140,85 @@ namespace Disco.Services.Searching #endregion #region Users - public static List SearchUsers(DiscoDataContext Database, string Term, int? LimitCount = null) + public static List SearchUsersUpstream(DiscoDataContext Database, string Term, bool PersistResults, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { - if (string.IsNullOrWhiteSpace(Term) || Term.Length < 2) - throw new ArgumentException("Search Term must contain at least two characters", "Term"); - - // Search Active Directory & Import Relevant Users - UserService.SearchUsers(Database, Term); - - var matches = Database.Users.Where(u => - u.UserId.Contains(Term) || - u.Surname.Contains(Term) || - u.GivenName.Contains(Term) || - u.DisplayName.Contains(Term) - ); - - if (LimitCount.HasValue) - matches = matches.Take(LimitCount.Value); - - return matches.Select(u => new UserSearchResultItem() + return UserService.SearchUsers(Database, Term, PersistResults, LimitCount).Select(u => new UserSearchResultItem() { Id = u.UserId, Surname = u.Surname, GivenName = u.GivenName, DisplayName = u.DisplayName, - AssignedDevicesCount = u.DeviceUserAssignments.Where(dua => !dua.UnassignedDate.HasValue).Count(), - JobCount = u.Jobs.Count() - }).ToList(); + AssignedDevicesCount = 0, + JobCount = 0 + }).OrderByDescending(i => i.ScoreValues.Score(Term)).ToList(); } - public static List SearchUsersUpstream(string Term, int? LimitCount = null) + public static List SearchUsers(DiscoDataContext Database, string Term, bool PersistResults, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { - IEnumerable matches = ActiveDirectory.SearchADUserAccounts(Term, Quick: true); + if (string.IsNullOrWhiteSpace(Term) || Term.Length < 2) + throw new ArgumentException("Search Term must contain at least two characters", "Term"); + + // Search Active Directory + var adResults = SearchUsersUpstream(Database, Term, PersistResults, LimitCount); + + // Search Database + var dbResults = Database.Users.Where(u => + u.UserId.Contains(Term) || + u.Surname.Contains(Term) || + u.GivenName.Contains(Term) || + u.DisplayName.Contains(Term) + ).Select(u => new UserSearchResultItem() + { + Id = u.UserId, + Surname = u.Surname, + GivenName = u.GivenName, + DisplayName = u.DisplayName, + AssignedDevicesCount = u.DeviceUserAssignments.Where(dua => !dua.UnassignedDate.HasValue).Count(), + JobCount = u.Jobs.Count() + }).ToList(); + + + IEnumerable results; + if (PersistResults) + { + // AD Search persisted the results to the database + results = dbResults; + } + else + { + var adResultsIndexed = adResults.ToDictionary(u => u.Id, StringComparer.OrdinalIgnoreCase); + var dbResultsIndexed = dbResults.ToDictionary(u => u.Id, StringComparer.OrdinalIgnoreCase); + + // Update DB Results + dbResults.ForEach(u => + { + UserSearchResultItem adResult; + if (adResultsIndexed.TryGetValue(u.Id, out adResult)) + { + u.Surname = adResult.Surname; + u.GivenName = adResult.GivenName; + u.DisplayName = adResult.DisplayName; + } + }); + + // Add AD Results + var adResultsAdditional = adResults.Where(u => !dbResultsIndexed.ContainsKey(u.Id)); + + // Join AD & DB Results + results = adResultsAdditional.Concat(dbResults); + } + + results = results.OrderByDescending(i => i.ScoreValues.Score(Term)); if (LimitCount.HasValue) - matches = matches.Take(LimitCount.Value); + results = results.Take(LimitCount.Value); - return matches.Select(m => m.ToRepositoryUser()).ToList(); + return results.ToList(); } #endregion #region Devices - public static List SearchDevices(DiscoDataContext Database, string Term, int? LimitCount = null, bool SearchDetails = false) + public static List SearchDevices(DiscoDataContext Database, string Term, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit, bool SearchDetails = false) { IQueryable query; @@ -207,23 +245,26 @@ namespace Disco.Services.Searching Term.Contains(d.SerialNumber)); } - return query.ToDeviceSearchResultItems(LimitCount); + return query + .ToDeviceSearchResultItems(LimitCount) + .OrderByDescending(i => i.ScoreValues.Score(Term)) + .ToList(); } - public static List SearchDeviceModel(DiscoDataContext Database, int DeviceModelId, int? LimitCount = null) + public static List SearchDeviceModel(DiscoDataContext Database, int DeviceModelId, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { return Database.Devices.Where(d => d.DeviceModelId == DeviceModelId).ToDeviceSearchResultItems(LimitCount); } - public static List SearchDeviceProfile(DiscoDataContext Database, int DeviceProfileId, int? LimitCount = null) + public static List SearchDeviceProfile(DiscoDataContext Database, int DeviceProfileId, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { return Database.Devices.Where(d => d.DeviceProfileId == DeviceProfileId).ToDeviceSearchResultItems(LimitCount); } - public static List SearchDeviceBatch(DiscoDataContext Database, int DeviceBatchId, int? LimitCount = null) + public static List SearchDeviceBatch(DiscoDataContext Database, int DeviceBatchId, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { return Database.Devices.Where(d => d.DeviceBatchId == DeviceBatchId).ToDeviceSearchResultItems(LimitCount); } - private static List ToDeviceSearchResultItems(this IQueryable Query, int? LimitCount = null) + private static List ToDeviceSearchResultItems(this IQueryable Query, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { if (LimitCount.HasValue) Query = Query.Take(LimitCount.Value); diff --git a/Disco.Services/Users/UserService.cs b/Disco.Services/Users/UserService.cs index aa43b4c6..9cacad0f 100644 --- a/Disco.Services/Users/UserService.cs +++ b/Disco.Services/Users/UserService.cs @@ -205,19 +205,24 @@ namespace Disco.Services.Users Cache.FlushCache(); } - internal static IEnumerable SearchUsers(DiscoDataContext Database, string Term) + internal static List SearchUsers(DiscoDataContext Database, string Term, bool PersistResults, int? LimitCount = ActiveDirectory.DefaultSearchResultLimit) { - var adImportedUsers = ActiveDirectory.SearchADUserAccounts(Term, Quick: true); - foreach (var adU in adImportedUsers.Select(adU => adU.ToRepositoryUser())) + var adImportedUsers = ActiveDirectory.SearchADUserAccounts(Term, Quick: true, ResultLimit: LimitCount).Select(adU => adU.ToRepositoryUser()).ToList(); + + if (PersistResults) { - var existingUser = Database.Users.Find(adU.UserId); - if (existingUser != null) - existingUser.UpdateSelf(adU); - else - Database.Users.Add(adU); - Database.SaveChanges(); - UserService.InvalidateCachedUser(adU.UserId); + foreach (var adU in adImportedUsers) + { + var existingUser = Database.Users.Find(adU.UserId); + if (existingUser != null) + existingUser.UpdateSelf(adU); + else + Database.Users.Add(adU); + Database.SaveChanges(); + UserService.InvalidateCachedUser(adU.UserId); + } } + return adImportedUsers; } diff --git a/Disco.Web/Areas/API/Controllers/DeviceController.cs b/Disco.Web/Areas/API/Controllers/DeviceController.cs index db6d8330..277058ee 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceController.cs @@ -114,8 +114,16 @@ namespace Disco.Web.Areas.API.Controllers } [DiscoAuthorize(Claims.Device.Actions.AssignUser)] - public virtual ActionResult UpdateAssignedUserId(string id, string AssignedUserId = null, bool redirect = false) + public virtual ActionResult UpdateAssignedUserId(string id, string AssignedUserId = null, string AssignedUserDomain = null, bool redirect = false) { + if (AssignedUserId != null && !AssignedUserId.Contains('\\')) + { + if (string.IsNullOrWhiteSpace(AssignedUserDomain)) + AssignedUserId = string.Format(@"{0}\{1}", ActiveDirectory.Context.PrimaryDomain.NetBiosName, AssignedUserId); + else + AssignedUserId = string.Format(@"{0}\{1}", AssignedUserDomain, AssignedUserId); + } + return Update(id, pAssignedUserId, AssignedUserId, redirect); } @@ -361,6 +369,9 @@ namespace Disco.Web.Areas.API.Controllers [DiscoAuthorize(Claims.Device.Show)] public virtual ActionResult LastNetworkLogonDate(string id) { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException("id", "The Device Serial Number is required"); + var device = Database.Devices.Find(id); if (device == null) { diff --git a/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs b/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs index 1cda01f2..e7a0a430 100644 --- a/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs +++ b/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs @@ -2,6 +2,7 @@ using Disco.BI.Extensions; using Disco.Models.Repository; using Disco.Services.Authorization; +using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Users; using Disco.Services.Web; using System; @@ -287,7 +288,7 @@ namespace Disco.Web.Areas.API.Controllers } [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages)] - public virtual ActionResult ImporterUndetectedDataIdLookup(string id, string term, int limitCount = 20) + public virtual ActionResult ImporterUndetectedDataIdLookup(string id, string term, int limitCount = ActiveDirectory.DefaultSearchResultLimit) { if (!string.IsNullOrEmpty(id) && !string.IsNullOrWhiteSpace(term)) { @@ -330,7 +331,7 @@ namespace Disco.Web.Areas.API.Controllers results = Disco.Services.Searching.Search.SearchJobsTable(Database, term, limitCount, false).Items.Select(sr => Models.DocumentTemplate.ImporterUndetectedDataIdLookupModel.FromSearchResultItem(sr)).ToArray(); break; case DocumentTemplate.DocumentTemplateScopes.User: - results = Disco.Services.Searching.Search.SearchUsers(Database, term, limitCount).Select(sr => Models.DocumentTemplate.ImporterUndetectedDataIdLookupModel.FromSearchResultItem(sr)).ToArray(); + results = Disco.Services.Searching.Search.SearchUsers(Database, term, false, limitCount).Select(sr => Models.DocumentTemplate.ImporterUndetectedDataIdLookupModel.FromSearchResultItem(sr)).ToArray(); break; default: results = null; diff --git a/Disco.Web/Areas/API/Controllers/SearchController.cs b/Disco.Web/Areas/API/Controllers/SearchController.cs index 8a32fb7f..97c8e3a7 100644 --- a/Disco.Web/Areas/API/Controllers/SearchController.cs +++ b/Disco.Web/Areas/API/Controllers/SearchController.cs @@ -1,5 +1,6 @@ using Disco.Models.Services.Searching; using Disco.Services.Authorization; +using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Searching; using Disco.Services.Web; using System; @@ -13,7 +14,7 @@ namespace Disco.Web.Areas.API.Controllers public partial class SearchController : AuthorizedDatabaseController { [DiscoAuthorizeAny(Claims.Job.Search, Claims.Device.Search, Claims.User.Search)] - public virtual ActionResult QuickQuery(string Term, int Limit = 15) + public virtual ActionResult QuickQuery(string Term, int Limit = ActiveDirectory.DefaultSearchResultLimit) { if (string.IsNullOrWhiteSpace(Term)) throw new ArgumentNullException("Term", "The search query term is required"); @@ -36,20 +37,35 @@ namespace Disco.Web.Areas.API.Controllers break; case '@': // User Only if (Authorization.Has(Claims.User.Search)) - results = results.Concat(Search.SearchUsers(Database, Term.Substring(1), Limit)); + results = results.Concat(Search.SearchUsers(Database, Term.Substring(1), false, Limit)); break; default: // Search All if (Authorization.Has(Claims.Job.Search)) results = results.Concat(Search.SearchJobs(Database, Term, Limit)); if (Authorization.Has(Claims.User.Search)) - results = results.Concat(Search.SearchUsers(Database, Term, Limit)); + results = results.Concat(Search.SearchUsers(Database, Term, false, Limit)); if (Authorization.Has(Claims.Device.Search)) results = results.Concat(Search.SearchDevices(Database, Term, Limit)); break; } - results = results.OrderByDescending(i => i.ScoreValue.Score(Term)).Take(Limit); + results = results.OrderByDescending(i => i.ScoreValues.Score(Term)).Take(Limit); + + return Json(results, JsonRequestBehavior.AllowGet); + } + + [DiscoAuthorize(Claims.User.Search)] + public virtual ActionResult UsersUpstream(string Term, int Limit = 15) + { + if (string.IsNullOrWhiteSpace(Term)) + throw new ArgumentNullException("Term", "The search query term is required"); + if (Term.Length < 2) + throw new ArgumentException("The search query term must be at least two characters", "Term"); + if (Limit < 1) + throw new ArgumentException("The search query limit cannot be less than 1", "Limit"); + + var results = Search.SearchUsersUpstream(Database, Term, false, Limit); return Json(results, JsonRequestBehavior.AllowGet); } diff --git a/Disco.Web/Areas/API/Controllers/UserController.cs b/Disco.Web/Areas/API/Controllers/UserController.cs index b36b27b9..426cc60b 100644 --- a/Disco.Web/Areas/API/Controllers/UserController.cs +++ b/Disco.Web/Areas/API/Controllers/UserController.cs @@ -12,12 +12,6 @@ namespace Disco.Web.Areas.API.Controllers { public partial class UserController : AuthorizedDatabaseController { - [DiscoAuthorize(Claims.User.Search)] - public virtual ActionResult UpstreamUsers(string term) - { - return Json(Disco.Services.Searching.Search.SearchUsersUpstream(term), JsonRequestBehavior.AllowGet); - } - #region User Attachements [DiscoAuthorize(Claims.User.ShowAttachments)] diff --git a/Disco.Web/Controllers/SearchController.cs b/Disco.Web/Controllers/SearchController.cs index 471d8d01..3312233f 100644 --- a/Disco.Web/Controllers/SearchController.cs +++ b/Disco.Web/Controllers/SearchController.cs @@ -62,13 +62,13 @@ namespace Disco.Web.Controllers return View(m); } if (Authorization.Has(Claims.Job.Search)) - m.Jobs = Services.Searching.Search.SearchJobsTable(Database, term, null, true, searchDetails); + m.Jobs = Services.Searching.Search.SearchJobsTable(Database, term, LimitCount: null, IncludeJobStatus: true, SearchDetails: searchDetails); if (Authorization.Has(Claims.Device.Search)) - m.Devices = Services.Searching.Search.SearchDevices(Database, term, null, searchDetails); + m.Devices = Services.Searching.Search.SearchDevices(Database, term, LimitCount: null, SearchDetails: searchDetails); if (Authorization.Has(Claims.User.Search)) - m.Users = Services.Searching.Search.SearchUsers(Database, term); + m.Users = Services.Searching.Search.SearchUsers(Database, term, true, LimitCount: null); } else { @@ -83,7 +83,7 @@ namespace Disco.Web.Controllers if (vm != null) { m.FriendlyTerm = string.Format("Device Model: {0}", vm.ToString()); - m.Devices = Services.Searching.Search.SearchDeviceModel(Database, vm.Id); + m.Devices = Services.Searching.Search.SearchDeviceModel(Database, vm.Id, LimitCount: null); break; } } @@ -100,7 +100,7 @@ namespace Disco.Web.Controllers if (dp != null) { m.FriendlyTerm = string.Format("Device Profile: {0}", dp.ToString()); - m.Devices = Services.Searching.Search.SearchDeviceProfile(Database, dp.Id); + m.Devices = Services.Searching.Search.SearchDeviceProfile(Database, dp.Id, LimitCount: null); break; } } @@ -117,7 +117,7 @@ namespace Disco.Web.Controllers if (db != null) { m.FriendlyTerm = string.Format("Device Batch: {0}", db.ToString()); - m.Devices = Services.Searching.Search.SearchDeviceBatch(Database, db.Id); + m.Devices = Services.Searching.Search.SearchDeviceBatch(Database, db.Id, LimitCount: null); break; } } @@ -154,7 +154,7 @@ namespace Disco.Web.Controllers return RedirectToAction(MVC.Job.Show(termInt)); } } - m.Jobs = Services.Searching.Search.SearchJobsTable(Database, term, null, true, searchDetails); + m.Jobs = Services.Searching.Search.SearchJobsTable(Database, term, LimitCount: null, IncludeJobStatus: true, SearchDetails: searchDetails); break; case "users": Authorization.Require(Claims.User.Search); @@ -164,7 +164,7 @@ namespace Disco.Web.Controllers m.ErrorMessage = "A search term of at least two characters is required"; return View(m); } - m.Users = Services.Searching.Search.SearchUsers(Database, term); + m.Users = Services.Searching.Search.SearchUsers(Database, term, true, LimitCount: null); if (m.Users.Count == 1) { return RedirectToAction(MVC.User.Show(m.Users[0].Id)); diff --git a/Disco.Web/T4MVC.cs b/Disco.Web/T4MVC.cs index 85f5ba77..05cb18d0 100644 --- a/Disco.Web/T4MVC.cs +++ b/Disco.Web/T4MVC.cs @@ -3684,6 +3684,7 @@ namespace Disco.Web.Areas.API.Controllers { public readonly string id = "id"; public readonly string AssignedUserId = "AssignedUserId"; + public readonly string AssignedUserDomain = "AssignedUserDomain"; public readonly string redirect = "redirect"; } static readonly ActionParamsClass_UpdateAllowUnauthenticatedEnrol s_params_UpdateAllowUnauthenticatedEnrol = new ActionParamsClass_UpdateAllowUnauthenticatedEnrol(); @@ -3896,15 +3897,16 @@ namespace Disco.Web.Areas.API.Controllers return callInfo; } - partial void UpdateAssignedUserIdOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string id, string AssignedUserId, bool redirect); + partial void UpdateAssignedUserIdOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string id, string AssignedUserId, string AssignedUserDomain, bool redirect); - public override System.Web.Mvc.ActionResult UpdateAssignedUserId(string id, string AssignedUserId, bool redirect) + public override System.Web.Mvc.ActionResult UpdateAssignedUserId(string id, string AssignedUserId, string AssignedUserDomain, bool redirect) { var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UpdateAssignedUserId); ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "id", id); ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "AssignedUserId", AssignedUserId); + ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "AssignedUserDomain", AssignedUserDomain); ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "redirect", redirect); - UpdateAssignedUserIdOverride(callInfo, id, AssignedUserId, redirect); + UpdateAssignedUserIdOverride(callInfo, id, AssignedUserId, AssignedUserDomain, redirect); return callInfo; } @@ -9065,6 +9067,12 @@ namespace Disco.Web.Areas.API.Controllers { return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.QuickQuery); } + [NonAction] + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public virtual System.Web.Mvc.ActionResult UsersUpstream() + { + return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UsersUpstream); + } [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public SearchController Actions { get { return MVC.API.Search; } } @@ -9082,12 +9090,14 @@ namespace Disco.Web.Areas.API.Controllers public class ActionNamesClass { public readonly string QuickQuery = "QuickQuery"; + public readonly string UsersUpstream = "UsersUpstream"; } [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public class ActionNameConstants { public const string QuickQuery = "QuickQuery"; + public const string UsersUpstream = "UsersUpstream"; } @@ -9100,6 +9110,15 @@ namespace Disco.Web.Areas.API.Controllers public readonly string Term = "Term"; public readonly string Limit = "Limit"; } + static readonly ActionParamsClass_UsersUpstream s_params_UsersUpstream = new ActionParamsClass_UsersUpstream(); + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public ActionParamsClass_UsersUpstream UsersUpstreamParams { get { return s_params_UsersUpstream; } } + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public class ActionParamsClass_UsersUpstream + { + public readonly string Term = "Term"; + public readonly string Limit = "Limit"; + } static readonly ViewsClass s_views = new ViewsClass(); [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public ViewsClass Views { get { return s_views; } } @@ -9130,6 +9149,17 @@ namespace Disco.Web.Areas.API.Controllers return callInfo; } + partial void UsersUpstreamOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string Term, int Limit); + + public override System.Web.Mvc.ActionResult UsersUpstream(string Term, int Limit) + { + var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UsersUpstream); + ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "Term", Term); + ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "Limit", Limit); + UsersUpstreamOverride(callInfo, Term, Limit); + return callInfo; + } + } } @@ -9564,12 +9594,6 @@ namespace Disco.Web.Areas.API.Controllers return RedirectToRoutePermanent(callInfo.RouteValueDictionary); } - [NonAction] - [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] - public virtual System.Web.Mvc.ActionResult UpstreamUsers() - { - return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UpstreamUsers); - } [NonAction] [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public virtual System.Web.Mvc.ActionResult AttachmentDownload() @@ -9628,7 +9652,6 @@ namespace Disco.Web.Areas.API.Controllers [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public class ActionNamesClass { - public readonly string UpstreamUsers = "UpstreamUsers"; public readonly string AttachmentDownload = "AttachmentDownload"; public readonly string AttachmentThumbnail = "AttachmentThumbnail"; public readonly string AttachmentUpload = "AttachmentUpload"; @@ -9641,7 +9664,6 @@ namespace Disco.Web.Areas.API.Controllers [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public class ActionNameConstants { - public const string UpstreamUsers = "UpstreamUsers"; public const string AttachmentDownload = "AttachmentDownload"; public const string AttachmentThumbnail = "AttachmentThumbnail"; public const string AttachmentUpload = "AttachmentUpload"; @@ -9652,14 +9674,6 @@ namespace Disco.Web.Areas.API.Controllers } - static readonly ActionParamsClass_UpstreamUsers s_params_UpstreamUsers = new ActionParamsClass_UpstreamUsers(); - [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] - public ActionParamsClass_UpstreamUsers UpstreamUsersParams { get { return s_params_UpstreamUsers; } } - [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] - public class ActionParamsClass_UpstreamUsers - { - public readonly string term = "term"; - } static readonly ActionParamsClass_AttachmentDownload s_params_AttachmentDownload = new ActionParamsClass_AttachmentDownload(); [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public ActionParamsClass_AttachmentDownload AttachmentDownloadParams { get { return s_params_AttachmentDownload; } } @@ -9740,16 +9754,6 @@ namespace Disco.Web.Areas.API.Controllers { public T4MVC_UserController() : base(Dummy.Instance) { } - partial void UpstreamUsersOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string term); - - public override System.Web.Mvc.ActionResult UpstreamUsers(string term) - { - var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UpstreamUsers); - ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "term", term); - UpstreamUsersOverride(callInfo, term); - return callInfo; - } - partial void AttachmentDownloadOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, int id); public override System.Web.Mvc.ActionResult AttachmentDownload(int id) diff --git a/Disco.Web/Views/Device/AddOffline.cshtml b/Disco.Web/Views/Device/AddOffline.cshtml index c5b35819..34f29c2e 100644 --- a/Disco.Web/Views/Device/AddOffline.cshtml +++ b/Disco.Web/Views/Device/AddOffline.cshtml @@ -126,7 +126,7 @@ .watermark('Search Users') .focus(function () { $AssignedUserId.select() }) .autocomplete({ - source: '@(Url.Action(MVC.API.User.UpstreamUsers()))', + source: '@(Url.Action(MVC.API.Search.UsersUpstream()))', minLength: 2, focus: function (e, ui) { $AssignedUserId.val(ui.item.DisplayName + ' (' + ui.item.Id + ')'); diff --git a/Disco.Web/Views/Device/AddOffline.generated.cs b/Disco.Web/Views/Device/AddOffline.generated.cs index a1146b1c..0b149926 100644 --- a/Disco.Web/Views/Device/AddOffline.generated.cs +++ b/Disco.Web/Views/Device/AddOffline.generated.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34011 +// Runtime Version:4.0.30319.34014 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -494,7 +494,7 @@ WriteLiteral(@" #line 129 "..\..\Views\Device\AddOffline.cshtml" - Write(Url.Action(MVC.API.User.UpstreamUsers())); + Write(Url.Action(MVC.API.Search.UsersUpstream())); #line default diff --git a/Disco.Web/Views/Device/DeviceParts/_Subject.cshtml b/Disco.Web/Views/Device/DeviceParts/_Subject.cshtml index b59b1900..31ee3b56 100644 --- a/Disco.Web/Views/Device/DeviceParts/_Subject.cshtml +++ b/Disco.Web/Views/Device/DeviceParts/_Subject.cshtml @@ -500,7 +500,7 @@ } @if (Model.Device.CanUpdateAssignment()) { - @Html.ActionLinkSmallButton("Update Assignment", MVC.API.Device.UpdateAssignedUserId(Model.Device.SerialNumber, null, true), "Device_Show_User_Actions_Assign_Button") + @Html.ActionLinkSmallButton("Update Assignment", MVC.API.Device.UpdateAssignedUserId(Model.Device.SerialNumber, null, null, true), "Device_Show_User_Actions_Assign_Button")

 Assign to User:


@@ -555,7 +555,7 @@ inputUserId = $('#Device_Show_User_Actions_Assign_UserId'); inputUserId.focus(function () { inputUserId.select() }) .autocomplete({ - source: '@(Url.Action(MVC.API.User.UpstreamUsers()))', + source: '@(Url.Action(MVC.API.Search.UsersUpstream()))', minLength: 2, select: function (e, ui) { inputUserId.val(ui.item.Id); diff --git a/Disco.Web/Views/Device/DeviceParts/_Subject.generated.cs b/Disco.Web/Views/Device/DeviceParts/_Subject.generated.cs index 7595e59d..948d3588 100644 --- a/Disco.Web/Views/Device/DeviceParts/_Subject.generated.cs +++ b/Disco.Web/Views/Device/DeviceParts/_Subject.generated.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34011 +// Runtime Version:4.0.30319.34014 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -1901,14 +1901,14 @@ WriteLiteral(" "); #line hidden #line 503 "..\..\Views\Device\DeviceParts\_Subject.cshtml" - Write(Html.ActionLinkSmallButton("Update Assignment", MVC.API.Device.UpdateAssignedUserId(Model.Device.SerialNumber, null, true), "Device_Show_User_Actions_Assign_Button")); + Write(Html.ActionLinkSmallButton("Update Assignment", MVC.API.Device.UpdateAssignedUserId(Model.Device.SerialNumber, null, null, true), "Device_Show_User_Actions_Assign_Button")); #line default #line hidden #line 503 "..\..\Views\Device\DeviceParts\_Subject.cshtml" - + #line default @@ -2008,7 +2008,7 @@ WriteLiteral("\r\n \"Assign\": function () {\r\n #line 558 "..\..\Views\Device\DeviceParts\_Subject.cshtml" - Write(Url.Action(MVC.API.User.UpstreamUsers())); + Write(Url.Action(MVC.API.Search.UsersUpstream())); #line default @@ -2314,28 +2314,28 @@ WriteLiteral("
  • \r\n WriteLiteral(" type=\"radio\""); -WriteAttribute("id", Tuple.Create(" id=\"", 41935), Tuple.Create("\"", 42013) -, Tuple.Create(Tuple.Create("", 41940), Tuple.Create("Device_Show_Device_Actions_Decommission_Reason_", 41940), true) +WriteAttribute("id", Tuple.Create(" id=\"", 41943), Tuple.Create("\"", 42021) +, Tuple.Create(Tuple.Create("", 41948), Tuple.Create("Device_Show_Device_Actions_Decommission_Reason_", 41948), true) #line 679 "..\..\Views\Device\DeviceParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create("", 41987), Tuple.Create((int)decommissionReason + , Tuple.Create(Tuple.Create("", 41995), Tuple.Create((int)decommissionReason #line default #line hidden -, 41987), false) +, 41995), false) ); WriteLiteral("\r\n name=\"Device_Show_Device_Actions_Decomm" + "ission_Reason\""); -WriteAttribute("value", Tuple.Create(" value=\"", 42109), Tuple.Create("\"", 42143) +WriteAttribute("value", Tuple.Create(" value=\"", 42117), Tuple.Create("\"", 42151) #line 680 "..\..\Views\Device\DeviceParts\_Subject.cshtml" - , Tuple.Create(Tuple.Create("", 42117), Tuple.Create((int)decommissionReason + , Tuple.Create(Tuple.Create("", 42125), Tuple.Create((int)decommissionReason #line default #line hidden -, 42117), false) +, 42125), false) ); WriteLiteral(" "); @@ -2349,15 +2349,15 @@ WriteLiteral(" "); #line hidden WriteLiteral("/>\r\n ((int)decommissionReason + , Tuple.Create(Tuple.Create("", 42354), Tuple.Create((int)decommissionReason #line default #line hidden -, 42346), false) +, 42354), false) ); WriteLiteral(">");