Beispiel #1
0
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            int? metricId = hfMetricId.Value.AsIntegerOrNull();

            if ( !metricId.HasValue )
            {
                return;
            }

            var rockContext = new RockContext();
            SortProperty sortProperty = gMetricValues.SortProperty;
            MetricValueService metricValueService = new MetricValueService( rockContext );
            var qry = metricValueService.Queryable();

            qry = qry.Where( a => a.MetricId == metricId );

            var entityColumn = gMetricValues.Columns.OfType<BoundField>().FirstOrDefault( a => a.DataField == "EntityId" );
            var metric = new MetricService( rockContext ).Get( metricId ?? 0 );
            entityColumn.Visible = metric != null && metric.EntityTypeId.HasValue;

            if ( metric != null )
            {
                _entityNameLookup = new Dictionary<int, string>();

                var entityTypeCache = EntityTypeCache.Read( metric.EntityTypeId ?? 0 );
                if ( entityTypeCache != null )
                {
                    entityColumn.HeaderText = entityTypeCache.FriendlyName;
                    IQueryable<IEntity> entityQry = null;
                    if ( entityTypeCache.GetEntityType() == typeof( Rock.Model.Group ) )
                    {
                        // special case for Group since there could be a very large number (especially if you include families), so limit to GroupType.ShowInGroupList
                        entityQry = new GroupService( rockContext ).Queryable().Where( a => a.GroupType.ShowInGroupList );
                    }
                    else
                    {
                        Type[] modelType = { entityTypeCache.GetEntityType() };
                        Type genericServiceType = typeof( Rock.Data.Service<> );
                        Type modelServiceType = genericServiceType.MakeGenericType( modelType );
                        var serviceInstance = Activator.CreateInstance( modelServiceType, new object[] { rockContext } ) as IService;
                        MethodInfo qryMethod = serviceInstance.GetType().GetMethod( "Queryable", new Type[] { } );
                        entityQry = qryMethod.Invoke( serviceInstance, new object[] { } ) as IQueryable<IEntity>;
                    }

                    if ( entityQry != null )
                    {
                        var entityList = entityQry.ToList();
                        foreach ( var e in entityList )
                        {
                            _entityNameLookup.AddOrReplace( e.Id, e.ToString() );
                        }
                    }
                }
            }

            var drp = new DateRangePicker();
            drp.DelimitedValues = gfMetricValues.GetUserPreference( "Date Range" );
            if ( drp.LowerValue.HasValue )
            {
                qry = qry.Where( a => a.MetricValueDateTime >= drp.LowerValue.Value );
            }

            if ( drp.UpperValue.HasValue )
            {
                DateTime upperDate = drp.UpperValue.Value.Date.AddDays( 1 );
                qry = qry.Where( a => a.MetricValueDateTime < upperDate );
            }

            var metricValueType = gfMetricValues.GetUserPreference( "Goal/Measure" ).ConvertToEnumOrNull<MetricValueType>();
            if ( metricValueType.HasValue )
            {
                qry = qry.Where( a => a.MetricValueType == metricValueType.Value );
            }

            var entityParts = gfMetricValues.GetUserPreference( this.EntityPreferenceKey ).Split( '|' );
            if ( entityParts.Length == 2 )
            {
                if ( entityParts[0].AsInteger() == metric.EntityTypeId )
                {
                    var entityId = entityParts[1].AsIntegerOrNull();
                    if ( entityId.HasValue )
                    {
                        qry = qry.Where( a => a.EntityId == entityId );
                    }
                }
            }

            if ( sortProperty != null )
            {
                gMetricValues.DataSource = qry.Sort( sortProperty ).ToList();
            }
            else
            {
                gMetricValues.DataSource = qry.OrderBy( s => s.Order ).ThenBy( s => s.MetricValueDateTime ).ThenBy( s => s.YValue ).ThenBy( s => s.XValue ).ToList();
            }

            gMetricValues.DataBind();
        }
