Skip to content
Open
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 @@ -12,12 +12,12 @@ import scala.util.{Failure, Success, Try}
object ProcessAdminConstructor {
private val logger = LoggerFactory.getLogger(ProcessAdminConstructor.getClass)

def ProcessAndUpdateJsonFiles(reportConfigQuery: String, collectionId: Int, databaseId: Int, dashboardId: Int, tabId: Int, stateNameId: Int, districtNameId: Int, programNameId: Int, blockNameId: Int, clusterNameId: Int, orgNameId: Int, projects: String, solutions: String, metabaseUtil: MetabaseUtil, postgresUtil: PostgresUtil): ListBuffer[Int] = {
def ProcessAndUpdateJsonFiles(reportConfigQuery: String, collectionId: Int, databaseId: Int, dashboardId: Int, tabId: Int, stateNameId: Int, districtNameId: Int, programNameId: Int, blockNameId: Int, clusterNameId: Int, orgNameId: Int, projects: String, solutions: String, tenantId: String, metabaseUtil: MetabaseUtil, postgresUtil: PostgresUtil): ListBuffer[Int] = {
logger.info(s"=====> Started processing admin level json update function for Micro Improvements dashboard")
val questionCardId = ListBuffer[Int]()
val objectMapper = new ObjectMapper()

def processJsonFiles(reportConfigQuery: String, collectionId: Int, databaseId: Int, dashboardId: Int, stateNameId: Int, districtNameId: Int, programNameId: Int, blockNameId: Int, clusterNameId: Int, orgNameId: Int): Unit = {
def processJsonFiles(reportConfigQuery: String, collectionId: Int, databaseId: Int, dashboardId: Int, stateNameId: Int, districtNameId: Int, programNameId: Int, blockNameId: Int, clusterNameId: Int, orgNameId: Int, tenantId: String): Unit = {
val dashcardsArray = objectMapper.createArrayNode()
val queryResult = postgresUtil.fetchData(reportConfigQuery)
queryResult.foreach { row =>
Expand All @@ -29,7 +29,7 @@ object ProcessAdminConstructor {
val originalQuestionCard = configJson.path("questionCard")
val chartName = Option(originalQuestionCard.path("name").asText()).getOrElse("Unknown Chart")
val updatedQuestionCard = updateQuestionCardJsonValues(configJson, collectionId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, databaseId)
val finalQuestionCard = updatePostgresDatabaseQuery(updatedQuestionCard, projects, solutions)
val finalQuestionCard = updatePostgresDatabaseQuery(updatedQuestionCard, projects, solutions, tenantId)
val requestBody = finalQuestionCard.asInstanceOf[ObjectNode]
val cardId = mapper.readTree(metabaseUtil.createQuestionCard(requestBody.toString)).path("id").asInt()
logger.info(s">>> Successfully created question card with card_id: $cardId for $chartName")
Expand Down Expand Up @@ -128,7 +128,7 @@ object ProcessAdminConstructor {
}
}

def updatePostgresDatabaseQuery(json: JsonNode, projectsTable: String, solutionsTable: String): JsonNode = {
def updatePostgresDatabaseQuery(json: JsonNode, projectsTable: String, solutionsTable: String, tenantId: String): JsonNode = {
Try {
val queryNode = json.at("/dataset_query/native/query")
if (queryNode.isMissingNode || !queryNode.isTextual) {
Expand All @@ -138,6 +138,7 @@ object ProcessAdminConstructor {
val updatedQuery = queryNode.asText()
.replace("${config.projects}", projectsTable)
.replace("${config.solutions}", solutionsTable)
.replace("${tenant_id}", s"'$tenantId'")

val updatedJson = json.deepCopy().asInstanceOf[ObjectNode]
updatedJson.at("/dataset_query/native")
Expand Down Expand Up @@ -176,7 +177,7 @@ object ProcessAdminConstructor {
}.toOption
}

processJsonFiles(reportConfigQuery, collectionId, databaseId, dashboardId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId)
processJsonFiles(reportConfigQuery, collectionId, databaseId, dashboardId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, tenantId)
logger.info(s"=====> Completed processing admin level json update function for Micro Improvements dashboard")
questionCardId
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,8 @@ class ProjectMetabaseDashboardFunction(config: CombinedDashboardCreatorConfig)(i
*/
logger.info("-->> Process Program Manager Collection and project solution Dashboard")
if (targetedSolutionId.nonEmpty && solutionName.nonEmpty && targetedProgramId.nonEmpty && programName.nonEmpty) {
val programCollectionName = s"$programName [org : $orgId]"
val programCollectionDescription = s"Program Id: $targetedProgramId\n\nProgram External Id: $programExternalId\n\nCollection For: Program Manager\n\nProgram Description: $programDescription"
val programCollectionName = s"$programName"
val programCollectionDescription = s"Program Id: $targetedProgramId\n\nProgram External Id: $programExternalId\n\nCreator Organisation: $orgId\n\nCollection For: Admin\n\nProgram Description: $programDescription"
Comment on lines +248 to +249
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the Program Manager collection tagged as Program Manager.

This branch creates Program Manager collections, but the description now says Collection For: Admin. The later validateCollection(..., "Program Manager", ...) lookup uses that description, so reruns will fail to match the existing collection.

Proposed fix
-            val programCollectionDescription = s"Program Id: $targetedProgramId\n\nProgram External Id: $programExternalId\n\nCreator Organisation: $orgId\n\nCollection For: Admin\n\nProgram Description: $programDescription"
+            val programCollectionDescription = s"Program Id: $targetedProgramId\n\nProgram External Id: $programExternalId\n\nCreator Organisation: $orgId\n\nCollection For: Program Manager\n\nProgram Description: $programDescription"

As per coding guidelines metabase-jobs/**: "Verify: - Idempotency of ETL runs".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
val programCollectionName = s"$programName"
val programCollectionDescription = s"Program Id: $targetedProgramId\n\nProgram External Id: $programExternalId\n\nCreator Organisation: $orgId\n\nCollection For: Admin\n\nProgram Description: $programDescription"
val programCollectionName = s"$programName"
val programCollectionDescription = s"Program Id: $targetedProgramId\n\nProgram External Id: $programExternalId\n\nCreator Organisation: $orgId\n\nCollection For: Program Manager\n\nProgram Description: $programDescription"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@metabase-jobs/combined-dashboard-creator/src/main/scala/org/shikshalokam/job/combined/dashboard/creator/functions/project/ProjectMetabaseDashboardFunction.scala`
around lines 248 - 249, The programCollectionDescription currently hardcodes
"Collection For: Admin" which breaks idempotency because validateCollection(...,
"Program Manager", ...) expects "Program Manager"; update the
programCollectionDescription string (the variable programCollectionDescription
in ProjectMetabaseDashboardFunction.scala) to use "Collection For: Program
Manager" so the description matches the validateCollection lookup and reruns
will find the existing Program Manager collection.

val solutionCollectionName = s"$solutionName [Project]"
val solutionCollectionDescription = s"Solution Id: $targetedSolutionId\n\nSolution External Id: $solutionExternalId\n\nCollection For: Program Manager\n\nSolution Description: $solutionDescription"

Expand Down Expand Up @@ -293,37 +293,49 @@ class ProjectMetabaseDashboardFunction(config: CombinedDashboardCreatorConfig)(i
}
}

def createMicroImprovementsCollectionAndDashboard(metaDataTable: String, reportConfig: String, databaseId: Int): Unit = {
val createDashboardQuery = s"UPDATE $metaDataTable SET status = 'Failed',error_message = 'errorMessage' WHERE id = '$admin';"
val (mipCollectionName, mipCollectionDescription) = (s"Micro Improvements", s"This collection contains dashboards that track the progress, participation, and effectiveness of Micro Improvement Projects across various programs.\n\nCollection For: Admin")
val mipCollectionId = Utils.checkAndCreateCollection(mipCollectionName, mipCollectionDescription, metabaseUtil)
if (mipCollectionId != -1) {
Utils.createGroupForCollection(metabaseUtil, s"Report_Admin_Micro_Improvement", mipCollectionId)
val (mipDashboardName, mipDashboardDescription) = (s"Overview - Across States and Programs", s"A consolidated view of project progress and user participation.")
val (mipDashboardId, projectTabId, userTabId, csvTabId) = Utils.createMicroImprovementsDashboardAndTabs(mipCollectionId, mipDashboardName, mipDashboardDescription, metabaseUtil)
if (projectTabId != -1 && userTabId != -1 && csvTabId != -1) {
val stateNameId: Int = getTheColumnId(databaseId, projects, "state_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val districtNameId: Int = getTheColumnId(databaseId, projects, "district_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val programNameId: Int = getTheColumnId(databaseId, projects, "program_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val blockNameId: Int = getTheColumnId(databaseId, projects, "block_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val clusterNameId: Int = getTheColumnId(databaseId, projects, "cluster_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val orgNameId: Int = getTheColumnId(databaseId, projects, "org_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val projectDetailsConfigQuery: String = s"SELECT question_type, config FROM $reportConfig WHERE dashboard_name = 'Admin' AND report_name = 'Project-Details';"
val projectQuestionCardIdList = ProcessAdminConstructor.ProcessAndUpdateJsonFiles(projectDetailsConfigQuery, mipCollectionId, databaseId, mipDashboardId, projectTabId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, projects, solutions, metabaseUtil, postgresUtil)
val userDetailsConfigQuery: String = s"SELECT question_type, config FROM $reportConfig WHERE dashboard_name = 'Admin' AND report_name = 'User-Details';"
val userQuestionCardIdList = ProcessAdminConstructor.ProcessAndUpdateJsonFiles(userDetailsConfigQuery, mipCollectionId, databaseId, mipDashboardId, userTabId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, projects, solutions, metabaseUtil, postgresUtil)
val submissionDetailsConfigQuery: String = s"SELECT question_type, config FROM $reportConfig WHERE dashboard_name = 'Admin' AND report_name = 'Submission-Details-CSV';"
val submissionQuestionCardIdList = ProcessAdminConstructor.ProcessAndUpdateJsonFiles(submissionDetailsConfigQuery, mipCollectionId, databaseId, mipDashboardId, csvTabId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, projects, solutions, metabaseUtil, postgresUtil)
val mainQuestionIdsString = "[" + (projectQuestionCardIdList ++ userQuestionCardIdList ++ submissionQuestionCardIdList).mkString(",") + "]"
val parametersQuery: String = s"SELECT config FROM $reportConfig WHERE report_name = 'Project-Parameter' AND question_type = 'admin-parameter'"
UpdateParameters.UpdateAdminParameterFunction(metabaseUtil, parametersQuery, mipDashboardId, postgresUtil)
val projectMetadataJson = new ObjectMapper().createArrayNode().add(new ObjectMapper().createObjectNode().put("collectionId", mipCollectionId).put("collectionName", mipCollectionName).put("dashboardId", mipDashboardId).put("dashboardName", mipDashboardName).put("questionIds", mainQuestionIdsString))
postgresUtil.insertData(s"UPDATE $metaDataTable SET main_metadata = COALESCE(main_metadata::jsonb, '[]'::jsonb) || '$projectMetadataJson' ::jsonb WHERE entity_id = '$admin';")
} else {
logger.info(s"Creating tabs failed please check: $projectTabId, $userTabId, $csvTabId")
def createMicroImprovementsCollectionAndDashboard(metaDataTable: String, reportConfig: String, databaseId: Int): Unit = {
val createDashboardQuery = s"UPDATE $metaDataTable SET status = 'Failed',error_message = 'errorMessage' WHERE id = '$admin';"
val (mipCollectionName, mipCollectionDescription) = (s"Micro Improvements [TenantId: $tenantId]", s"This collection contains dashboards that track the progress, participation, and effectiveness of Micro Improvement Projects across various programs.\n\nCollection For: Admin")
val mipCollectionId = Utils.checkAndCreateCollection(mipCollectionName, mipCollectionDescription, metabaseUtil)
if (mipCollectionId != -1) {
Utils.createGroupForCollection(metabaseUtil, s"Report_Admin_Micro_Improvement", mipCollectionId)
val (mipDashboardName, mipDashboardDescription) = (s"Overview - Across States and Programs", s"A consolidated view of project progress and user participation.")
val (mipDashboardId, projectTabId, userTabId, csvTabId) = Utils.createMicroImprovementsDashboardAndTabs(mipCollectionId, mipDashboardName, mipDashboardDescription, metabaseUtil)
if (projectTabId != -1 && userTabId != -1 && csvTabId != -1) {
val stateNameId: Int = getTheColumnId(databaseId, projects, "state_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val districtNameId: Int = getTheColumnId(databaseId, projects, "district_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val programNameId: Int = getTheColumnId(databaseId, projects, "program_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val blockNameId: Int = getTheColumnId(databaseId, projects, "block_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val clusterNameId: Int = getTheColumnId(databaseId, projects, "cluster_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val orgNameId: Int = getTheColumnId(databaseId, projects, "org_name", metabaseUtil, metabasePostgresUtil, metabaseApiKey, createDashboardQuery)
val projectDetailsConfigQuery: String = s"SELECT question_type, config FROM $reportConfig WHERE dashboard_name = 'Admin' AND report_name = 'Project-Details';"
val projectQuestionCardIdList = ProcessAdminConstructor.ProcessAndUpdateJsonFiles(projectDetailsConfigQuery, mipCollectionId, databaseId, mipDashboardId, projectTabId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, projects, solutions, tenantId, metabaseUtil, postgresUtil)
val userDetailsConfigQuery: String = s"SELECT question_type, config FROM $reportConfig WHERE dashboard_name = 'Admin' AND report_name = 'User-Details';"
val userQuestionCardIdList = ProcessAdminConstructor.ProcessAndUpdateJsonFiles(userDetailsConfigQuery, mipCollectionId, databaseId, mipDashboardId, userTabId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, projects, solutions, tenantId, metabaseUtil, postgresUtil)
val submissionDetailsConfigQuery: String = s"SELECT question_type, config FROM $reportConfig WHERE dashboard_name = 'Admin' AND report_name = 'Submission-Details-CSV';"
val submissionQuestionCardIdList = ProcessAdminConstructor.ProcessAndUpdateJsonFiles(submissionDetailsConfigQuery, mipCollectionId, databaseId, mipDashboardId, csvTabId, stateNameId, districtNameId, programNameId, blockNameId, clusterNameId, orgNameId, projects, solutions, tenantId, metabaseUtil, postgresUtil)
val mainQuestionIdsString = "[" + (projectQuestionCardIdList ++ userQuestionCardIdList ++ submissionQuestionCardIdList).mkString(",") + "]"
val filterQuery: String = s"SELECT config FROM $reportConfig WHERE report_name = 'Project-Filters' AND question_type = 'admin-filter'"
val filterResults: List[Map[String, Any]] = postgresUtil.fetchData(filterQuery)
val objectMapper = new ObjectMapper()
val slugNameToTenantIdFilterMap = mutable.Map[String, Int]()
for (result <- filterResults) {
val configString = result.get("config").map(_.toString).getOrElse("")
val configJson = objectMapper.readTree(configString)
val slugName = configJson.findValue("name").asText()
val tenantIdFilter: Int = UpdateAndAddAdminTenantFilter.updateAndAddFilter(metabaseUtil, configJson: JsonNode, s"$tenantId", mipCollectionId, databaseId, projects, solutions)
slugNameToTenantIdFilterMap(slugName) = tenantIdFilter
}
val immutableSlugNameToTenantIdFilterMap: Map[String, Int] = slugNameToTenantIdFilterMap.toMap
val parametersQuery: String = s"SELECT config FROM $reportConfig WHERE report_name = 'Project-Parameter' AND question_type = 'admin-parameter'"
UpdateParameters.updateParameterFunction(metabaseUtil, postgresUtil, parametersQuery, immutableSlugNameToTenantIdFilterMap, mipDashboardId)
Comment on lines +318 to +331
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not persist invalid filter card IDs into admin parameters.

UpdateAndAddAdminTenantFilter.updateAndAddFilter can return -1 on failure, and this loop currently stores it anyway. That bad ID is then written into values_source_config.card_id, which breaks the admin filter wiring.

Proposed fix
             val slugName = configJson.findValue("name").asText()
             val tenantIdFilter: Int = UpdateAndAddAdminTenantFilter.updateAndAddFilter(metabaseUtil, configJson: JsonNode, s"$tenantId", mipCollectionId, databaseId, projects, solutions)
+            if (tenantIdFilter == -1) {
+              throw new IllegalStateException(s"Failed to create admin filter card for '$slugName'")
+            }
             slugNameToTenantIdFilterMap(slugName) = tenantIdFilter

As per coding guidelines metabase-jobs/**: "Verify: - Metabase cards/filters mapping consistency".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@metabase-jobs/combined-dashboard-creator/src/main/scala/org/shikshalokam/job/combined/dashboard/creator/functions/project/ProjectMetabaseDashboardFunction.scala`
around lines 318 - 331, The loop stores a sentinel -1 from
UpdateAndAddAdminTenantFilter.updateAndAddFilter into
slugNameToTenantIdFilterMap which later gets persisted by
UpdateParameters.updateParameterFunction; change the loop in
ProjectMetabaseDashboardFunction so you only put entries into
slugNameToTenantIdFilterMap when tenantIdFilter != -1 (i.e., successful
creation), skip/omit failed entries, and optionally log a warning including
slugName and the failure; pass the resulting immutable map (containing only
valid positive card IDs) to UpdateParameters.updateParameterFunction.

val projectMetadataJson = new ObjectMapper().createArrayNode().add(new ObjectMapper().createObjectNode().put("collectionId", mipCollectionId).put("collectionName", mipCollectionName).put("dashboardId", mipDashboardId).put("dashboardName", mipDashboardName).put("questionIds", mainQuestionIdsString))
postgresUtil.insertData(s"UPDATE $metaDataTable SET main_metadata = COALESCE(main_metadata::jsonb, '[]'::jsonb) || '$projectMetadataJson' ::jsonb WHERE entity_id = '$admin';")
} else {
logger.error(s"Creating tabs failed please check: $projectTabId, $userTabId, $csvTabId")
}
}
}

def processStateDashboard(tenantId: String, stateName: String, targetedStateId: String, reportConfig: String, metaDataTable: String, databaseId: Int, projects: String, solutions: String, metabaseUtil: MetabaseUtil, postgresUtil: PostgresUtil, reportFor: String): Int = {
val collectionName = s"$stateName State [Tenant : $tenantId]"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.shikshalokam.job.combined.dashboard.creator.functions.project

import com.fasterxml.jackson.databind.node.{ArrayNode, ObjectNode}
import com.fasterxml.jackson.databind.{JsonNode, ObjectMapper}
import org.shikshalokam.job.util.MetabaseUtil

object UpdateAndAddAdminTenantFilter {
val objectMapper = new ObjectMapper()

def updateAndAddFilter(metabaseUtil: MetabaseUtil, queryResult: JsonNode, tenantId: String, collectionId: Int, databaseId: Int, projectTable: String, solutionTable: String): Int = {
println(s"** Started to update and create filter questions for state dashboard")
val objectMapper = new ObjectMapper()

def replaceTenantName(json: JsonNode, tenantId: String, projectTable: String, solutionTable: String): JsonNode = {
def processNode(node: JsonNode): JsonNode = {
node match {
case obj: ObjectNode =>
obj.fieldNames().forEachRemaining { fieldName =>
val childNode = obj.get(fieldName)
if (childNode.isTextual) {
var updatedText = childNode.asText()

if (updatedText.contains("TENANTID")) {
updatedText = updatedText.replace("TENANTID", tenantId)
}
if (updatedText.contains("${config.projects}")) {
updatedText = updatedText.replace("${config.projects}", projectTable)
}
if (updatedText.contains("${config.solutions}")) {
updatedText = updatedText.replace("${config.solutions}", solutionTable)
}
obj.put(fieldName, updatedText)
} else {
obj.set(fieldName, processNode(childNode))
}
}
obj

case array: ArrayNode =>
val newArray = array.deepCopy().asInstanceOf[ArrayNode]
newArray.removeAll()
array.elements().forEachRemaining { child =>
newArray.add(processNode(child))
}
newArray

case _ => node
}
}

val updatedJson = processNode(json.deepCopy())
updatedJson
}

def updateCollectionIdAndDatabaseId(jsonFile: JsonNode, collectionId: Int, databaseId: Int): JsonNode = {
try {
val questionCardNode = jsonFile.get("questionCard").asInstanceOf[ObjectNode]
if (questionCardNode == null) {
throw new IllegalArgumentException("'questionCard' node not found.")
}
questionCardNode.put("collection_id", collectionId)
val datasetQueryNode = questionCardNode.get("dataset_query").asInstanceOf[ObjectNode]
if (datasetQueryNode == null) {
throw new IllegalArgumentException("'dataset_query' node not found.")
}
datasetQueryNode.put("database", databaseId)
jsonFile
} catch {
case ex: Exception =>
throw new RuntimeException(s"Error updating JSON: ${ex.getMessage}", ex)
}
}

def getTheQuestionId(json: JsonNode): Int = {
try {
val requestBody = json.get("questionCard")
val questionCardResponse = metabaseUtil.createQuestionCard(requestBody.toString)
val responseJson = objectMapper.readTree(questionCardResponse)
Option(responseJson.get("id")).map(_.asInt()).getOrElse {
println("Error: 'id' field not found in the response.")
-1
}
} catch {
case ex: Exception =>
println(s"Error fetching 'id' from response: ${ex.getMessage}")
-1
}
Comment on lines +74 to +87
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not return -1 for card creation failures.

-1 is treated as a real card_id downstream, so any Metabase create/parse failure will silently poison values_source_config.card_id and break the admin dashboard wiring. Fail fast here, or return an Option[Int]/Either and let the caller skip invalid mappings. As per coding guidelines: Metabase cards/filters mapping consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@metabase-jobs/combined-dashboard-creator/src/main/scala/org/shikshalokam/job/combined/dashboard/creator/functions/project/UpdateAndAddAdminTenantFilter.scala`
around lines 74 - 87, The function getTheQuestionId should not return -1 on
failure; change its signature to return Option[Int] (i.e., def
getTheQuestionId(json: JsonNode): Option[Int]) and remove printing/returning -1;
on success return Some(id) and on any parse/create failure return None (or
rethrow a descriptive exception if you prefer fail-fast). Update all callers
that assign into values_source_config.card_id to handle None (skip mapping or
fail-fast), and ensure metabaseUtil.createQuestionCard errors are caught and
propagated as None or an exception so invalid card ids are never written as -1.

}


val ReplacedStateNameJson = replaceTenantName(queryResult, tenantId, projectTable, solutionTable)
val updatedJson = updateCollectionIdAndDatabaseId(ReplacedStateNameJson, collectionId, databaseId)
val questionId = getTheQuestionId(updatedJson)
println(s"** Completed to update and create filter questions for state dashboard")
questionId
}
}
Loading