Skip to content

Commit

Permalink
Merge pull request tech-by-design#245 from megin1989/main
Browse files Browse the repository at this point in the history
feat: add FHIR Submission Summary widgets  in dashboard tech-by-design#244
  • Loading branch information
ratheesh-kr authored Aug 1, 2024
2 parents 3cef9a8 + dec19b8 commit a1c55a7
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 4 deletions.
Binary file modified hub-prime/lib/techbd-udi-jooq-ingress.auto.jar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List; // Ensure this import is present
import java.util.Map;
import org.jooq.impl.DSL;
Expand Down Expand Up @@ -124,7 +125,7 @@ public ResponseEntity<?> handleFHRequest(@PathVariable String tenantId, @PathVar

String interactionCount = recentInteractions.get(0).get("interaction_count").toString();

String formattedTime = getrecentInteractioString(mre);
String formattedTime = getrecentInteractioString(mre);

if ("html".equalsIgnoreCase(extension)) {
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML)
Expand Down Expand Up @@ -202,17 +203,124 @@ private String getrecentInteractioString(String mreTime) {
return formattedTime;

}

private String convertToEST(String inputTime) {
// Parse the input time string to a ZonedDateTime
ZonedDateTime inputDateTime = ZonedDateTime.parse(inputTime, DateTimeFormatter.ISO_ZONED_DATE_TIME);

// Convert the ZonedDateTime to the EST time zone
ZonedDateTime estDateTime = inputDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));

// Format the ZonedDateTime to a string in the desired format
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return estDateTime.format(formatter);
}

@GetMapping(value = "/dashboard/stat/fhir/most-recent-interactions", produces = "text/html")
public String fetchRecentFHIRInteractions(Model model) {
String schemaName = "techbd_udi_ingress";
String viewName = "fhir_submission_summary";
try {
final var typableTable = JooqRowsSupplier.TypableTable.fromTablesRegistry(Tables.class, schemaName,
viewName);

List<Map<String, Object>> fhirSubmission = udiPrimeJpaConfig.dsl().selectFrom(typableTable.table())
.fetch()
.intoMaps();

if (fhirSubmission != null && !fhirSubmission.isEmpty()) {
Map<String, Object> data = fhirSubmission.get(0);
model.addAttribute("totalSubmissions", data.getOrDefault("total_submissions", "0").toString());
model.addAttribute("pendingSubmissions", data.getOrDefault("pending_submissions", "0").toString());
model.addAttribute("acceptedSubmissions", data.getOrDefault("accepted_submissions", "0").toString());
model.addAttribute("rejectedSubmissions", data.getOrDefault("rejected_submissions", "0").toString());
} else {
model.addAttribute("totalSubmissions", "0");
model.addAttribute("pendingSubmissions", "0");
model.addAttribute("acceptedSubmissions", "0");
model.addAttribute("rejectedSubmissions", "0");
}
} catch (Exception e) {
LOG.error("Error fetching FHIR interactions", e);
model.addAttribute("totalSubmissions", "0");
model.addAttribute("pendingSubmissions", "0");
model.addAttribute("acceptedSubmissions", "0");
model.addAttribute("rejectedSubmissions", "0");
}

return "fragments/interactions :: serverTextStat";
}

@GetMapping("/dashboard/most-recent-interactions-new")
public ResponseEntity<List<InteractionData>> getMostRecentInteractions() {

// Define schema and view name
String schemaName = "techbd_udi_ingress";
String viewName = "fhir_submission_summary";

// Fetch the typable table
final var typableTable = JooqRowsSupplier.TypableTable.fromTablesRegistry(Tables.class, schemaName, viewName);

// Query the view and fetch the results
List<Map<String, Object>> fhirSubmission = udiPrimeJpaConfig.dsl().selectFrom(typableTable.table())
.fetch()
.intoMaps();

// Initialize list to hold the results
List<InteractionData> interactions = new ArrayList<>();

// Check if data is available
if (fhirSubmission != null && !fhirSubmission.isEmpty()) {
Map<String, Object> data = fhirSubmission.get(0);

// Populate the list with data
interactions.add(new InteractionData("Total Submissions",
Integer.parseInt(data.getOrDefault("total_submissions", "0").toString())));
interactions.add(new InteractionData("Pending Submissions",
Integer.parseInt(data.getOrDefault("pending_submissions", "0").toString())));
interactions.add(new InteractionData("Accepted Submissions",
Integer.parseInt(data.getOrDefault("accepted_submissions", "0").toString())));
interactions.add(new InteractionData("Rejected Submissions",
Integer.parseInt(data.getOrDefault("rejected_submissions", "0").toString())));
} else {
// Default values if no data found
interactions.add(new InteractionData("Total Submissions", 0));
interactions.add(new InteractionData("Pending Submissions", 0));
interactions.add(new InteractionData("Accepted Submissions", 0));
interactions.add(new InteractionData("Rejected Submissions", 0));
}
// Return the data with HTTP status OK
return ResponseEntity.ok().body(interactions);

}