Beispiel #2
0
        /// <summary>
        /// Handles the Click event of the btnSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void btnSave_Click( object sender, EventArgs e )
        {
            Metric metric;

            var rockContext = new RockContext();
            MetricService metricService = new MetricService( rockContext );
            MetricCategoryService metricCategoryService = new MetricCategoryService( rockContext );
            MetricValueService metricValueService = new MetricValueService( rockContext );
            bool deleteValuesOnSave = sender == btnDeleteValuesAndSave;

            int metricId = hfMetricId.Value.AsInteger();

            if ( metricId == 0 )
            {
                metric = new Metric();
            }
            else
            {
                metric = metricService.Get( metricId );
            }

            metric.Title = tbTitle.Text;
            metric.Subtitle = tbSubtitle.Text;
            metric.Description = tbDescription.Text;
            metric.IconCssClass = tbIconCssClass.Text;
            metric.SourceValueTypeId = ddlSourceType.SelectedValueAsId();
            metric.XAxisLabel = tbXAxisLabel.Text;
            metric.YAxisLabel = tbYAxisLabel.Text;
            metric.IsCumulative = cbIsCumulative.Checked;

            var origEntityType = metric.EntityTypeId.HasValue ? EntityTypeCache.Read( metric.EntityTypeId.Value ) : null;
            var newEntityType = etpEntityType.SelectedEntityTypeId.HasValue ? EntityTypeCache.Read( etpEntityType.SelectedEntityTypeId.Value ) : null;
            if ( origEntityType != null && !deleteValuesOnSave )
            {
                if ( newEntityType == null || newEntityType.Id != origEntityType.Id )
                {
                    // if the EntityTypeId of this metric has changed to NULL or to another EntityType, warn about the EntityId values being wrong
                    bool hasEntityValues = metricValueService.Queryable().Any( a => a.MetricId == metric.Id && a.EntityId.HasValue );

                    if ( hasEntityValues )
                    {
                        nbEntityTypeChanged.Text = string.Format(
                            "Warning: You can't change the series partition to {0} when there are values associated with {1}. Do you want to delete existing values?",
                            newEntityType != null ? newEntityType.FriendlyName : "<none>",
                            origEntityType.FriendlyName );
                        mdEntityTypeChanged.Show();
                        nbEntityTypeChanged.Visible = true;
                        return;
                    }
                }
            }

            metric.EntityTypeId = etpEntityType.SelectedEntityTypeId;

            int sourceTypeDataView = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_DATAVIEW.AsGuid() ).Id;
            int sourceTypeSQL = DefinedValueCache.Read( Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_SQL.AsGuid() ).Id;

            var personService = new PersonService( rockContext );
            var metricChampionPerson = personService.Get( ppMetricChampionPerson.SelectedValue ?? 0 );
            metric.MetricChampionPersonAliasId = metricChampionPerson != null ? metricChampionPerson.PrimaryAliasId : null;
            var adminPerson = personService.Get( ppAdminPerson.SelectedValue ?? 0 );
            metric.AdminPersonAliasId = adminPerson != null ? adminPerson.PrimaryAliasId : null;

            if ( metric.SourceValueTypeId == sourceTypeSQL )
            {
                metric.SourceSql = ceSourceSql.Text;
            }
            else
            {
                metric.SourceSql = string.Empty;
            }

            if ( metric.SourceValueTypeId == sourceTypeDataView )
            {
                metric.DataViewId = ddlDataView.SelectedValueAsId();
            }
            else
            {
                metric.DataViewId = null;
            }

            var scheduleSelectionType = rblScheduleSelect.SelectedValueAsEnum<ScheduleSelectionType>();
            if ( scheduleSelectionType == ScheduleSelectionType.NamedSchedule )
            {
                metric.ScheduleId = ddlSchedule.SelectedValueAsId();
            }
            else
            {
                metric.ScheduleId = hfUniqueScheduleId.ValueAsInt();
            }

            if ( !Page.IsValid )
            {
                return;
            }

            if ( !metric.IsValid )
            {
                // Controls will render the error messages
                return;
            }

            if ( !cpMetricCategories.SelectedValuesAsInt().Any() )
            {
                cpMetricCategories.ShowErrorMessage( "Must select at least one category" );
                return;
            }

            // do a WrapTransaction since we are doing multiple SaveChanges()
            rockContext.WrapTransaction( () =>
            {
                var scheduleService = new ScheduleService( rockContext );
                var schedule = scheduleService.Get( metric.ScheduleId ?? 0 );
                int metricScheduleCategoryId = new CategoryService( rockContext ).Get( Rock.SystemGuid.Category.SCHEDULE_METRICS.AsGuid() ).Id;
                if ( schedule == null )
                {
                    schedule = new Schedule();

                    // make it an "Unnamed" metrics schedule
                    schedule.Name = string.Empty;
                    schedule.CategoryId = metricScheduleCategoryId;
                }

                // if the schedule was a unique schedule (configured in the Metric UI, set the schedule's ical content to the schedule builder UI's value
                if ( scheduleSelectionType == ScheduleSelectionType.Unique )
                {
                    schedule.iCalendarContent = sbSchedule.iCalendarContent;
                }

                if ( !schedule.HasSchedule() && scheduleSelectionType == ScheduleSelectionType.Unique )
                {
                    // don't save as a unique schedule if the schedule doesn't do anything
                    schedule = null;
                }
                else
                {
                    if ( schedule.Id == 0 )
                    {
                        scheduleService.Add( schedule );

                        // save to make sure we have a scheduleId
                        rockContext.SaveChanges();
                    }
                }

                if ( schedule != null )
                {
                    metric.ScheduleId = schedule.Id;
                }
                else
                {
                    metric.ScheduleId = null;
                }

                if ( metric.Id == 0 )
                {
                    metricService.Add( metric );

                    // save to make sure we have a metricId
                    rockContext.SaveChanges();
                }

                // update MetricCategories for Metric
                metric.MetricCategories = metric.MetricCategories ?? new List<MetricCategory>();
                var selectedCategoryIds = cpMetricCategories.SelectedValuesAsInt();

                // delete any categories that were removed
                foreach ( var metricCategory in metric.MetricCategories.ToList() )
                {
                    if ( !selectedCategoryIds.Contains( metricCategory.CategoryId ) )
                    {
                        metricCategoryService.Delete( metricCategory );
                    }
                }

                // add any categories that were added
                foreach ( int categoryId in selectedCategoryIds )
                {
                    if ( !metric.MetricCategories.Any( a => a.CategoryId == categoryId ) )
                    {
                        metricCategoryService.Add( new MetricCategory { CategoryId = categoryId, MetricId = metric.Id } );
                    }
                }

                rockContext.SaveChanges();

                // delete MetricValues associated with the old entityType if they confirmed the EntityType change
                if ( deleteValuesOnSave )
                {
                    metricValueService.DeleteRange( metricValueService.Queryable().Where( a => a.MetricId == metric.Id && a.EntityId.HasValue ) );

                    // since there could be 1000s of values that got deleted, do a SaveChanges that skips PrePostProcessing
                    rockContext.SaveChanges( true );
                }

                // delete any orphaned Unnamed metric schedules
                var metricIdSchedulesQry = metricService.Queryable().Select( a => a.ScheduleId );
                int? metricScheduleId = schedule != null ? schedule.Id : (int?)null;
                var orphanedSchedules = scheduleService.Queryable()
                    .Where( a => a.CategoryId == metricScheduleCategoryId && a.Name == string.Empty && a.Id != ( metricScheduleId ?? 0 ) )
                    .Where( s => !metricIdSchedulesQry.Any( m => m == s.Id ) );
                foreach ( var item in orphanedSchedules )
                {
                    scheduleService.Delete( item );
                }

                if ( orphanedSchedules.Any() )
                {
                    rockContext.SaveChanges();
                }
            } );

            var qryParams = new Dictionary<string, string>();
            qryParams["MetricId"] = metric.Id.ToString();
            if ( hfMetricCategoryId.ValueAsInt() == 0 )
            {
                int? parentCategoryId = PageParameter( "ParentCategoryId" ).AsIntegerOrNull();
                int? metricCategoryId = new MetricCategoryService( new RockContext() ).Queryable().Where( a => a.MetricId == metric.Id && a.CategoryId == parentCategoryId ).Select( a => a.Id ).FirstOrDefault();
                hfMetricCategoryId.Value = metricCategoryId.ToString();
            }

            qryParams["MetricCategoryId"] = hfMetricCategoryId.Value;
            qryParams["ExpandedIds"] = PageParameter( "ExpandedIds" );

            NavigateToPage( RockPage.Guid, qryParams );
        }
