Exemple #1
0
        /// <summary>
        /// Renders the specified writer.
        /// </summary>
        /// <param name="badge">The badge.</param>
        /// <param name="writer">The writer.</param>
        public override void Render(BadgeCache badge, System.Web.UI.HtmlTextWriter writer)
        {
            RockContext rockContext           = new RockContext();
            var         dataViewAttributeGuid = GetAttributeValue(badge, "DataView").AsGuid();
            var         dataViewService       = new DataViewService(rockContext);

            if (dataViewAttributeGuid != Guid.Empty)
            {
                var dataView = dataViewService.Get(dataViewAttributeGuid);
                if (dataView != null)
                {
                    var errors = new List <string>();

                    Stopwatch stopwatch     = Stopwatch.StartNew();
                    var       qry           = dataView.GetQuery(null, 30, out errors);
                    var       isEntityFound = false;
                    if (qry != null)
                    {
                        isEntityFound = qry.Where(e => e.Id == Entity.Id).Any();
                        stopwatch.Stop();
                        DataViewService.AddRunDataViewTransaction(dataView.Id,
                                                                  Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds));
                    }

                    if (isEntityFound)
                    {
                        Dictionary <string, object> mergeValues = new Dictionary <string, object>();
                        mergeValues.Add("Person", Person);
                        mergeValues.Add("Entity", Entity);
                        writer.Write(GetAttributeValue(badge, "BadgeContent").ResolveMergeFields(mergeValues));
                    }
                }
            }
        }
Exemple #2
0
        /// <summary>
        /// Executes the specified workflow.
        /// </summary>
        /// <param name="rockContext">The rock context.</param>
        /// <param name="action">The action.</param>
        /// <param name="entity">The entity.</param>
        /// <param name="errorMessages">The error messages.</param>
        /// <returns></returns>
        public override bool Execute(RockContext rockContext, WorkflowAction action, Object entity, out List <string> errorMessages)
        {
            errorMessages = new List <string>();

            var person = GetPersonAliasFromActionAttribute("Person", rockContext, action, errorMessages);

            if (person != null)
            {
                Guid booleanGuid = GetAttributeValue(action, "Boolean").AsGuid();
                if (!booleanGuid.IsEmpty())
                {
                    var attribute = AttributeCache.Get(booleanGuid, rockContext);
                    if (attribute != null)
                    {
                        var dataViewAttributeGuid = GetAttributeValue(action, "DataView").AsGuid();
                        if (dataViewAttributeGuid != Guid.Empty)
                        {
                            var dataViewService = new DataViewService(rockContext);
                            var dataView        = dataViewService.Get(dataViewAttributeGuid);
                            if (dataView != null)
                            {
                                var       timeout       = GetAttributeValue(action, "Timeout").AsIntegerOrNull();
                                Stopwatch stopwatch     = Stopwatch.StartNew();
                                var       qry           = dataView.GetQuery(null, timeout, out errorMessages);
                                var       isPersonFound = false;
                                if (qry != null)
                                {
                                    isPersonFound = qry.Where(e => e.Id == person.Id).Any();
                                    stopwatch.Stop();
                                    DataViewService.AddRunDataViewTransaction(dataView.Id,
                                                                              Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds));
                                }

                                if (isPersonFound)
                                {
                                    SetWorkflowAttributeValue(action, booleanGuid, "True");
                                }
                                else
                                {
                                    SetWorkflowAttributeValue(action, booleanGuid, "False");
                                }
                            }
                        }
                    }
                    else
                    {
                        errorMessages.Add("DataView could not be found.");
                    }
                }
            }
            else
            {
                errorMessages.Add("No person was provided for DataView.");
            }

            errorMessages.ForEach(m => action.AddLogEntry(m, true));

            return(true);
        }
