Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored Observation data gathering to support unassigned #4423

Merged
merged 1 commit into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void GenerateSpreadsheet(int assessmentId, MemoryStream ms)
_report.SetReportsAssessmentId(assessmentId);
BasicReportData data = new BasicReportData();
data.information = _report.GetInformation();
data.Individuals = _report.GetObservationIndividuals().OrderBy(x => x.FullName).ToList();
data.Individuals = _report.GetObservationIndividuals().ToList();


foreach (var ind in data.Individuals)
Expand All @@ -114,7 +114,7 @@ public void GenerateSpreadsheet(int assessmentId, MemoryStream ms)

row.Append(new Cell
{
CellValue = new CellValue(obs.Observation),
CellValue = new CellValue(obs.ObservationTitle),
DataType = CellValues.String
});

Expand All @@ -130,9 +130,14 @@ public void GenerateSpreadsheet(int assessmentId, MemoryStream ms)
DataType = CellValues.String
});

string resolutionDateString = "";
if (obs.ResolutionDate != null)
{
resolutionDateString = obs.ResolutionDate.ToString();
}
row.Append(new Cell
{
CellValue = new CellValue((DateTimeOffset)obs.ResolutionDate),
CellValue = new CellValue(resolutionDateString),
DataType = CellValues.String
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public partial class ReportsDataBusiness : IReportsDataBusiness
private readonly IAdminTabBusiness _adminTabBusiness;
private readonly IMaturityBusiness _maturityBusiness;
private readonly IQuestionRequirementManager _questionRequirement;
private readonly ITokenManager _tokenManager;
private ITokenManager _tokenManager;

public List<int> OutOfScopeQuestions = new List<int>();

Expand All @@ -62,6 +62,17 @@ public ReportsDataBusiness(CSETContext context, IAssessmentUtil assessmentUtil,
}


/// <summary>
/// Allows the token to be set after construction. This is needed
/// in the Reports realm, when tokens are passed as a param to the controller method.
/// </summary>
/// <param name="token"></param>
public void SetToken(ITokenManager token)
{
_tokenManager = token;
}


/// <summary>
/// Returns an unfiltered list of MatRelevantAnswers for the current assessment.
/// The optional modelId parameter is used to get a specific model's questions. If not
Expand Down Expand Up @@ -675,7 +686,6 @@ orderby h.Question_Group_Heading
/// <returns></returns>
public List<QuestionsWithComments> GetQuestionsWithComments()
{

var results = new List<QuestionsWithComments>();

var rm = new Question.RequirementBusiness(_assessmentUtil, _questionRequirement, _context, _tokenManager);
Expand Down Expand Up @@ -731,7 +741,6 @@ orderby h.Question_Group_Heading
/// <returns></returns>
public List<QuestionsMarkedForReview> GetQuestionsMarkedForReview()
{

var results = new List<QuestionsMarkedForReview>();

// get any "marked for review" or commented answers that currently apply
Expand Down Expand Up @@ -785,7 +794,6 @@ orderby h.Question_Group_Heading
/// <returns></returns>
public List<QuestionsMarkedForReview> GetQuestionsReviewed()
{

var results = new List<QuestionsMarkedForReview>();
var rm = new Question.RequirementBusiness(_assessmentUtil, _questionRequirement, _context, _tokenManager);

Expand Down Expand Up @@ -1087,82 +1095,119 @@ public BasicReportData.INFORMATION GetInformation()


/// <summary>
/// Returns a list of individuals assigned to observations.
///
/// </summary>
/// <returns></returns>
public List<Individual> GetObservationIndividuals()
{
var observations = (from a in _context.FINDING_CONTACT
join b in _context.FINDING on a.Finding_Id equals b.Finding_Id
join c in _context.ANSWER on b.Answer_Id equals c.Answer_Id
join mq in _context.MATURITY_QUESTIONS on c.Question_Or_Requirement_Id equals mq.Mat_Question_Id into mq1
List<Individual> individualList = [];

var observations = (from f in _context.FINDING
join fc in _context.FINDING_CONTACT on f.Finding_Id equals fc.Finding_Id
join a in _context.ANSWER on f.Answer_Id equals a.Answer_Id
join mq in _context.MATURITY_QUESTIONS on a.Question_Or_Requirement_Id equals mq.Mat_Question_Id into mq1
from mq in mq1.DefaultIfEmpty()
join r in _context.NEW_REQUIREMENT on c.Question_Or_Requirement_Id equals r.Requirement_Id into r1
from r in r1.DefaultIfEmpty()
join d in _context.ASSESSMENT_CONTACTS on a.Assessment_Contact_Id equals d.Assessment_Contact_Id
join i in _context.IMPORTANCE on b.Importance_Id equals i.Importance_Id into i1
join nr in _context.NEW_REQUIREMENT on a.Question_Or_Requirement_Id equals nr.Requirement_Id into nr1
from nr in nr1.DefaultIfEmpty()
join ac in _context.ASSESSMENT_CONTACTS on fc.Assessment_Contact_Id equals ac.Assessment_Contact_Id into ac1
from ac in ac1.DefaultIfEmpty()
join i in _context.IMPORTANCE on f.Importance_Id equals i.Importance_Id into i1
from i in i1.DefaultIfEmpty()
where c.Assessment_Id == _assessmentId
orderby a.Assessment_Contact_Id, b.Answer_Id, b.Finding_Id
select new { a, b, c, mq, r, d, i.Value }).ToList();
where a.Assessment_Id == _assessmentId
select new ObservationIngredients() {
Finding = f,
FC = fc,
Answer = a,
MaturityQuestion = mq,
NewRequirement = nr,
Importance = i }).ToList();

var acc = _context.ASSESSMENT_CONTACTS.Where(x => x.Assessment_Id == _assessmentId).OrderBy(x => x.Assessment_Contact_Id).ToList();


// Get any associated questions to get their display reference
var standardQuestions = GetQuestionsForEachStandard();
var componentQuestions = GetComponentQuestions();


List<Individual> individualList = new List<Individual>();

// First handle the 'assigned' Observations
foreach (var contact in acc)
{
Individual individual = new Individual()
{
FullName = FormatName(contact.FirstName, contact.LastName)
};

int contactId = 0;
Individual individual = null;
individualList.Add(individual);

foreach (var f in observations)
{
if (contactId != f.a.Assessment_Contact_Id)
var obsList = observations.Where(x => x.FC?.Assessment_Contact_Id == contact.Assessment_Contact_Id).ToList();

foreach (var m in obsList)
{
individual = new Individual()
{
Observations = new List<Observations>(),
FullName = FormatName(f.d.FirstName, f.d.LastName)
};
var obs = GenerateObservation(m, standardQuestions, componentQuestions);

individualList.Add(individual);
individual.Observations.Add(obs);
}
contactId = f.a.Assessment_Contact_Id;
}


TinyMapper.Bind<FINDING, Observations>();
Observations obs = TinyMapper.Map<Observations>(f.b);
obs.Observation = f.b.Summary;
obs.ResolutionDate = f.b.Resolution_Date;
obs.Importance = f.Value;
// Include any 'unassigned' Observations
var ind = new Individual();
ind.FullName = "Unassigned";

var unnasignedObs = observations.Where(x => x.FC == null).ToList();
foreach (var obs in unnasignedObs)
{
var observation = GenerateObservation(obs, standardQuestions, componentQuestions);
ind.Observations.Add(observation);
}

// get the question identifier and text
GetQuestionTitleAndText(f, standardQuestions, componentQuestions, f.c.Answer_Id,
out string qid, out string qtxt);
if (ind.Observations.Count > 0)
{
individualList.Add(ind);
}

if (_maturityBusiness.GetMaturityModel(_assessmentId)?.ModelName == "CIE")
{
GetQuestionTitleAndTextForCie(f, standardQuestions, componentQuestions, f.c.Answer_Id,
out qid, out qtxt);
}
return individualList;
}

obs.QuestionIdentifier = qid;
obs.QuestionText = qtxt;

/// <summary>
/// Creates and populates an instance of Observation
/// </summary>
/// <param name="oi"></param>
/// <returns></returns>
private Observation GenerateObservation(ObservationIngredients oi, List<StandardQuestions> standardQuestions, List<ComponentQuestion> componentQuestions)
{
TinyMapper.Bind<FINDING, Observation>();
Observation obs = TinyMapper.Map<Observation>(oi.Finding);
obs.ObservationTitle = oi.Finding.Summary;
obs.ResolutionDate = oi.Finding.Resolution_Date;
obs.Importance = oi.Importance.Value;


var othersList = (from a in f.b.FINDING_CONTACT
join b in _context.ASSESSMENT_CONTACTS on a.Assessment_Contact_Id equals b.Assessment_Contact_Id
select FormatName(b.FirstName, b.LastName)).ToList();
obs.OtherContacts = string.Join(",", othersList);
// get the question identifier and text
GetQuestionTitleAndText(oi, standardQuestions, componentQuestions, oi.Answer.Answer_Id,
out string qid, out string qtxt);

individual.Observations.Add(obs);
if (_maturityBusiness.GetMaturityModel(_assessmentId)?.ModelName == "CIE")
{
GetQuestionTitleAndTextForCie(oi, standardQuestions, componentQuestions, oi.Answer.Answer_Id,
out qid, out qtxt);
}

return individualList;
obs.QuestionIdentifier = qid;
obs.QuestionText = qtxt;


// list names of all people assigned to the observation
var othersList = (from a in oi.Finding.FINDING_CONTACT
join b in _context.ASSESSMENT_CONTACTS on a.Assessment_Contact_Id equals b.Assessment_Contact_Id
select FormatName(b.FirstName, b.LastName)).ToList();
obs.Assignees = string.Join(",", othersList);


return obs;
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ public List<DocumentLibraryTable> GetDocumentLibrary()
/// question text with the translated version.
/// </summary>
/// <returns></returns>
private void GetQuestionTitleAndText(dynamic f,
private void GetQuestionTitleAndText(ObservationIngredients f,
List<StandardQuestions> stdList, List<ComponentQuestion> compList,
int answerId,
out string identifier, out string questionText)
Expand All @@ -262,12 +262,12 @@ private void GetQuestionTitleAndText(dynamic f,
questionText = "";
var lang = _tokenManager.GetCurrentLanguage();

switch (f.c.Question_Type)
switch (f.Answer.Question_Type)
{
case "Question":
foreach (var s in stdList)
{
var q1 = s.Questions.FirstOrDefault(x => x.QuestionId == f.c.Question_Or_Requirement_Id);
var q1 = s.Questions.FirstOrDefault(x => x.QuestionId == f.Answer.Question_Or_Requirement_Id);
if (q1 != null)
{
identifier = q1.CategoryAndNumber;
Expand All @@ -279,7 +279,7 @@ private void GetQuestionTitleAndText(dynamic f,
return;

case "Component":
var q2 = compList.FirstOrDefault(x => x.QuestionId == f.c.Question_Or_Requirement_Id);
var q2 = compList.FirstOrDefault(x => x.QuestionId == f.Answer.Question_Or_Requirement_Id);
if (q2 != null)
{
identifier = q2.ComponentName;
Expand All @@ -289,28 +289,28 @@ private void GetQuestionTitleAndText(dynamic f,
return;

case "Requirement":
identifier = f.r.Requirement_Title;
identifier = f.NewRequirement.Requirement_Title;
var rb = new RequirementBusiness(_assessmentUtil, _questionRequirement, _context, _tokenManager);
questionText = rb.ResolveParameters(f.r.Requirement_Id, answerId, f.r.Requirement_Text);
questionText = rb.ResolveParameters(f.NewRequirement.Requirement_Id, answerId, f.NewRequirement.Requirement_Text);

// translate
questionText = _overlay.GetRequirement(f.r.Requirement_Id, lang)?.RequirementText ?? questionText;
questionText = _overlay.GetRequirement(f.NewRequirement.Requirement_Id, lang)?.RequirementText ?? questionText;

return;

case "Maturity":

identifier = f.mq.Question_Title;
questionText = f.mq.Question_Text;
identifier = f.MaturityQuestion.Question_Title;
questionText = f.MaturityQuestion.Question_Text;

// CPG is a special case
if (!String.IsNullOrEmpty(f.mq.Security_Practice))
if (!String.IsNullOrEmpty(f.MaturityQuestion.Security_Practice))
{
questionText = f.mq.Security_Practice;
questionText = f.MaturityQuestion.Security_Practice;
}

// overlay
MaturityQuestionOverlay o = _overlay.GetMaturityQuestion(f.mq.Mat_Question_Id, lang);
MaturityQuestionOverlay o = _overlay.GetMaturityQuestion(f.MaturityQuestion.Mat_Question_Id, lang);
if (o != null)
{
identifier = o.QuestionTitle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
////////////////////////////////
using CSETWebCore.Business.Reports;
using CSETWebCore.DataLayer.Model;
using CSETWebCore.Interfaces.Helpers;
using CSETWebCore.Model.Diagram;
using CSETWebCore.Model.Maturity;
using CSETWebCore.Model.Question;
Expand All @@ -18,6 +19,8 @@ public interface IReportsDataBusiness
{
void SetReportsAssessmentId(int assessmentId);

void SetToken(ITokenManager token);

List<MatRelevantAnswers> GetMaturityDeficiencies(int? modelId = null);
List<MatRelevantAnswers> GetCommentsList(int? modelId = null);
List<MatRelevantAnswers> GetMarkedForReviewList(int? modelId = null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ public class Control_Questions
}
}

public class Observations
public class Observation
{
public string Observation { get; set; }
public string ObservationTitle { get; set; }
public string QuestionIdentifier { get; set; }
public string QuestionText { get; set; }
public string Importance { get; set; }
Expand All @@ -125,7 +125,11 @@ public class Observations
public string Impact { get; set; }
public string Recommendations { get; set; }
public string Vulnerabilities { get; set; }
public string OtherContacts { get; set; }

/// <summary>
/// A comma list of all people assigned to the Observation
/// </summary>
public string Assignees { get; set; }
}

public class DocumentLibraryTable
Expand Down Expand Up @@ -252,7 +256,7 @@ public List<RelevantAnswers> GetAnswersForAssessment(int assessmentID, CSETConte
public class Individual
{
public string FullName { get; set; }
public List<Observations> Observations { get; set; }
public List<Observation> Observations { get; set; } = [];
}

public class QuestionsWithAltJust
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using CSETWebCore.DataLayer.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSETWebCore.Model.Reports
{
public class ObservationIngredients
{
public FINDING Finding { get; set; }
public FINDING_CONTACT FC { get; set; }
public ANSWER Answer { get; set; }
public MATURITY_QUESTIONS MaturityQuestion { get; set; }
public NEW_REQUIREMENT NewRequirement { get; set; }

public IMPORTANCE Importance { get; set; }
}
}
Loading
Loading