// Example class representing interaction data
public class InteractionData {
private String label;
private int count;

// Constructor
public InteractionData(String label, int count) {
this.label = label;
this.count = count;
}

// Getters and Setters
public String getLabel() {
return label;
}

public void setLabel(String label) {
this.label = label;
}

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}
}

}
34 changes: 34 additions & 0 deletions hub-prime/src/main/resources/templates/fragments/interactions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Dashboard Fragment</title>
</head>
<body th:fragment="serverTextStat">

<!-- Total Submissions Widget -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-2">Total Submissions</h2>
<p class="text-4xl font-bold" th:text="${totalSubmissions}">0</p>
</div>

<!-- Pending Submissions Widget -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-2">Pending Submissions</h2>
<p class="text-4xl font-bold" th:text="${pendingSubmissions}">0</p>
</div>

<!-- Accepted Submissions Widget -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-2">Accepted Submissions</h2>
<p class="text-4xl font-bold" th:text="${acceptedSubmissions}">0</p>
</div>

<!-- Rejected Submissions Widget -->
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-2">Rejected Submissions</h2>
<p class="text-4xl font-bold" th:text="${rejectedSubmissions}">0</p>
</div>

</body>
</html>
113 changes: 113 additions & 0 deletions hub-prime/src/main/resources/templates/page/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

