From 8f8e64ae916061184141c204e330efa889064ee0 Mon Sep 17 00:00:00 2001 From: Armin Hasanpour Date: Fri, 17 Apr 2026 12:18:41 -0700 Subject: [PATCH 1/4] AB#32448 - Guarded Generate buttons. --- .../AttachmentSummaryAppService.cs | 2 +- .../ApplicationAnalysisAppService.cs | 2 +- .../ApplicationScoringAppService.cs | 2 +- .../Pages/GrantApplications/Details.cshtml | 116 ++++++++---------- .../AssessmentScoresWidgetViewComponent.cs | 10 +- .../ChefsAttachments/ChefsAttachments.cs | 42 ++++--- .../ChefsAttachments/Default.cshtml | 5 +- .../Components/ReviewList/ReviewList.cs | 19 ++- .../Components/AssessmentScoresWidgetTests.cs | 13 +- 9 files changed, 125 insertions(+), 86 deletions(-) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs index 681d7e55d..eb96711d0 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/Attachments/AttachmentSummaryAppService.cs @@ -12,7 +12,7 @@ namespace Unity.GrantManager.Attachments; -[Authorize(AIPermissions.Analysis.ViewAttachmentSummary)] +[Authorize(AIPermissions.Analysis.GenerateAttachmentSummaries)] [Dependency(ReplaceServices = true)] [ExposeServices(typeof(AttachmentSummaryAppService), typeof(IAttachmentSummaryAppService))] public class AttachmentSummaryAppService( diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs index 483a30c8d..6e23ab597 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationAnalysisAppService.cs @@ -10,7 +10,7 @@ namespace Unity.GrantManager.GrantApplications; -[Authorize(AIPermissions.Analysis.ViewApplicationAnalysis)] +[Authorize(AIPermissions.Analysis.GenerateApplicationAnalysis)] public class ApplicationAnalysisAppService( IApplicationAIGenerationQueue aiGenerationQueue, IFeatureChecker featureChecker) diff --git a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs index d42a95f20..c1132a9e3 100644 --- a/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs +++ b/applications/Unity.GrantManager/modules/Unity.AI/src/Unity.AI.Application/GrantApplications/ApplicationScoringAppService.cs @@ -10,7 +10,7 @@ namespace Unity.GrantManager.GrantApplications; -[Authorize(AIPermissions.Analysis.ViewScoringResult)] +[Authorize(AIPermissions.Analysis.GenerateScoring)] public class ApplicationScoringAppService( IApplicationAIGenerationQueue aiGenerationQueue, IFeatureChecker featureChecker) diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml index 30ea386fc..125d6ca87 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml @@ -2,7 +2,9 @@ @* #pragma warning disable S1128 *@ @using Microsoft.Extensions.Localization @using Unity.AI.Permissions +@using Unity.AI.Settings @using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget +@using Unity.GrantManager.Applications @using Unity.GrantManager.Flex @using Unity.GrantManager.Localization @using Unity.GrantManager.Web.Pages.GrantApplications @@ -14,6 +16,7 @@ @using Volo.Abp.Authorization.Permissions @using Volo.Abp.Features @using Volo.Abp.MultiTenancy +@using Volo.Abp.Settings @* #pragma warning restore S1128 *@ @@ -23,18 +26,29 @@ @inject IFeatureChecker FeatureChecker @inject IPermissionChecker PermissionChecker @inject ICurrentTenant CurrentTenant +@inject ISettingProvider SettingProvider +@inject IApplicationFormRepository ApplicationFormRepository @{ PageLayout.Content.Title = L["Grants"].Value; var notificationsFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Notifications"); var readEmailGranted = await PermissionChecker.IsGrantedAsync("Notifications.Email"); - var aiAttachmentSummariesEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries") - && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewAttachmentSummary); + var flexFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Flex"); + + // View guards — control tab visibility and loading of existing AI results var aiApplicationAnalysisEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis") && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewApplicationAnalysis); - var aiScoringEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.Scoring") - && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewScoringResult); - var flexFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Flex"); + + // Shared manual-generation guards + var tenantManualEnabled = await SettingProvider.GetAsync(AISettings.ManualGenerationEnabled, defaultValue: false); + var applicationForm = await ApplicationFormRepository.GetAsync(Model.ApplicationFormId); + var formManualEnabled = applicationForm.ManuallyInitiateAIAnalysis; + + // Generate guard — controls visibility of the Generate button in the AI Analysis tab + var aiApplicationAnalysisGenerateEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis") + && tenantManualEnabled + && formManualEnabled + && await PermissionChecker.IsGrantedAsync(AIPermissions.Analysis.GenerateApplicationAnalysis); } @section styles { @@ -72,7 +86,6 @@ - @functions { @@ -153,7 +166,7 @@
- @await Component.InvokeAsync("ReviewList") + @await Component.InvokeAsync("ReviewList", new { applicationFormId = Model.ApplicationFormId })
@@ -389,7 +402,7 @@
@await Component.InvokeAsync("ApplicationAttachments") - @await Component.InvokeAsync("ChefsAttachments") + @await Component.InvokeAsync("ChefsAttachments", new { applicationFormId = Model.ApplicationFormId })
@*-------- Attachments Tab Section END ---------*@ @@ -415,12 +428,15 @@
AI Application Analysis
- + @if (aiApplicationAnalysisGenerateEnabled) + { + + }
@* Default message when no analysis data is available *@ @@ -494,15 +510,12 @@ } - @if (aiAttachmentSummariesEnabled && aiApplicationAnalysisEnabled && aiScoringEnabled) - { - - } +
@@ -511,19 +524,12 @@
Attachment Summary
- @if (aiAttachmentSummariesEnabled) - { - - } - else - { - Unavailable: feature or permission not enabled - } +
@@ -538,19 +544,12 @@
Application Analysis
- @if (aiApplicationAnalysisEnabled) - { - - } - else - { - Unavailable: feature or permission not enabled - } +
@@ -565,19 +564,12 @@
Application Scoring
- @if (aiScoringEnabled) - { - - } - else - { - Unavailable: feature or permission not enabled - } +
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs index e8a226872..7c25a4d5e 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/AssessmentScoresWidgetViewComponent.cs @@ -15,11 +15,13 @@ using Unity.Flex; using Unity.Flex.Scoresheets.Enums; using Unity.AI.Models; +using Unity.AI.Settings; using Unity.GrantManager.Applications; using System.Text.Json; using Unity.AI.Permissions; using Volo.Abp.Authorization.Permissions; using Volo.Abp.Features; +using Volo.Abp.Settings; namespace Unity.GrantManager.Web.Views.Shared.Components.AssessmentScoresWidget { @@ -32,6 +34,7 @@ public class AssessmentScoresWidgetViewComponent(IAssessmentRepository assessmen IScoresheetRepository scoresheetRepository, IScoresheetInstanceRepository scoresheetInstanceRepository, IApplicationRepository applicationRepository, + IApplicationFormRepository applicationFormRepository, IFeatureChecker featureChecker, IPermissionChecker permissionChecker) : AbpViewComponent { @@ -44,7 +47,10 @@ public async Task InvokeAsync(Guid assessmentId, Guid curr var assessment = await assessmentRepository.GetAsync(assessmentId); var application = await applicationRepository.GetAsync(assessment.ApplicationId); + var applicationForm = await applicationFormRepository.GetAsync(application.ApplicationFormId); var scoresheetInstance = await scoresheetInstanceRepository.GetByCorrelationAsync(assessment.Id); + var settingProvider = LazyServiceProvider.LazyGetRequiredService(); + var tenantManualEnabled = await settingProvider.GetAsync(AISettings.ManualGenerationEnabled, defaultValue: false); // Parse AI scoresheet answers if available Dictionary? aiAnswers = null; @@ -100,7 +106,9 @@ public async Task InvokeAsync(Guid assessmentId, Guid curr CurrentUserId = currentUserId, AssessorId = assessment.AssessorId, IsAIScoringEnabled = await featureChecker.IsEnabledAsync("Unity.AI.Scoring") && - await permissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewScoringResult), + tenantManualEnabled && + applicationForm.ManuallyInitiateAIAnalysis && + await permissionChecker.IsGrantedAsync(AIPermissions.Analysis.GenerateScoring), IsAiAssessment = assessment.IsAiAssessment, }; diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs index 2dc33039c..010123c80 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs @@ -1,12 +1,16 @@ using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.AspNetCore.Mvc.UI.Widgets; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; -using System.Collections.Generic; using Volo.Abp.Features; using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Settings; using Unity.AI.Permissions; +using Unity.AI.Settings; +using Unity.GrantManager.Applications; namespace Unity.GrantManager.Web.Views.Shared.Components.ChefsAttachments { @@ -14,23 +18,31 @@ namespace Unity.GrantManager.Web.Views.Shared.Components.ChefsAttachments [Widget( ScriptTypes = new[] { typeof(ChefsAttachmentsScriptBundleContributor) }, StyleTypes = new[] { typeof(ChefsAttachmentsStyleBundleContributor) })] - public class ChefsAttachments : AbpViewComponent + public class ChefsAttachments( + IFeatureChecker featureChecker, + IPermissionChecker permissionChecker, + IApplicationFormRepository applicationFormRepository) : AbpViewComponent { - private readonly IFeatureChecker _featureChecker; - private readonly IPermissionChecker _permissionChecker; - - public ChefsAttachments(IFeatureChecker featureChecker, IPermissionChecker permissionChecker) + public async Task InvokeAsync(Guid applicationFormId) { - _featureChecker = featureChecker; - _permissionChecker = permissionChecker; - } + var featureEnabled = await featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries"); + + // View guard — for toggling visibility of existing summaries + ViewBag.IsAIAttachmentSummariesEnabled = + featureEnabled && + await permissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewAttachmentSummary); + + // Generate guard — full 3-level chain for the Generate Summary button + var settingProvider = LazyServiceProvider.LazyGetRequiredService(); + var tenantManualEnabled = await settingProvider.GetAsync(AISettings.ManualGenerationEnabled, defaultValue: false); + var applicationForm = await applicationFormRepository.GetAsync(applicationFormId); + + ViewBag.IsAIAttachmentSummariesGenerateEnabled = + featureEnabled && + tenantManualEnabled && + applicationForm.ManuallyInitiateAIAnalysis && + await permissionChecker.IsGrantedAsync(AIPermissions.Analysis.GenerateAttachmentSummaries); - public async Task InvokeAsync() - { - var isAIAttachmentSummariesEnabled = - await _featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries") && - await _permissionChecker.IsGrantedAsync(AIPermissions.Analysis.ViewAttachmentSummary); - ViewBag.IsAIAttachmentSummariesEnabled = isAIAttachmentSummariesEnabled; return View(); } } diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/Default.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/Default.cshtml index 5b5a3f369..682441d1f 100644 --- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/Default.cshtml +++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/Default.cshtml @@ -7,7 +7,7 @@
Submission Attachments
- @if (ViewBag.IsAIAttachmentSummariesEnabled) + @if (ViewBag.IsAIAttachmentSummariesGenerateEnabled) { + } + @if (ViewBag.IsAIAttachmentSummariesEnabled) + {