Exemple #3
0
        /// <summary>
        /// Job that will sync groups.
        ///
        /// Called by the <see cref="IScheduler" /> when a
        /// <see cref="ITrigger" /> fires that is associated with
        /// the <see cref="IJob" />.
        /// </summary>
        public virtual void Execute(IJobExecutionContext context)
        {
            // Get the job setting(s)
            JobDataMap dataMap = context.JobDetail.JobDataMap;
            bool       requirePasswordReset = dataMap.GetBoolean("RequirePasswordReset");
            var        commandTimeout       = dataMap.GetString("CommandTimeout").AsIntegerOrNull() ?? 180;

            // Counters for displaying results
            int    groupsSynced  = 0;
            int    groupsChanged = 0;
            string groupName     = string.Empty;
            string dataViewName  = string.Empty;
            var    errors        = new List <string>();

            try
            {
                // get groups set to sync
                var activeSyncList = new List <GroupSyncInfo>();
                using (var rockContext = new RockContext())
                {
                    // Get groups that are not archived and are still active.
                    activeSyncList = new GroupSyncService(rockContext)
                                     .Queryable()
                                     .AsNoTracking()
                                     .AreNotArchived()
                                     .AreActive()
                                     .NeedToBeSynced()
                                     .Select(x => new GroupSyncInfo {
                        SyncId = x.Id, GroupName = x.Group.Name
                    })
                                     .ToList();
                }

                foreach (var syncInfo in activeSyncList)
                {
                    int  syncId         = syncInfo.SyncId;
                    bool hasSyncChanged = false;
                    context.UpdateLastStatusMessage($"Syncing group {syncInfo.GroupName}");

                    // Use a fresh rockContext per sync so that ChangeTracker doesn't get bogged down
                    using (var rockContext = new RockContext())
                    {
                        // increase the timeout just in case the data view source is slow
                        rockContext.Database.CommandTimeout = commandTimeout;
                        rockContext.SourceOfChange          = "Group Sync";

                        // Get the Sync
                        var sync = new GroupSyncService(rockContext)
                                   .Queryable()
                                   .Include(a => a.Group)
                                   .Include(a => a.SyncDataView)
                                   .AsNoTracking()
                                   .FirstOrDefault(s => s.Id == syncId);

                        if (sync == null || sync.SyncDataView.EntityTypeId != EntityTypeCache.Get(typeof(Person)).Id)
                        {
                            // invalid sync or invalid SyncDataView
                            continue;
                        }

                        List <string> syncErrors = new List <string>();

                        dataViewName = sync.SyncDataView.Name;
                        groupName    = sync.Group.Name;

                        Stopwatch stopwatch = Stopwatch.StartNew();
                        // Get the person id's from the data view (source)
                        var dataViewQry     = sync.SyncDataView.GetQuery(null, rockContext, commandTimeout, out syncErrors);
                        var sourcePersonIds = dataViewQry.Select(q => q.Id).ToList();
                        stopwatch.Stop();
                        DataViewService.AddRunDataViewTransaction(sync.SyncDataView.Id,
                                                                  Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds));
                        // If any error occurred trying get the 'where expression' from the sync-data-view,
                        // just skip trying to sync that particular group's Sync Data View for now.
                        if (syncErrors.Count > 0)
                        {
                            errors.AddRange(syncErrors);
                            ExceptionLogService.LogException(new Exception(string.Format("An error occurred while trying to GroupSync group '{0}' and data view '{1}' so the sync was skipped. Error: {2}", groupName, dataViewName, String.Join(",", syncErrors))));
                            continue;
                        }

                        // Get the person id's in the group (target) for the role being synced.
                        // Note: targetPersonIds must include archived group members
                        // so we don't try to delete anyone who's already archived, and
                        // it must include deceased members so we can remove them if they
                        // are no longer in the data view.
                        var existingGroupMemberPersonList = new GroupMemberService(rockContext)
                                                            .Queryable(true, true).AsNoTracking()
                                                            .Where(gm => gm.GroupId == sync.GroupId)
                                                            .Where(gm => gm.GroupRoleId == sync.GroupTypeRoleId)
                                                            .Select(gm => new
                        {
                            PersonId   = gm.PersonId,
                            IsArchived = gm.IsArchived
                        })
                                                            .ToList();

                        var targetPersonIdsToDelete = existingGroupMemberPersonList.Where(t => !sourcePersonIds.Contains(t.PersonId) && t.IsArchived != true).ToList();
                        if (targetPersonIdsToDelete.Any())
                        {
                            context.UpdateLastStatusMessage($"Deleting {targetPersonIdsToDelete.Count()} group records in {syncInfo.GroupName} that are no longer in the sync data view");
                        }

                        int deletedCount = 0;

                        // Delete people from the group/role that are no longer in the data view --
                        // but not the ones that are already archived.
                        foreach (var targetPerson in targetPersonIdsToDelete)
                        {
                            deletedCount++;
                            if (deletedCount % 100 == 0)
                            {
                                context.UpdateLastStatusMessage($"Deleted {deletedCount} of {targetPersonIdsToDelete.Count()} group member records for group {syncInfo.GroupName}");
                            }

                            try
                            {
                                // Use a new context to limit the amount of change-tracking required
                                using (var groupMemberContext = new RockContext())
                                {
                                    // Delete the records for that person's group and role.
                                    // NOTE: just in case there are duplicate records, delete all group member records for that person and role
                                    var groupMemberService = new GroupMemberService(groupMemberContext);
                                    foreach (var groupMember in groupMemberService
                                             .Queryable(true, true)
                                             .Where(m =>
                                                    m.GroupId == sync.GroupId &&
                                                    m.GroupRoleId == sync.GroupTypeRoleId &&
                                                    m.PersonId == targetPerson.PersonId)
                                             .ToList())
                                    {
                                        groupMemberService.Delete(groupMember);
                                    }

                                    groupMemberContext.SaveChanges();

                                    // If the Group has an exit email, and person has an email address, send them the exit email
                                    if (sync.ExitSystemCommunication != null)
                                    {
                                        var person = new PersonService(groupMemberContext).Get(targetPerson.PersonId);
                                        if (person.CanReceiveEmail(false))
                                        {
                                            // Send the exit email
                                            var mergeFields = new Dictionary <string, object>();
                                            mergeFields.Add("Group", sync.Group);
                                            mergeFields.Add("Person", person);
                                            var emailMessage = new RockEmailMessage(sync.ExitSystemCommunication);
                                            emailMessage.AddRecipient(new RockEmailMessageRecipient(person, mergeFields));
                                            var emailErrors = new List <string>();
                                            emailMessage.Send(out emailErrors);
                                            errors.AddRange(emailErrors);
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                ExceptionLogService.LogException(ex);
                                continue;
                            }

                            hasSyncChanged = true;
                        }


                        // Now find all the people in the source list who are NOT already in the target list (as Unarchived)
                        var targetPersonIdsToAdd = sourcePersonIds.Where(s => !existingGroupMemberPersonList.Any(t => t.PersonId == s && t.IsArchived == false)).ToList();

                        // Make a list of PersonIds that have an Archived group member record
                        // if this person isn't already a member of the list as an Unarchived member, we can Restore the group member for that PersonId instead
                        var archivedTargetPersonIds = existingGroupMemberPersonList.Where(t => t.IsArchived == true).Select(a => a.PersonId).ToList();

                        context.UpdateLastStatusMessage($"Adding {targetPersonIdsToAdd.Count()} group member records for group {syncInfo.GroupName}");
                        int addedCount    = 0;
                        int notAddedCount = 0;
                        foreach (var personId in targetPersonIdsToAdd)
                        {
                            if ((addedCount + notAddedCount) % 100 == 0)
                            {
                                string notAddedMessage = string.Empty;
                                if (notAddedCount > 0)
                                {
                                    notAddedMessage = $"{Environment.NewLine} There are {notAddedCount} members that could not be added due to group requirements.";
                                }
                                context.UpdateLastStatusMessage($"Added {addedCount} of {targetPersonIdsToAdd.Count()} group member records for group {syncInfo.GroupName}. {notAddedMessage}");
                            }
                            try
                            {
                                // Use a new context to limit the amount of change-tracking required
                                using (var groupMemberContext = new RockContext())
                                {
                                    var groupMemberService = new GroupMemberService(groupMemberContext);
                                    var groupService       = new GroupService(groupMemberContext);

                                    // If this person is currently archived...
                                    if (archivedTargetPersonIds.Contains(personId))
                                    {
                                        // ...then we'll just restore them;
                                        GroupMember archivedGroupMember = groupService.GetArchivedGroupMember(sync.Group, personId, sync.GroupTypeRoleId);

                                        if (archivedGroupMember == null)
                                        {
                                            // shouldn't happen, but just in case
                                            continue;
                                        }

                                        archivedGroupMember.GroupMemberStatus = GroupMemberStatus.Active;
                                        if (archivedGroupMember.IsValidGroupMember(groupMemberContext))
                                        {
                                            addedCount++;
                                            groupMemberService.Restore(archivedGroupMember);
                                            groupMemberContext.SaveChanges();
                                        }
                                        else
                                        {
                                            notAddedCount++;
                                            // Validation errors will get added to the ValidationResults collection. Add those results to the log and then move on to the next person.
                                            var ex = new GroupMemberValidationException(string.Join(",", archivedGroupMember.ValidationResults.Select(r => r.ErrorMessage).ToArray()));
                                            ExceptionLogService.LogException(ex);
                                            continue;
                                        }
                                    }
                                    else
                                    {
                                        // ...otherwise we will add a new person to the group with the role specified in the sync.
                                        var newGroupMember = new GroupMember {
                                            Id = 0
                                        };
                                        newGroupMember.PersonId          = personId;
                                        newGroupMember.GroupId           = sync.GroupId;
                                        newGroupMember.GroupMemberStatus = GroupMemberStatus.Active;
                                        newGroupMember.GroupRoleId       = sync.GroupTypeRoleId;

                                        if (newGroupMember.IsValidGroupMember(groupMemberContext))
                                        {
                                            addedCount++;
                                            groupMemberService.Add(newGroupMember);
                                            groupMemberContext.SaveChanges();
                                        }
                                        else
                                        {
                                            notAddedCount++;
                                            // Validation errors will get added to the ValidationResults collection. Add those results to the log and then move on to the next person.
                                            var ex = new GroupMemberValidationException(string.Join(",", newGroupMember.ValidationResults.Select(r => r.ErrorMessage).ToArray()));
                                            ExceptionLogService.LogException(ex);
                                            continue;
                                        }
                                    }

                                    // If the Group has a welcome email, and person has an email address, send them the welcome email and possibly create a login
                                    if (sync.WelcomeSystemCommunication != null)
                                    {
                                        var person = new PersonService(groupMemberContext).Get(personId);
                                        if (person.CanReceiveEmail(false))
                                        {
                                            // If the group is configured to add a user account for anyone added to the group, and person does not yet have an
                                            // account, add one for them.
                                            string newPassword = string.Empty;
                                            bool   createLogin = sync.AddUserAccountsDuringSync;

                                            // Only create a login if requested, no logins exist and we have enough information to generate a user name.
                                            if (createLogin && !person.Users.Any() && !string.IsNullOrWhiteSpace(person.NickName) && !string.IsNullOrWhiteSpace(person.LastName))
                                            {
                                                newPassword = System.Web.Security.Membership.GeneratePassword(9, 1);
                                                string username = Rock.Security.Authentication.Database.GenerateUsername(person.NickName, person.LastName);

                                                UserLogin login = UserLoginService.Create(
                                                    groupMemberContext,
                                                    person,
                                                    AuthenticationServiceType.Internal,
                                                    EntityTypeCache.Get(Rock.SystemGuid.EntityType.AUTHENTICATION_DATABASE.AsGuid()).Id,
                                                    username,
                                                    newPassword,
                                                    true,
                                                    requirePasswordReset);
                                            }

                                            // Send the welcome email
                                            var mergeFields = new Dictionary <string, object>();
                                            mergeFields.Add("Group", sync.Group);
                                            mergeFields.Add("Person", person);
                                            mergeFields.Add("NewPassword", newPassword);
                                            mergeFields.Add("CreateLogin", createLogin);
                                            var emailMessage = new RockEmailMessage(sync.WelcomeSystemCommunication);
                                            emailMessage.AddRecipient(new RockEmailMessageRecipient(person, mergeFields));
                                            var emailErrors = new List <string>();
                                            emailMessage.Send(out emailErrors);
                                            errors.AddRange(emailErrors);
                                        }
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                ExceptionLogService.LogException(ex);
                                continue;
                            }

                            hasSyncChanged = true;
                        }

                        // Increment Groups Changed Counter (if people were deleted or added to the group)
                        if (hasSyncChanged)
                        {
                            groupsChanged++;
                        }

                        // Increment the Groups Synced Counter
                        groupsSynced++;
                    }

                    // Update last refresh datetime in different context to avoid side-effects.
                    using (var rockContext = new RockContext())
                    {
                        var sync = new GroupSyncService(rockContext)
                                   .Queryable()
                                   .FirstOrDefault(s => s.Id == syncId);

                        sync.LastRefreshDateTime = RockDateTime.Now;

                        rockContext.SaveChanges();
                    }
                }

                // Format the result message
                var resultMessage = string.Empty;
                if (groupsSynced == 0)
                {
                    resultMessage = "No groups to sync";
                }
                else if (groupsSynced == 1)
                {
                    resultMessage = "1 group was synced";
                }
                else
                {
                    resultMessage = string.Format("{0} groups were synced", groupsSynced);
                }

                resultMessage += string.Format(" and {0} groups were changed", groupsChanged);

                if (errors.Any())
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine();
                    sb.Append("Errors: ");
                    errors.ForEach(e => { sb.AppendLine(); sb.Append(e); });
                    string errorMessage = sb.ToString();
                    resultMessage += errorMessage;
                    throw new Exception(errorMessage);
                }

                context.Result = resultMessage;
            }
            catch (System.Exception ex)
            {
                HttpContext context2 = HttpContext.Current;
                ExceptionLogService.LogException(ex, context2);
                throw;
            }
        }
Exemple #4
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute(IJobExecutionContext context)
        {
            var dataMap        = context.JobDetail.JobDataMap;
            var commandTimeout = dataMap.GetString(AttributeKey.CommandTimeout).AsIntegerOrNull() ?? 300;
            var metricSourceValueTypeDataviewGuid = Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_DATAVIEW.AsGuid();
            var metricSourceValueTypeSqlGuid      = Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_SQL.AsGuid();
            var metricSourceValueTypeLavaGuid     = Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_LAVA.AsGuid();

            Guid[] calculatedSourceTypes = new Guid[] {
                metricSourceValueTypeDataviewGuid,
                metricSourceValueTypeSqlGuid,
                metricSourceValueTypeLavaGuid
            };

            var metricsQry = new MetricService(new RockContext()).Queryable().AsNoTracking().Where(
                a => a.ScheduleId.HasValue &&
                a.SourceValueTypeId.HasValue &&
                calculatedSourceTypes.Contains(a.SourceValueType.Guid));

            var metricIdList = metricsQry.OrderBy(a => a.Title).ThenBy(a => a.Subtitle).Select(a => a.Id).ToList();

            var    metricExceptions       = new List <Exception>();
            int    metricsCalculated      = 0;
            int    metricValuesCalculated = 0;
            Metric metric = null;

            foreach (var metricId in metricIdList)
            {
                try
                {
                    using (var rockContextForMetricEntity = new RockContext())
                    {
                        rockContextForMetricEntity.Database.CommandTimeout = commandTimeout;
                        var metricService = new MetricService(rockContextForMetricEntity);

                        metric = metricService.Get(metricId);

                        /*  Get the last time the metric has run, if it has never run then set it as the first day of the current week to give it a chance to run now.
                         *  NOTE: This is being used instead of Min Date to prevent a schedule with a start date of months or years back from running for each period since then.
                         *  This would be the case for the "out-of-the-box" Rock daily metrics "Active Records", "Active Families", and "Active Connection Requests" if they
                         *  have never run before. Or if a new user created metric uses a schedule that has an older start date.
                         */
                        DateTime currentDateTime = RockDateTime.Now;
                        DateTime?startOfWeek     = currentDateTime.StartOfWeek(RockDateTime.FirstDayOfWeek);
                        DateTime?lastRunDateTime = metric.LastRunDateTime ?? startOfWeek;

                        // get all the schedule times that were supposed to run since that last time it was scheduled to run
                        var scheduledDateTimesToProcess = metric.Schedule.GetScheduledStartTimes(lastRunDateTime.Value, currentDateTime).Where(a => a > lastRunDateTime.Value).ToList();
                        foreach (var scheduleDateTime in scheduledDateTimesToProcess)
                        {
                            using (var rockContextForMetricValues = new RockContext())
                            {
                                rockContextForMetricValues.Database.CommandTimeout = commandTimeout;
                                var metricPartitions                     = new MetricPartitionService(rockContextForMetricValues).Queryable().Where(a => a.MetricId == metric.Id).ToList();
                                var metricValueService                   = new MetricValueService(rockContextForMetricValues);
                                List <ResultValue> resultValues          = new List <ResultValue>();
                                bool getMetricValueDateTimeFromResultSet = false;
                                if (metric.SourceValueType.Guid == metricSourceValueTypeDataviewGuid)
                                {
                                    // get the metric value from the DataView
                                    if (metric.DataView != null)
                                    {
                                        bool parseCampusPartition = metricPartitions.Count == 1 &&
                                                                    metric.AutoPartitionOnPrimaryCampus &&
                                                                    metric.DataView.EntityTypeId == Web.Cache.EntityTypeCache.GetId(SystemGuid.EntityType.PERSON) &&
                                                                    metricPartitions[0].EntityTypeId == Web.Cache.EntityTypeCache.GetId(SystemGuid.EntityType.CAMPUS);

                                        // Dataview metrics can be partitioned by campus only and AutoPartitionOnPrimaryCampus must be selected.
                                        if (metricPartitions.Count > 1 ||
                                            (metricPartitions[0].EntityTypeId.HasValue && parseCampusPartition == false))
                                        {
                                            throw new NotImplementedException("Partitioned Metrics using DataViews is only supported for Person data views using a single partition of type 'Campus' with 'Auto Partition on Primary Campus' checked. Any other dataview partition configuration is not supported.");
                                        }

                                        Stopwatch stopwatch = Stopwatch.StartNew();
                                        var       qry       = metric.DataView.GetQuery(new DataViewGetQueryArgs());

                                        if (parseCampusPartition)
                                        {
                                            // Create a dictionary of campus IDs with a person count
                                            var campusPartitionValues = new Dictionary <int, int>();
                                            foreach (var person in qry.ToList())
                                            {
                                                var iPerson  = ( Person )person;
                                                var campusId = iPerson.GetCampus()?.Id ?? -1;
                                                campusPartitionValues.TryGetValue(campusId, out var currentCount);
                                                campusPartitionValues[campusId] = currentCount + 1;
                                            }

                                            // Use the dictionary to create the ResultValues for each campus (partition)
                                            foreach (var campusPartitionValue in campusPartitionValues)
                                            {
                                                var resultValue = new ResultValue
                                                {
                                                    MetricValueDateTime = scheduleDateTime,
                                                    Partitions          = new List <ResultValuePartition>(),
                                                    Value = campusPartitionValue.Value
                                                };

                                                int?entityId = campusPartitionValue.Key;
                                                if (entityId == -1)
                                                {
                                                    entityId = null;
                                                }

                                                resultValue.Partitions.Add(new ResultValuePartition
                                                {
                                                    PartitionPosition = 0,
                                                    EntityId          = entityId
                                                });

                                                resultValues.Add(resultValue);
                                            }
                                        }
                                        else
                                        {
                                            // Put the entire set in one result since there is no partition
                                            resultValues.Add(new ResultValue
                                            {
                                                Value = Convert.ToDecimal(qry.Count()),
                                                MetricValueDateTime = scheduleDateTime,
                                                Partitions          = new List <ResultValuePartition>()
                                            });
                                        }

                                        stopwatch.Stop();
                                        DataViewService.AddRunDataViewTransaction(metric.DataView.Id, Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds));
                                    }
                                }
                                else if (metric.SourceValueType.Guid == metricSourceValueTypeSqlGuid)
                                {
                                    //// calculate the metricValue using the results from the SQL
                                    //// assume SQL is in one of the following forms:
                                    //// -- "SELECT Count(*) FROM ..."
                                    //// -- "SELECT Count(*), [MetricValueDateTime] FROM ..."
                                    //// -- "SELECT Count(*), Partition0EntityId, Partition1EntityId, Partition2EntityId,.. FROM ..."
                                    //// -- "SELECT Count(*), [MetricValueDateTime], Partition0EntityId, Partition1EntityId, Partition2EntityId,.. FROM ..."
                                    if (!string.IsNullOrWhiteSpace(metric.SourceSql))
                                    {
                                        string formattedSql = metric.SourceSql.ResolveMergeFields(metric.GetMergeObjects(scheduleDateTime));
                                        var    tableResult  = DbService.GetDataTable(formattedSql, System.Data.CommandType.Text, null);

                                        if (tableResult.Columns.Count >= 2 && tableResult.Columns[1].ColumnName == "MetricValueDateTime")
                                        {
                                            getMetricValueDateTimeFromResultSet = true;
                                        }

                                        foreach (var row in tableResult.Rows.OfType <System.Data.DataRow>())
                                        {
                                            var resultValue = new ResultValue();

                                            resultValue.Value = Convert.ToDecimal(row[0] == DBNull.Value ? 0 : row[0]);
                                            if (getMetricValueDateTimeFromResultSet)
                                            {
                                                resultValue.MetricValueDateTime = Convert.ToDateTime(row[1]);
                                            }
                                            else
                                            {
                                                resultValue.MetricValueDateTime = scheduleDateTime;
                                            }

                                            resultValue.Partitions = new List <ResultValuePartition>();
                                            int partitionPosition    = 0;
                                            int partitionFieldIndex  = getMetricValueDateTimeFromResultSet ? 2 : 1;
                                            int partitionColumnCount = tableResult.Columns.Count - 1;
                                            while (partitionFieldIndex <= partitionColumnCount)
                                            {
                                                resultValue.Partitions.Add(new ResultValuePartition
                                                {
                                                    PartitionPosition = partitionPosition,
                                                    EntityId          = row[partitionFieldIndex] as int?
                                                });

                                                partitionPosition++;
                                                partitionFieldIndex++;
                                            }

                                            resultValues.Add(resultValue);
                                        }
                                    }
                                }
                                else if (metric.SourceValueType.Guid == metricSourceValueTypeLavaGuid)
                                {
                                    //// calculate the metricValue using the results from Lava
                                    //// assume Lava Output is in one of the following forms:
                                    //// A single Count
                                    //// 42
                                    //// A List of Count, MetricValueDateTime
                                    //// 42, 1/1/2017
                                    //// 40, 1/2/2017
                                    //// 49, 1/3/2017
                                    //// A List of Count, Partition0EntityId, Partition1EntityId, Partition2EntityId
                                    //// 42, 201, 450, 654
                                    //// 42, 202, 450, 654
                                    //// 42, 203, 450, 654
                                    //// A List of Count, MetricValueDateTime,  Partition0EntityId, Partition1EntityId, Partition2EntityId
                                    //// 42, 1/1/2017, 201, 450, 654
                                    //// 42, 1/2/2017, 202, 450, 654
                                    //// 42, 1/3/2017, 203, 450, 654
                                    if (!string.IsNullOrWhiteSpace(metric.SourceLava))
                                    {
                                        var           mergeObjects = metric.GetMergeObjects(scheduleDateTime);
                                        string        lavaResult   = metric.SourceLava.ResolveMergeFields(mergeObjects, enabledLavaCommands: "All", throwExceptionOnErrors: true);
                                        List <string> resultLines  = lavaResult.Split(new string[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)
                                                                     .Select(a => a.Trim()).Where(a => !string.IsNullOrEmpty(a)).ToList();
                                        List <string[]> resultList = resultLines.Select(a => a.SplitDelimitedValues()).ToList();

                                        if (resultList.Any())
                                        {
                                            if (resultList[0].Length >= 2)
                                            {
                                                // if the value of the data in the 2nd column is a Date, assume that is is the MetricValueDateTime
                                                if (resultList[0][1].AsDateTime().HasValue)
                                                {
                                                    getMetricValueDateTimeFromResultSet = true;
                                                }
                                            }
                                        }

                                        foreach (var row in resultList)
                                        {
                                            var resultValue = new ResultValue();

                                            resultValue.Value = row[0].AsDecimal();
                                            if (getMetricValueDateTimeFromResultSet)
                                            {
                                                resultValue.MetricValueDateTime = row[1].AsDateTime() ?? scheduleDateTime;
                                            }
                                            else
                                            {
                                                resultValue.MetricValueDateTime = scheduleDateTime;
                                            }

                                            resultValue.Partitions = new List <ResultValuePartition>();
                                            int partitionPosition    = 0;
                                            int partitionFieldIndex  = getMetricValueDateTimeFromResultSet ? 2 : 1;
                                            int partitionColumnCount = row.Length - 1;
                                            while (partitionFieldIndex <= partitionColumnCount)
                                            {
                                                resultValue.Partitions.Add(new ResultValuePartition
                                                {
                                                    PartitionPosition = partitionPosition,
                                                    EntityId          = row[partitionFieldIndex].AsIntegerOrNull()
                                                });

                                                partitionPosition++;
                                                partitionFieldIndex++;
                                            }

                                            resultValues.Add(resultValue);
                                        }
                                    }
                                }

                                metric.LastRunDateTime = scheduleDateTime;
                                metricsCalculated++;
                                metricValuesCalculated += resultValues.Count();

                                if (resultValues.Any())
                                {
                                    List <MetricValue> metricValuesToAdd = new List <MetricValue>();
                                    foreach (var resultValue in resultValues)
                                    {
                                        var metricValue = new MetricValue();
                                        metricValue.MetricId            = metric.Id;
                                        metricValue.MetricValueDateTime = resultValue.MetricValueDateTime;
                                        metricValue.MetricValueType     = MetricValueType.Measure;
                                        metricValue.YValue                = resultValue.Value;
                                        metricValue.CreatedDateTime       = RockDateTime.Now;
                                        metricValue.ModifiedDateTime      = RockDateTime.Now;
                                        metricValue.MetricValuePartitions = new List <MetricValuePartition>();
                                        var metricPartitionsByPosition = metricPartitions.OrderBy(a => a.Order).ToList();

                                        if (!resultValue.Partitions.Any() && metricPartitionsByPosition.Count() == 1 && !metricPartitionsByPosition[0].EntityTypeId.HasValue)
                                        {
                                            // a metric with just the default partition (not partitioned by Entity)
                                            var metricPartition      = metricPartitionsByPosition[0];
                                            var metricValuePartition = new MetricValuePartition();
                                            metricValuePartition.MetricPartition   = metricPartition;
                                            metricValuePartition.MetricPartitionId = metricPartition.Id;
                                            metricValuePartition.MetricValue       = metricValue;
                                            metricValuePartition.EntityId          = null;
                                            metricValue.MetricValuePartitions.Add(metricValuePartition);
                                        }
                                        else
                                        {
                                            foreach (var partitionResult in resultValue.Partitions)
                                            {
                                                if (metricPartitionsByPosition.Count > partitionResult.PartitionPosition)
                                                {
                                                    var metricPartition      = metricPartitionsByPosition[partitionResult.PartitionPosition];
                                                    var metricValuePartition = new MetricValuePartition();
                                                    metricValuePartition.MetricPartition   = metricPartition;
                                                    metricValuePartition.MetricPartitionId = metricPartition.Id;
                                                    metricValuePartition.MetricValue       = metricValue;
                                                    metricValuePartition.EntityId          = partitionResult.EntityId;
                                                    metricValue.MetricValuePartitions.Add(metricValuePartition);
                                                }
                                            }
                                        }

                                        if (metricValue.MetricValuePartitions == null || !metricValue.MetricValuePartitions.Any())
                                        {
                                            // shouldn't happen, but just in case
                                            throw new Exception("MetricValue requires at least one Partition Value");
                                        }
                                        else
                                        {
                                            metricValuesToAdd.Add(metricValue);
                                        }
                                    }

                                    // if a single metricValueDateTime was specified, delete any existing metric values for this date and refresh with the current results
                                    var dbTransaction = rockContextForMetricValues.Database.BeginTransaction();
                                    if (getMetricValueDateTimeFromResultSet)
                                    {
                                        var metricValueDateTimes = metricValuesToAdd.Select(a => a.MetricValueDateTime).Distinct().ToList();
                                        foreach (var metricValueDateTime in metricValueDateTimes)
                                        {
                                            bool alreadyHasMetricValues = metricValueService.Queryable().Where(a => a.MetricId == metric.Id && a.MetricValueDateTime == metricValueDateTime).Any();
                                            if (alreadyHasMetricValues)
                                            {
                                                // use direct SQL to clean up any existing metric values
                                                rockContextForMetricValues.Database.ExecuteSqlCommand(@"
                                                    DELETE
                                                    FROM MetricValuePartition
                                                    WHERE MetricValueId IN (
                                                        SELECT Id
                                                        FROM MetricValue
                                                        WHERE MetricId = @metricId
                                                        AND MetricValueDateTime = @metricValueDateTime
                                                    )
                                                ", new SqlParameter("@metricId", metric.Id), new SqlParameter("@metricValueDateTime", metricValueDateTime));

                                                rockContextForMetricValues.Database.ExecuteSqlCommand(@"
                                                    DELETE
                                                    FROM MetricValue
                                                    WHERE MetricId = @metricId
                                                    AND MetricValueDateTime = @metricValueDateTime
                                                ", new SqlParameter("@metricId", metric.Id), new SqlParameter("@metricValueDateTime", metricValueDateTime));
                                            }
                                        }
                                    }

                                    metricValueService.AddRange(metricValuesToAdd);

                                    // disable savechanges PrePostProcessing since there could be hundreds or thousands of metric values getting inserted/updated
                                    rockContextForMetricValues.SaveChanges(true);
                                    dbTransaction.Commit();
                                }

                                rockContextForMetricEntity.SaveChanges();
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    metricExceptions.Add(new Exception($"Exception when calculating metric for {metric}: {ex.Message}", ex));
                }
            }

            context.Result = string.Format("Calculated a total of {0} metric values for {1} metrics", metricValuesCalculated, metricsCalculated);

            if (metricExceptions.Any())
            {
                throw new AggregateException("One or more metric calculations failed ", metricExceptions);
            }
        }
Exemple #5
0
        /// <summary>
        /// Perform the job using the parameters supplied in the execution context.
        /// </summary>
        /// <param name="context"></param>
        public void Execute(IJobExecutionContext context)
        {
            // Get the configuration settings for this job instance.
            var dataMap = context.JobDetail.JobDataMap;

            var workflowTypeGuid = dataMap.GetString(AttributeKey.Workflow).AsGuidOrNull();
            var dataViewGuid     = dataMap.GetString(AttributeKey.DataView).AsGuidOrNull();

            if (dataViewGuid == null)
            {
                throw new Exception("Data view not selected");
            }

            var rockContext     = new RockContext();
            var dataViewService = new DataViewService(rockContext);
            var dataView        = dataViewService.Get(dataViewGuid.Value);

            if (dataView == null)
            {
                throw new Exception("Data view not found");
            }

            // Get the set of entity key values returned by the Data View.
            var errorMessages = new List <string>();

            Stopwatch stopwatch = Stopwatch.StartNew();
            var       qry       = dataView.GetQuery(null, rockContext, null, out errorMessages);

            var modelType = dataView.EntityType.GetType();

            if (qry == null)
            {
                throw new Exception("Data view results not found");
            }

            if (modelType == null)
            {
                throw new Exception("Entity type of data view not found");
            }

            var entityIds = qry.Select(e => e.Id).ToList();

            stopwatch.Stop();
            DataViewService.AddRunDataViewTransaction(dataView.Id,
                                                      Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds));

            var entityTypeId   = dataView.EntityTypeId.Value;
            var entityTypeName = modelType.GetFriendlyTypeName();

            int workflowsLaunched = 0;

            // For each entity, create a new transaction to launch a workflow.
            foreach (var entityId in entityIds)
            {
                var transaction = new LaunchEntityWorkflowTransaction(workflowTypeGuid.Value, string.Empty, entityTypeId, entityId);

                Rock.Transactions.RockQueue.TransactionQueue.Enqueue(transaction);

                workflowsLaunched++;
            }

            context.Result = string.Format("{0} workflows launched", workflowsLaunched);
        }