diff --git a/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java index ab5ffd61c..16b827f6d 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java @@ -155,7 +155,7 @@ public void createAdvancedQuestion(Database database) { .withVisualization(VisualizationType.PIE); MetabaseQuery query = createAdvancedQuery("individual", "subject_type", config, database); postQuestion( - "Pie Chart : Count of Non Voided Individuals - Non Voided Subject Type", + QuestionName.QUESTION_1.getQuestionName(), query, config, getCollectionForDatabase(database).getIdAsInt() @@ -174,7 +174,7 @@ public void createAdvancedQuestion2(Database database) { .withVisualization(VisualizationType.PIE); MetabaseQuery query = createAdvancedQuery("program_enrolment", "program", config, database); postQuestion( - "Pie Chart : Number of Non exited and Non voided Enrollments for Non Voided Program ", + QuestionName.QUESTION_2.getQuestionName(), query, config, getCollectionForDatabase(database).getIdAsInt() diff --git a/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java b/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java index b8356bf86..f724c4824 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java @@ -1,6 +1,7 @@ package org.avni.server.dao.metabase; import org.avni.server.domain.metabase.GroupPermissionsBody; +import org.avni.server.util.ObjectMapperSingleton; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.HttpEntity; @@ -38,9 +39,14 @@ protected HttpEntity createHttpEntity(T body) { return new HttpEntity<>(body, headers); } - protected void sendPutRequest(String url, Map requestBody) { - HttpEntity> entity = createHttpEntity(requestBody); - restTemplate.exchange(url, HttpMethod.PUT, entity, Map.class); + protected void sendPutRequest(String url, Object requestBody) { + try { + String jsonBody = ObjectMapperSingleton.getObjectMapper().writeValueAsString(requestBody); + HttpEntity entity = createHttpEntity(jsonBody); + restTemplate.exchange(url, HttpMethod.PUT, entity, String.class); + } catch (Exception e) { + throw new RuntimeException("Error serializing request body to JSON", e); + } } public T postForObject(String url, Object request, Class responseType) { diff --git a/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseDashboardRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseDashboardRepository.java new file mode 100644 index 000000000..bc0855afc --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseDashboardRepository.java @@ -0,0 +1,38 @@ +package org.avni.server.dao.metabase; + +import org.avni.server.domain.metabase.*; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +@Repository +public class MetabaseDashboardRepository extends MetabaseConnector { + private final DatabaseRepository databaseRepository; + + public MetabaseDashboardRepository(RestTemplateBuilder restTemplateBuilder , DatabaseRepository databaseRepository) { + super(restTemplateBuilder); + this.databaseRepository = databaseRepository; + } + + public Dashboard save(CreateDashboardRequest createDashboardRequest) { + String url = metabaseApiUrl + "/dashboard"; + return postForObject(url, createDashboardRequest, Dashboard.class); + } + + + public CollectionItem getDashboardByName(CollectionInfoResponse globalCollection) { + List items = databaseRepository.getExistingCollectionItems(globalCollection.getIdAsInt()); + + return items.stream() + .filter(item -> item.getName().equals(globalCollection.getName())) + .findFirst() + .orElse(null); + } + + public void updateDashboard(int dashboardId, DashboardUpdateRequest request) { + String url = metabaseApiUrl + "/dashboard/" + dashboardId; + sendPutRequest(url, request); + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java index c19a70571..9d3e5023d 100644 --- a/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java @@ -4,7 +4,17 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class CollectionItem { + private String name; + + public CollectionItem() { + } + + public CollectionItem(String name, int id) { + this.name = name; + this.id = id; + } + private int id; public String getName() { diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/CreateDashboardRequest.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CreateDashboardRequest.java new file mode 100644 index 000000000..b246a184e --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CreateDashboardRequest.java @@ -0,0 +1,34 @@ +package org.avni.server.domain.metabase; + +public class CreateDashboardRequest { + private final String name; + private final String description; + private final Integer collection_id; + + public CreateDashboardRequest(String name, String description, Integer collection_id) { + this.name = name; + this.description = description; + this.collection_id = collection_id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Integer getCollection_id() { + return collection_id; + } + + @Override + public String toString() { + return "{" + + "name='" + name + '\'' + + ", description='" + description + '\'' + + ", collectionId=" + collection_id + + '}'; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/Dashboard.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/Dashboard.java new file mode 100644 index 000000000..e485957e3 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/Dashboard.java @@ -0,0 +1,25 @@ +package org.avni.server.domain.metabase; + +import org.springframework.stereotype.Component; + +@Component +public class Dashboard { + private int id; + private String name; + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "Dashboard{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DashboardResponse.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DashboardResponse.java new file mode 100644 index 000000000..09c53207a --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DashboardResponse.java @@ -0,0 +1,45 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class DashboardResponse { + + private String name; + private String id; + + public DashboardResponse() { + } + + public DashboardResponse(@JsonProperty("name") String name, + @JsonProperty("id") Object id) { + this.name = name; + + if (id instanceof Integer) { + this.id = String.valueOf(id); + } else { + this.id = id.toString(); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public int getIdAsInt() { + try { + return Integer.parseInt(id); + } catch (NumberFormatException e) { + throw new RuntimeException("Failed to convert id to integer: " + id, e); + } + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DashboardUpdateRequest.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DashboardUpdateRequest.java new file mode 100644 index 000000000..fcbf682e0 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DashboardUpdateRequest.java @@ -0,0 +1,16 @@ +package org.avni.server.domain.metabase; + +import java.util.List; + +public class DashboardUpdateRequest { + private final List dashcards; + + public DashboardUpdateRequest(List dashcards) { + this.dashcards = dashcards; + } + + + public List getDashcards() { + return dashcards; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/Dashcard.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/Dashcard.java new file mode 100644 index 000000000..636b7b72c --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/Dashcard.java @@ -0,0 +1,87 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Collections; +import java.util.List; + +public class Dashcard { + @JsonProperty("id") + private int dashboardId; + + @JsonProperty("card_id") + private int cardId; + + @JsonProperty("dashboard_tab_id") + private Integer dashboardTabId; + + private int row; + private int col; + + @JsonProperty("size_x") + private int sizeX; + + @JsonProperty("size_y") + private int sizeY; + + @JsonProperty("visualization_settings") + private Object visualizationSettings; + + @JsonProperty("parameter_mappings") + private List parameterMappings; + + public Dashcard(int dashboardId, int cardId, Integer dashboardTabId, int row, int col, int sizeX, int sizeY) { + this.dashboardId = dashboardId; + this.cardId = cardId; + this.dashboardTabId = dashboardTabId; + this.row = row; + this.col = col; + this.sizeX = sizeX; + this.sizeY = sizeY; + this.visualizationSettings = Collections.emptyMap(); + this.parameterMappings = Collections.emptyList(); + } + + public int getDashboardId() { + return dashboardId; + } + + public int getCardId() { + return cardId; + } + + public Integer getDashboardTabId() { + return dashboardTabId; + } + + public int getRow() { + return row; + } + + public int getCol() { + return col; + } + + public int getSizeX() { + return sizeX; + } + + public int getSizeY() { + return sizeY; + } + + @Override + public String toString() { + return "{" + + "dashboardId=" + dashboardId + + ", cardId=" + cardId + + ", dashboardTabId=" + dashboardTabId + + ", row=" + row + + ", col=" + col + + ", sizeX=" + sizeX + + ", sizeY=" + sizeY + + ", visualizationSettings=" + visualizationSettings + + ", parameterMappings=" + parameterMappings + + '}'; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/QuestionName.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/QuestionName.java new file mode 100644 index 000000000..bc658609d --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/QuestionName.java @@ -0,0 +1,16 @@ +package org.avni.server.domain.metabase; + +public enum QuestionName { + QUESTION_1("Pie Chart : Count of Non Voided Individuals - Non Voided Subject Type"), + QUESTION_2("Pie Chart : Number of Non exited and Non voided Enrollments for Non Voided Program"); + + private final String questionName; + + QuestionName(String questionName) { + this.questionName = questionName; + } + + public String getQuestionName() { + return questionName; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java b/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java index 9a9418fc4..364dbd424 100644 --- a/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java +++ b/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java @@ -1,5 +1,6 @@ package org.avni.server.service.metabase; +import org.avni.server.dao.metabase.MetabaseDashboardRepository; import org.avni.server.dao.metabase.DatabaseRepository; import org.avni.server.domain.metabase.*; import org.springframework.beans.factory.annotation.Autowired; @@ -16,13 +17,15 @@ public class DatabaseService implements QuestionCreationService{ private final DatabaseRepository databaseRepository; private final MetabaseService metabaseService; + private final MetabaseDashboardRepository metabaseDashboardRepository; private static final String ADDRESS_TABLE = "Address"; @Autowired - public DatabaseService(DatabaseRepository databaseRepository, MetabaseService metabaseService) { + public DatabaseService(DatabaseRepository databaseRepository, MetabaseService metabaseService, MetabaseDashboardRepository metabaseDashboardRepository) { this.databaseRepository = databaseRepository; this.metabaseService = metabaseService; + this.metabaseDashboardRepository = metabaseDashboardRepository; } public Database getGlobalDatabase() { @@ -33,6 +36,10 @@ public CollectionInfoResponse getGlobalCollection() { return metabaseService.getGlobalCollection(); } + public CollectionItem getGlobalDashboard(){ + return metabaseService.getGlobalDashboard(); + } + public SyncStatus getInitialSyncStatus() { DatabaseSyncStatus databaseSyncStatus = databaseRepository.getInitialSyncStatus(getGlobalDatabase()); String status = databaseSyncStatus.getInitialSyncStatus(); @@ -56,6 +63,25 @@ private List filterOutExistingQuestions(List entityNames) { .collect(Collectors.toList()); } + private boolean isQuestionMissing(String questionName) { + Set existingItemNames = databaseRepository + .getExistingCollectionItems(getGlobalCollection().getIdAsInt()) + .stream() + .map(item -> item.getName().trim().toLowerCase().replace(" ", "_")) + .collect(Collectors.toSet()); + + return !existingItemNames.contains(questionName.trim().toLowerCase().replace(" ", "_")); + } + + private int getCardIdByQuestionName(String questionName) { + return databaseRepository.getExistingCollectionItems(getGlobalCollection().getIdAsInt()).stream() + .filter(item -> item.getName().trim().equalsIgnoreCase(questionName.trim())) + .map(CollectionItem::getId) + .findFirst() + .orElseThrow(() -> new RuntimeException("Question not found: " + questionName)); + } + + @Override public void createQuestionForTable(TableDetails tableDetails, TableDetails addressTableDetails, FieldDetails addressFieldDetails, FieldDetails tableFieldDetails) { Database database = getGlobalDatabase(); @@ -158,8 +184,27 @@ public void createQuestionsForIndividualTables() { public void createAdvancedQuestions() { ensureSyncComplete(); Database database = getGlobalDatabase(); - databaseRepository.createAdvancedQuestion(database); - databaseRepository.createAdvancedQuestion2(database); + if (isQuestionMissing(QuestionName.QUESTION_1.getQuestionName())) { + databaseRepository.createAdvancedQuestion(database); + } + + if (isQuestionMissing(QuestionName.QUESTION_2.getQuestionName())) { + databaseRepository.createAdvancedQuestion2(database); + } + updateGlobalDashboardWithAdvancedQuestions(); + + } + + public void updateGlobalDashboardWithAdvancedQuestions() { + List dashcards = new ArrayList<>(); + dashcards.add(new Dashcard(-1, getCardIdByQuestionName(QuestionName.QUESTION_1.getQuestionName()), null, 0, 0, 12, 8)); + dashcards.add(new Dashcard(-2, getCardIdByQuestionName(QuestionName.QUESTION_2.getQuestionName()), null, 0, 12, 12, 8)); + + DashboardUpdateRequest dashboardUpdateRequest = new DashboardUpdateRequest( + dashcards + ); + + metabaseDashboardRepository.updateDashboard(getGlobalDashboard().getId(), dashboardUpdateRequest); } public void createQuestions() { diff --git a/avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java b/avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java index 1a0306f2c..f23705144 100644 --- a/avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java +++ b/avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java @@ -1,9 +1,6 @@ package org.avni.server.service.metabase; -import org.avni.server.dao.metabase.CollectionPermissionsRepository; -import org.avni.server.dao.metabase.CollectionRepository; -import org.avni.server.dao.metabase.DatabaseRepository; -import org.avni.server.dao.metabase.GroupPermissionsRepository; +import org.avni.server.dao.metabase.*; import org.avni.server.domain.Organisation; import org.avni.server.domain.metabase.*; import org.avni.server.service.OrganisationService; @@ -23,8 +20,10 @@ public class MetabaseService { private final GroupPermissionsRepository groupPermissionsRepository; private final CollectionPermissionsRepository collectionPermissionsRepository; private final CollectionRepository collectionRepository; + private final MetabaseDashboardRepository metabaseDashboardRepository; private Database globalDatabase; private CollectionInfoResponse globalCollection; + private CollectionItem globalDashboard; @Autowired public MetabaseService(OrganisationService organisationService, @@ -32,13 +31,15 @@ public MetabaseService(OrganisationService organisationService, DatabaseRepository databaseRepository, GroupPermissionsRepository groupPermissionsRepository, CollectionPermissionsRepository collectionPermissionsRepository, - CollectionRepository collectionRepository) { + CollectionRepository collectionRepository, + MetabaseDashboardRepository metabaseDashboardRepository) { this.organisationService = organisationService; this.avniDatabase = avniDatabase; this.databaseRepository = databaseRepository; this.groupPermissionsRepository = groupPermissionsRepository; this.collectionPermissionsRepository = collectionPermissionsRepository; this.collectionRepository = collectionRepository; + this.metabaseDashboardRepository = metabaseDashboardRepository; } public void setupMetabase() { @@ -64,6 +65,12 @@ public void setupMetabase() { collectionPermissionsRepository.getCollectionPermissionsGraph() ); collectionPermissions.updateAndSavePermissions(collectionPermissionsRepository, metabaseGroup.getId(), globalCollection.getIdAsInt()); + + globalDashboard = metabaseDashboardRepository.getDashboardByName(globalCollection); + if(globalDashboard == null){ + Dashboard metabaseDashboard = metabaseDashboardRepository.save(new CreateDashboardRequest(globalCollection.getName(),null,getGlobalCollection().getIdAsInt())); + globalDashboard = new CollectionItem(metabaseDashboard.getName(),metabaseDashboard.getId()); + } } public Database getGlobalDatabase() { @@ -89,4 +96,14 @@ public CollectionInfoResponse getGlobalCollection() { return globalCollection; } + public CollectionItem getGlobalDashboard() { + if (globalDashboard == null) { + globalDashboard = metabaseDashboardRepository.getDashboardByName(globalCollection); + if (globalDashboard == null) { + throw new RuntimeException("Global dashboard not found."); + } + } + return globalDashboard; + } + } \ No newline at end of file diff --git a/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java b/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java index d827b78d4..3cec6b473 100644 --- a/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java +++ b/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java @@ -70,6 +70,7 @@ public SetupStatusResponse getSetupStatus() { public CreateQuestionsResponse createQuestions() { try { databaseService.createQuestions(); + databaseService.updateGlobalDashboardWithAdvancedQuestions(); return new CreateQuestionsResponse(true, "Questions created successfully."); } catch (RuntimeException e) { return new CreateQuestionsResponse(false, "Database sync is not complete. Cannot create questions.");