Beispiel #3
0
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            int? metricId = hfMetricId.Value.AsIntegerOrNull();

            if ( !metricId.HasValue )
            {
                return;
            }

            var rockContext = new RockContext();
            SortProperty sortProperty = gMetricValues.SortProperty;
            MetricValueService metricValueService = new MetricValueService( rockContext );
            var qry = metricValueService.Queryable().Include( a => a.MetricValuePartitions );

            qry = qry.Where( a => a.MetricId == metricId );

            var metricValuePartitionsColumn = gMetricValues.Columns.OfType<RockLiteralField>().FirstOrDefault( a => a.ID == "lMetricValuePartitions" );
            var metric = new MetricService( rockContext ).Get( metricId ?? 0 );
            metricValuePartitionsColumn.Visible = metric != null && metric.MetricPartitions.Any( a => a.EntityTypeId.HasValue );

            var drp = new DateRangePicker();
            drp.DelimitedValues = gfMetricValues.GetUserPreference( "Date Range" );
            if ( drp.LowerValue.HasValue )
            {
                qry = qry.Where( a => a.MetricValueDateTime >= drp.LowerValue.Value );
            }

            if ( drp.UpperValue.HasValue )
            {
                DateTime upperDate = drp.UpperValue.Value.Date.AddDays( 1 );
                qry = qry.Where( a => a.MetricValueDateTime < upperDate );
            }

            var metricValueType = gfMetricValues.GetUserPreference( "Goal/Measure" ).ConvertToEnumOrNull<MetricValueType>();
            if ( metricValueType.HasValue )
            {
                qry = qry.Where( a => a.MetricValueType == metricValueType.Value );
            }

            var entityTypeEntityUserPreference = gfMetricValues.GetUserPreference( this.EntityTypeEntityPreferenceKey ) ?? string.Empty;

            var entityTypeEntityList = entityTypeEntityUserPreference.Split( ',' ).Select( a => a.Split( '|' ) ).Where( a => a.Length == 2 ).Select( a =>
                new
                {
                    EntityTypeId = a[0].AsIntegerOrNull(),
                    EntityId = a[1].AsIntegerOrNull()
                } );

            foreach ( var entityTypeEntity in entityTypeEntityList )
            {
                if ( entityTypeEntity.EntityTypeId.HasValue && entityTypeEntity.EntityId.HasValue )
                {
                    qry = qry.Where( a => a.MetricValuePartitions.Any( x => x.MetricPartition.EntityTypeId == entityTypeEntity.EntityTypeId && x.EntityId == entityTypeEntity.EntityId ) );
                }
            }

            if ( sortProperty != null )
            {
                qry = qry.Sort( sortProperty );
            }
            else
            {
                qry = qry.OrderBy( s => s.MetricValueDateTime ).ThenBy( s => s.YValue ).ThenBy( s => s.XValue ).ThenByDescending( s => s.ModifiedDateTime );
            }

            gMetricValues.SetLinqDataSource( qry );

            gMetricValues.DataBind();
        }
        public string GetHtmlForBlock( int blockId, int? entityTypeId = null, int? entityId = null )
        {
            RockContext rockContext = this.Service.Context as RockContext ?? new RockContext();
            Block block = new BlockService( rockContext ).Get( blockId );
            if ( block != null )
            {
                block.LoadAttributes();

                string liquidTemplate = block.GetAttributeValue( "LiquidTemplate" );

                var metricCategoryPairList = Rock.Attribute.MetricCategoriesFieldAttribute.GetValueAsGuidPairs( block.GetAttributeValue( "MetricCategories" ) );

                var metricGuids = metricCategoryPairList.Select( a => a.MetricGuid ).ToList();

                bool roundYValues = block.GetAttributeValue( "RoundValues" ).AsBooleanOrNull() ?? true;

                MetricService metricService = new MetricService( rockContext );
                var metrics = metricService.GetByGuids( metricGuids );
                List<object> metricsData = new List<object>();

                if ( metrics.Count() == 0 )
                {
                    return @"<div class='alert alert-warning'> 
								Please select a metric in the block settings.
							</div>";
                }

                MetricValueService metricValueService = new MetricValueService( rockContext );

                DateTime firstDayOfYear = new DateTime( RockDateTime.Now.Year, 1, 1 );
                DateTime currentDateTime = RockDateTime.Now;
                DateTime firstDayOfNextYear = new DateTime( RockDateTime.Now.Year + 1, 1, 1 );

                foreach ( var metric in metrics )
                {
                    var metricYTDData = JsonConvert.DeserializeObject( metric.ToJson(), typeof( MetricYTDData ) ) as MetricYTDData;
                    var qryMeasureValues = metricValueService.Queryable()
                        .Where( a => a.MetricId == metricYTDData.Id )
                        .Where( a => a.MetricValueDateTime >= firstDayOfYear && a.MetricValueDateTime < currentDateTime )
                        .Where( a => a.MetricValueType == MetricValueType.Measure );

                    //// if an entityTypeId/EntityId filter was specified, and the entityTypeId is the same as the metrics.EntityTypeId, filter the values to the specified entityId
                    //// Note: if a Metric or it's Metric Value doesn't have a context, include it regardless of Context setting
                    if ( entityTypeId.HasValue && ( metric.EntityTypeId == entityTypeId || metric.EntityTypeId == null ) )
                    {
                        if ( entityId.HasValue )
                        {
                            qryMeasureValues = qryMeasureValues.Where( a => a.EntityId == entityId || a.EntityId == null );
                        }
                    }

                    var lastMetricValue = qryMeasureValues.OrderByDescending( a => a.MetricValueDateTime ).FirstOrDefault();
                    if ( lastMetricValue != null )
                    {
                        metricYTDData.LastValue = lastMetricValue.YValue.HasValue ? Math.Round( lastMetricValue.YValue.Value, roundYValues ? 0 : 2 ) : (decimal?)null;
                        metricYTDData.LastValueDate = lastMetricValue.MetricValueDateTime.HasValue ? lastMetricValue.MetricValueDateTime.Value.Date : DateTime.MinValue;
                    }

                    decimal? sum = qryMeasureValues.Sum( a => a.YValue );
                    metricYTDData.CumulativeValue = sum.HasValue ? Math.Round( sum.Value, roundYValues ? 0 : 2 ) : (decimal?)null;

                    // figure out goal as of current date time by figuring out the slope of the goal
                    var qryGoalValuesCurrentYear = metricValueService.Queryable()
                        .Where( a => a.MetricId == metricYTDData.Id )
                        .Where( a => a.MetricValueDateTime >= firstDayOfYear && a.MetricValueDateTime < firstDayOfNextYear )
                        .Where( a => a.MetricValueType == MetricValueType.Goal );

                    // if an entityTypeId/EntityId filter was specified, and the entityTypeId is the same as the metrics.EntityTypeId, filter the values to the specified entityId
                    if ( entityTypeId.HasValue && metric.EntityTypeId == entityTypeId )
                    {
                        if ( entityId.HasValue )
                        {
                            qryGoalValuesCurrentYear = qryGoalValuesCurrentYear.Where( a => a.EntityId == entityId );
                        }
                    }

                    MetricValue goalLineStartPoint = qryGoalValuesCurrentYear.Where( a => a.MetricValueDateTime <= currentDateTime ).OrderByDescending( a => a.MetricValueDateTime ).FirstOrDefault();
                    MetricValue goalLineEndPoint = qryGoalValuesCurrentYear.Where( a => a.MetricValueDateTime >= currentDateTime ).FirstOrDefault();
                    if ( goalLineStartPoint != null && goalLineEndPoint != null )
                    {
                        var changeInX = goalLineEndPoint.DateTimeStamp - goalLineStartPoint.DateTimeStamp;
                        var changeInY = goalLineEndPoint.YValue - goalLineStartPoint.YValue;
                        if ( changeInX != 0 )
                        {
                            decimal? slope = changeInY / changeInX;
                            decimal goalValue = ( ( slope * ( currentDateTime.ToJavascriptMilliseconds() - goalLineStartPoint.DateTimeStamp ) ) + goalLineStartPoint.YValue ).Value;
                            metricYTDData.GoalValue = Math.Round( goalValue, roundYValues ? 0 : 2 );
                        }
                    }
                    else
                    {
                        // if there isn't a both a start goal and end goal within the date range, there wouldn't be a goal line shown in a line chart, so don't display a goal in liquid either
                        metricYTDData.GoalValue = null;
                    }

                    metricsData.Add( metricYTDData.ToLiquid() );
                }

                Dictionary<string, object> mergeValues = new Dictionary<string, object>();
                mergeValues.Add( "Metrics", metricsData );

                string resultHtml = liquidTemplate.ResolveMergeFields( mergeValues );

                // show liquid help for debug
                if ( block.GetAttributeValue( "EnableDebug" ).AsBoolean() )
                {
                    resultHtml += mergeValues.lavaDebugInfo();
                }

                return resultHtml;
            }

            return string.Format(
                @"<div class='alert alert-danger'> 
                    unable to find block_id: {1}
                </div>",
                blockId );
        }
