Integrating Tableau to the Data Service

As Tableau needs to retrieve the data from the Platform application, some dedicated HTTP endpoints need to be enabled in the Platform.

To add Tableau endpoints in the Data Service, you need to add the dependency to a built-in library. To do so, edit the extensions/data-service-extension/build.gradle Gradle configuration file and enrich the dependency block as in the following example:

dependencies {
    ...
    // Tableau
    implementation "com.decisionbrain.gene:data-tableau-base:${versions.decisionbrain.dbgene}"
}

You also have to create a Java configuration that adds the Tableau HTTP controller to the Data Service. To do so, add a TableauConfiguration class with the @EnableTableau in the data-service-extension as in the following code:

package com.example.caplan.data.configuration;

import com.decisionbrain.gene.tableau.dataservice.EnableTableau;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableTableau
public class TableauConfiguration { }

Some configurations need to be added to help the Data Service to know how to reach Tableau Server

tableau-client:
  enabled: true
  host: <the-url-of-your-Tableau-server>
  credentials:
    username: tableauUser
    password: tableauPassword

When deploying using a Helm chart, add the following values to your environment values YAML file:

  <your-application-chart-name>:
    dataService:
      env:
        GATEWAY_SERVER_URL:
          value: "https://www.my-platform-application.my-company.com"
        API_KEY_CLIENT_BASE_URL:
          value: "https://www.my-platform-application.my-company.com/api/scenario"

When Tableau retrieves the data from the Platform application, it asks for the data associated with the web data connector name. This data is retrieved from scenarios. The scenarios to be associated with a given web data connector are resolved by a component called TableauRefreshLifecycleHandler in the Platform application. (As the name suggests, this component also provides hooks that are called and the start and end of the data refresh operations.)

The default implementation of this component associates scenarios with a web data connector, based on the users choice. If you need a different resolution algorithm, you will have to code your own logic as a Spring component in the data-service-extension that implements the TableauRefreshLifecycleHandler interface, and annotate it as @Primary.

Here an example that retrieves all the available scenarios for the user that has created the Web Data Connector.

@Primary
@Component
public class CustomTableauRefreshLifecycleHandler implements TableauRefreshLifecycleHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomTableauRefreshLifecycleHandler.class);

    private final ScenarioServiceClient scenarioServiceClient;

    @Autowired
    public CustomizedResolveScenarioHandler(ScenarioServiceClient scenarioServiceClient) {
        this.scenarioServiceClient = scenarioServiceClient;
    }

    @Override
    public void refreshStarted(@NonNull String connectorId) throws MultipleWorkbooksException {
        LOGGER.info("Start refresh for WDC {}", connectorId);
    }
        
    @Override
    public void refreshCompleted(@NonNull String connectorId) throws MultipleWorkbooksException {
        LOGGER.info("Completed refresh for WDC {}", connectorId);
    }
        
    @Override
    public void refreshFailed(@NonNull String connectorId, String errorMessage) throws MultipleWorkbooksException {
        LOGGER.error("Failed refresh for WDC {}: {}", connectorId, errorMessage);
    }
        
    
    @Override
    public List<String> resolveScenarioIds(String connectorId) {
        LOGGER.info("Resolving Scenarios for WDC: {}", connectorId);

        var allScenarios = Optional.ofNullable(scenarioServiceClient.getScenarios(null, null).getBody())
                .map(scenarios -> scenarios.stream()
                    .map(PathDTO::getUuid)
                    .filter(Objects::nonNull)
                    .toList()
                ).orElse(List.of());
        // Add your custom logic here
        return allScenarios;
    }
}