<head>
<script src='https://unpkg.com/htmx.org/dist/htmx.min.js'></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
<title>Welcome</title>
<style>
.hanging-indent {
Expand Down Expand Up @@ -95,6 +96,20 @@ <h1 class="text-3xl font-bold mb-4 text-center">Welcome to Technology-by-Design'
</dl>
</div>

<div class="mt-12 w-full grid grid-cols-4 gap-8" id="dashboard-container">
<div th:replace="fragments/interactions :: serverTextStat"></div>
</div>
<div class="mt-12 w-full grid grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-bold mb-4">FHIR Submission Summary</h3>
<pre class=" bg-white p-4 rounded-lg shadow-md">
<dl id="pie-chart" class=" pie-chart-item ">
</dl>
</pre>

</div>
</div>

<div class="mt-12 w-full grid grid-cols-2 gap-8">
<div>
<h3 class="text-xl font-bold mb-4">Synthetic Testing (FHIR)</h3>
Expand Down Expand Up @@ -152,7 +167,105 @@ <h3 class="text-xl font-bold mb-4">Production (flat files)</h3>
</pre>
</div>
</div>


<script th:inline="javascript">
/*<![CDATA[*/
// Optional: Use JavaScript to fetch data dynamically if needed
async function fetchRecentData(days) {
try {
const response = await fetch(`/dashboard/stat/fhir/most-recent-interactions`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.text();
document.getElementById('dashboard-container').innerHTML = data;
} catch (error) {
console.error('Error fetching recent data:', error);
}
}

// Example usage on page load or user interaction
window.onload = function () {
fetchRecentData(7); // Fetch data for the last 7 days
fetchPieChartData();
}
function createPieChart(interactions) {
const width = 500; // Further increase width
const height = 500; // Further increase height
const radius = Math.min(width, height) / 2;

const color = d3.scaleOrdinal()
.domain(interactions.map(d => d.label))
.range(d3.schemeCategory10);

const pie = d3.pie()
.value(d => d.count);

const arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);

const svg = d3.select("#pie-chart")
.append("svg")
.attr("class", "inline-block align-middle w-[70%] mt-[-45px]") // Add Tailwind classes for width and height
.attr("viewBox", `0 0 ${width + 300} ${height}`) // Further increase width to make room for the legend
.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`);

svg.selectAll("path")
.data(pie(interactions))
.enter().append("path")
.attr("fill", d => color(d.data.label))
.attr("d", arc)
.append("title")
.text(d => `${d.data.label}: ${d.data.count}`);

// Add legend
const legend = svg.append("g")
.attr("transform", `translate(${radius + 50}, -${height / 2})`); // Move legend further to the right

const legendRectSize = 20; // Increase legend rectangle size
const legendSpacing = 8; // Increase legend spacing

const legendItems = legend.selectAll('.legend-item')
.data(interactions)
.enter()
.append('g')
.attr('class', 'legend-item')
.attr('transform', (d, i) => `translate(0, ${i * (legendRectSize + legendSpacing)})`);

legendItems.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.attr('fill', d => color(d.label))
.attr('stroke', d => color(d.label));

legendItems.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.style('font-size', '14px') // Increase font size
.style('font-family', 'sans-serif') // Optional: set font family
.text(d => d.label)
.attr('dy', '0.35em'); // Adjust text vertical alignment
}

function fetchPieChartData() {
// Fetch data from server-side using Thymeleaf
fetch('/dashboard/most-recent-interactions-new')
.then(response => response.json())
.then(data => {
// Call the pie chart creation function once with fetched data
createPieChart(data);
})
.catch(error => {
console.error('Error fetching interactions data:', error);
});
}
/*]]>*/
</script>
</div>

</body>

</html>
Original file line number Diff line number Diff line change
Expand Up @@ -862,3 +862,58 @@ WHERE rn = 1;
$function$
;

/**
* View to summarize FHIR submission interactions from the database.
* This view provides an aggregated overview of HTTP request interactions based on
* different types of nature values recorded in the `sat_interaction_http_request` table.
* The view calculates the following metrics over the past 7 days:
*
* 1. `total_submissions`: Counts submissions where the nature is 'Original FHIR Payload'.
* 2. `accepted_submissions`: Counts submissions where the nature is 'Forwarded HTTP Response'.
* 3. `rejected_submissions`: Counts submissions where the nature is 'Forwarded HTTP Response Error'.
* 4. `pending_submissions`: Counts submissions where the nature is neither 'Forwarded HTTP Response',
* 'Forwarded HTTP Response Error', nor 'org.techbd.service.http.Interactions$RequestResponseEncountered'.
*
* This view aggregates data for all interactions related to FHIR submissions based on the following:
* - Interaction metadata including the nature of HTTP requests.
* - Filtering interactions that occurred in the last 7 days.
* - Using a specific key value (`/Bundle`) to filter interactions.
*
* The view helps in monitoring and analyzing FHIR interaction statuses, providing insight into:
* - Total count of submissions made in the specified period.
* - Number of submissions that were accepted or rejected.
* - Submissions that are pending or still not categorized as accepted or rejected.
*
* Usage:
* SELECT * FROM techbd_udi_ingress.fhir_submission_summary;
*
* @return TABLE - A table summarizing total, accepted, rejected, and pending FHIR submissions
* for interactions within the past 7 days.
*/

DROP VIEW IF EXISTS techbd_udi_ingress.fhir_submission_summary CASCADE;

CREATE OR REPLACE VIEW techbd_udi_ingress.fhir_submission_summary AS
WITH submission_counts AS (
SELECT
--NONE -> ACCEPT_FHIR_BUNDLE
COUNT(CASE WHEN sihr.nature->>'nature' = 'Original FHIR Payload' THEN 1 END) AS total_submissions,
--FORWARD-> COMPLETE
COUNT(CASE WHEN sihr.nature->>'nature' = 'Forwarded HTTP Response' THEN 1 END) AS accepted_submissions,
--FORWARD-> FAIL
COUNT(CASE WHEN sihr.nature->>'nature' = 'Forwarded HTTP Response Error' THEN 1 END) AS rejected_submissions
FROM
techbd_udi_ingress.hub_interaction AS hintr
INNER JOIN techbd_udi_ingress.sat_interaction_http_request AS sihr
ON hintr.hub_interaction_id = sihr.hub_interaction_id
WHERE
hintr."key" = '/Bundle'
AND sihr.created_at >= CURRENT_DATE - INTERVAL '7 days'
)
SELECT
total_submissions,
accepted_submissions,
rejected_submissions,
total_submissions - accepted_submissions - rejected_submissions AS pending_submissions
FROM
submission_counts;

0 comments on commit a1c55a7

Please sign in to comment.