Beispiel #5
0
        public void SaveMetric(DateTime dt, int metric, Decimal value, int campus, string notes, int type)
        {
            int metricValueId = 0;
            RockContext rockContext = new RockContext();
            MetricValue metricValue;
            MetricValueService metricValueService = new MetricValueService(rockContext);

            //Does this metric already exist?
            var existingMetric = metricValueService
                .Queryable(
                    ).FirstOrDefault(a => a.MetricId == metric && a.MetricValueDateTime == dt && a.EntityId == campus);

            if (existingMetric != null)
            {
                metricValueId = existingMetric.Id;
            }

            if (metricValueId == 0)
            {
                metricValue = new MetricValue();
                metricValueService.Add(metricValue);
                metricValue.MetricId = metric;
                metricValue.Metric = metricValue.Metric ?? new MetricService(rockContext).Get(metricValue.MetricId);
            }
            else
            {
                metricValue = metricValueService.Get(metricValueId);
            }

            metricValue.MetricValueType = (MetricValueType)type;
            metricValue.XValue = null;
            metricValue.YValue = value;
            metricValue.Note = notes;
            metricValue.MetricValueDateTime = dt;
            metricValue.EntityId = campus;

            rockContext.SaveChanges();
        }
Beispiel #6
0
        /// <summary>
        /// Executes the specified context.
        /// </summary>
        /// <param name="context">The context.</param>
        public void Execute( IJobExecutionContext context )
        {
            var metricSourceValueTypeDataviewGuid = Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_DATAVIEW.AsGuid();
            var metricSourceValueTypeSqlGuid = Rock.SystemGuid.DefinedValue.METRIC_SOURCE_VALUE_TYPE_SQL.AsGuid();

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

            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() )
                    {
                        var metricService = new MetricService( rockContextForMetricEntity );

                        metric = metricService.Get( metricId );
                        var lastRunDateTime = metric.LastRunDateTime ?? metric.CreatedDateTime ?? metric.ModifiedDateTime;
                        if ( lastRunDateTime.HasValue )
                        {
                            var currentDateTime = RockDateTime.Now;

                            // 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() )
                                {
                                    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 )
                                        {
                                            var errorMessages = new List<string>();
                                            var qry = metric.DataView.GetQuery( null, null, out errorMessages );
                                            if ( metricPartitions.Count > 1 || metricPartitions.First().EntityTypeId.HasValue )
                                            {
                                                throw new NotImplementedException( "Partitioned Metrics using DataViews is not supported." );
                                            }
                                            else
                                            {
                                                var resultValue = new ResultValue();
                                                resultValue.Value = Convert.ToDecimal( qry.Count() );
                                                resultValue.Partitions = new List<ResultValuePartition>();
                                                resultValue.MetricValueDateTime = scheduleDateTime;
                                                resultValues.Add( resultValue );
                                            }
                                        }
                                    }
                                    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(*), Partion0EntityId, Partion1EntityId, Partion2EntityId,.. FROM ..."
                                        //// -- "SELECT Count(*), [MetricValueDateTime], Partion0EntityId, Partion1EntityId, Partion2EntityId,.. 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] );
                                                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 );
                                            }
                                        }
                                    }

                                    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( string.Format( "Exception when calculating metric for {0} ", metric ), 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 );
            }
        }
        /// <summary>
        /// Binds the metrics.
        /// </summary>
        private void BindMetrics()
        {
            var serviceMetricValues = new List<ServiceMetric>();

            int campusEntityTypeId = EntityTypeCache.Read( typeof( Rock.Model.Campus ) ).Id;
            int scheduleEntityTypeId = EntityTypeCache.Read( typeof( Rock.Model.Schedule ) ).Id;

            int? campusId = bddlCampus.SelectedValueAsInt();
            int? scheduleId = bddlService.SelectedValueAsInt();
            DateTime? weekend = bddlWeekend.SelectedValue.AsDateTime();

            var notes = new List<string>();

            if ( campusId.HasValue && scheduleId.HasValue && weekend.HasValue )
            {

                SetBlockUserPreference( "CampusId", campusId.HasValue ? campusId.Value.ToString() : "" );
                SetBlockUserPreference( "ScheduleId", scheduleId.HasValue ? scheduleId.Value.ToString() : "" );

                var metricCategories = MetricCategoriesFieldAttribute.GetValueAsGuidPairs( GetAttributeValue( "MetricCategories" ) );
                var metricGuids = metricCategories.Select( a => a.MetricGuid ).ToList();
                using ( var rockContext = new RockContext() )
                {
                    var metricValueService = new MetricValueService( rockContext );
                    foreach ( var metric in new MetricService( rockContext )
                        .GetByGuids( metricGuids )
                        .Where( m =>
                            m.MetricPartitions.Count == 2 &&
                            m.MetricPartitions.Any( p => p.EntityTypeId.HasValue && p.EntityTypeId.Value == campusEntityTypeId ) &&
                            m.MetricPartitions.Any( p => p.EntityTypeId.HasValue && p.EntityTypeId.Value == scheduleEntityTypeId ) )
                        .OrderBy( m => m.Title )
                        .Select( m => new
                        {
                            m.Id,
                            m.Title,
                            CampusPartitionId = m.MetricPartitions.Where( p => p.EntityTypeId.HasValue && p.EntityTypeId.Value == campusEntityTypeId ).Select( p => p.Id ).FirstOrDefault(),
                            SchedulePartitionId = m.MetricPartitions.Where( p => p.EntityTypeId.HasValue && p.EntityTypeId.Value == scheduleEntityTypeId ).Select( p => p.Id ).FirstOrDefault(),
                        } ) )
                    {
                        var serviceMetric = new ServiceMetric( metric.Id, metric.Title );

                        if ( campusId.HasValue && weekend.HasValue && scheduleId.HasValue )
                        {
                            var metricValue = metricValueService
                                .Queryable().AsNoTracking()
                                .Where( v =>
                                    v.MetricId == metric.Id &&
                                    v.MetricValueDateTime.HasValue && v.MetricValueDateTime.Value == weekend.Value &&
                                    v.MetricValuePartitions.Count == 2 &&
                                    v.MetricValuePartitions.Any( p => p.MetricPartitionId == metric.CampusPartitionId && p.EntityId.HasValue && p.EntityId.Value == campusId.Value ) &&
                                    v.MetricValuePartitions.Any( p => p.MetricPartitionId == metric.SchedulePartitionId && p.EntityId.HasValue && p.EntityId.Value == scheduleId.Value ) )
                                .FirstOrDefault();

                            if ( metricValue != null )
                            {
                                serviceMetric.Value = metricValue.YValue;

                                if ( !string.IsNullOrWhiteSpace ( metricValue.Note) &&
                                    !notes.Contains( metricValue.Note ) )
                                {
                                    notes.Add( metricValue.Note );
                                }

                            }
                        }

                        serviceMetricValues.Add( serviceMetric );
                    }
                }
            }

            rptrMetric.DataSource = serviceMetricValues;
            rptrMetric.DataBind();

            tbNote.Text = notes.AsDelimited( Environment.NewLine + Environment.NewLine );
        }
        /// <summary>
        /// Handles the Click event of the btnSave control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        protected void btnSave_Click( object sender, EventArgs e )
        {
            int campusEntityTypeId = EntityTypeCache.Read( typeof( Rock.Model.Campus ) ).Id;
            int scheduleEntityTypeId = EntityTypeCache.Read( typeof( Rock.Model.Schedule ) ).Id;

            int? campusId = bddlCampus.SelectedValueAsInt();
            int? scheduleId = bddlService.SelectedValueAsInt();
            DateTime? weekend = bddlWeekend.SelectedValue.AsDateTime();

            if ( campusId.HasValue && scheduleId.HasValue && weekend.HasValue )
            {
                using ( var rockContext = new RockContext() )
                {
                    var metricService = new MetricService( rockContext );
                    var metricValueService = new MetricValueService( rockContext );

                    foreach ( RepeaterItem item in rptrMetric.Items )
                    {
                        var hfMetricIId = item.FindControl( "hfMetricId" ) as HiddenField;
                        var nbMetricValue = item.FindControl( "nbMetricValue" ) as NumberBox;

                        if ( hfMetricIId != null && nbMetricValue != null  )
                        {
                            int metricId = hfMetricIId.ValueAsInt();
                            var metric = new MetricService( rockContext ).Get( metricId );

                            if ( metric != null )
                            {
                                int campusPartitionId = metric.MetricPartitions.Where( p => p.EntityTypeId.HasValue && p.EntityTypeId.Value == campusEntityTypeId ).Select( p => p.Id ).FirstOrDefault();
                                int schedulePartitionId = metric.MetricPartitions.Where( p => p.EntityTypeId.HasValue && p.EntityTypeId.Value == scheduleEntityTypeId ).Select( p => p.Id ).FirstOrDefault();

                                var metricValue = metricValueService
                                    .Queryable()
                                    .Where( v =>
                                        v.MetricId == metric.Id &&
                                        v.MetricValueDateTime.HasValue && v.MetricValueDateTime.Value == weekend.Value &&
                                        v.MetricValuePartitions.Count == 2 &&
                                        v.MetricValuePartitions.Any( p => p.MetricPartitionId == campusPartitionId && p.EntityId.HasValue && p.EntityId.Value == campusId.Value ) &&
                                        v.MetricValuePartitions.Any( p => p.MetricPartitionId == schedulePartitionId && p.EntityId.HasValue && p.EntityId.Value == scheduleId.Value ) )
                                    .FirstOrDefault();

                                if ( metricValue == null )
                                {
                                    metricValue = new MetricValue();
                                    metricValue.MetricValueType = MetricValueType.Measure;
                                    metricValue.MetricId = metric.Id;
                                    metricValue.MetricValueDateTime = weekend.Value;
                                    metricValueService.Add( metricValue );

                                    var campusValuePartition = new MetricValuePartition();
                                    campusValuePartition.MetricPartitionId = campusPartitionId;
                                    campusValuePartition.EntityId = campusId.Value;
                                    metricValue.MetricValuePartitions.Add( campusValuePartition );

                                    var scheduleValuePartition = new MetricValuePartition();
                                    scheduleValuePartition.MetricPartitionId = schedulePartitionId;
                                    scheduleValuePartition.EntityId = scheduleId.Value;
                                    metricValue.MetricValuePartitions.Add( scheduleValuePartition );
                                }

                                metricValue.YValue = nbMetricValue.Text.AsDecimalOrNull();
                                metricValue.Note = tbNote.Text;
                            }
                        }
                    }

                    rockContext.SaveChanges();
                }

                nbMetricsSaved.Text = string.Format( "Your metrics for the {0} service on {1} at the {2} Campus have been saved.",
                    bddlService.SelectedItem.Text, bddlWeekend.SelectedItem.Text, bddlCampus.SelectedItem.Text );
                nbMetricsSaved.Visible = true;

                BindMetrics();

            }
        }
        /// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            MetricValueService metricValueService = new MetricValueService( new RockContext() );
            SortProperty sortProperty = gMetricValues.SortProperty;
            var qry = metricValueService.Queryable();

            // in case called normally
            int? metricId = PageParameter( "MetricId" ).AsInteger( false );

            // in case called from CategoryTreeView
            int? metricCategoryId = PageParameter( "MetricCategoryId" ).AsInteger( false );
            MetricCategory metricCategory = null;
            if ( metricCategoryId.HasValue )
            {
                if ( metricCategoryId.Value > 0 )
                {
                    // editing a metric, but get the metricId from the metricCategory
                    metricCategory = new MetricCategoryService( new RockContext() ).Get( metricCategoryId.Value );
                    if ( metricCategory != null )
                    {
                        metricId = metricCategory.MetricId;
                    }
                }
                else
                {
                    // adding a new metric. Block will (hopefully) not be shown
                    metricId = 0;
                }
            }

            hfMetricId.Value = metricId.ToString();
            hfMetricCategoryId.Value = metricCategoryId.ToString();

            this.Visible = metricId.HasValue;

            qry = qry.Where( a => a.MetricId == metricId );

            if ( sortProperty != null )
            {
                gMetricValues.DataSource = qry.Sort( sortProperty ).ToList();
            }
            else
            {
                gMetricValues.DataSource = qry.OrderBy( s => s.Order ).ThenBy( s => s.XValue ).ThenBy( s => s.MetricValueDateTime ).ThenBy( s => s.YValue ).ToList();
            }

            gMetricValues.DataBind();
        }