qlik_elixir

An Elixir client library for uploading CSV files to Qlik Cloud with comprehensive API support


License
MIT

Documentation

QlikElixir

An Elixir client library for uploading CSV files to Qlik Cloud with comprehensive API support.

Features

  • Upload CSV files to Qlik Cloud using multipart form data
  • Support for both file path and binary content uploads
  • Automatic overwrite handling with delete-and-retry logic
  • File size validation (500MB limit)
  • Comprehensive error handling with custom error types
  • Support for multiple tenant configurations
  • Built-in retry logic and configurable timeouts
  • Full test coverage with mocked HTTP interactions

Installation

Add qlik_elixir to your list of dependencies in mix.exs:

def deps do
  [
    {:qlik_elixir, "~> 0.2.0"}
  ]
end

Then run:

mix deps.get

Configuration

Environment Variables

The simplest way to configure QlikElixir is through environment variables:

export QLIK_API_KEY="your-api-key"
export QLIK_TENANT_URL="https://your-tenant.qlikcloud.com"
export QLIK_CONNECTION_ID="your-connection-id"  # Optional

Application Configuration

You can also configure it in your config/config.exs:

config :qlik_elixir,
  api_key: "your-api-key",
  tenant_url: "https://your-tenant.qlikcloud.com",
  connection_id: "your-connection-id"  # Optional

Runtime Configuration

For runtime configuration or multiple tenants:

config = QlikElixir.new_config(
  api_key: "different-key",
  tenant_url: "https://other-tenant.qlikcloud.com",
  connection_id: "space-123",
  http_options: [timeout: 60_000]  # 1 minute timeout
)

QlikElixir.upload_csv("data.csv", config: config)

Usage

Basic File Upload

# Upload a CSV file
{:ok, %{"id" => file_id}} = QlikElixir.upload_csv("path/to/data.csv")

# Upload with custom name
{:ok, file} = QlikElixir.upload_csv("data.csv", name: "renamed_file.csv")

# Upload with overwrite
{:ok, file} = QlikElixir.upload_csv("data.csv", overwrite: true)

# Upload to specific connection
{:ok, file} = QlikElixir.upload_csv("data.csv", connection_id: "space-123")

Upload Content Directly

# Create CSV content dynamically
csv_content = "id,name,email\n1,John Doe,john@example.com\n2,Jane Smith,jane@example.com"

# Upload the content
{:ok, file} = QlikElixir.upload_csv_content(csv_content, "users.csv")

# With options
{:ok, file} = QlikElixir.upload_csv_content(
  csv_content, 
  "users.csv",
  connection_id: "space-456",
  overwrite: true
)

List Files

# List all files (up to 100)
{:ok, %{"data" => files, "total" => total}} = QlikElixir.list_files()

# With pagination
{:ok, result} = QlikElixir.list_files(limit: 20, offset: 40)

Check File Existence

# Check if a file exists by name
if QlikElixir.file_exists?("important_data.csv") do
  IO.puts("File already exists!")
end

Find File by Name

case QlikElixir.find_file_by_name("sales_data.csv") do
  {:ok, file} ->
    IO.puts("Found file with ID: #{file["id"]}")
  
  {:error, _} ->
    IO.puts("File not found")
end

Delete Files

# Delete by file ID
case QlikElixir.delete_file("file-id-123") do
  :ok ->
    IO.puts("File deleted successfully")
  
  {:error, error} ->
    IO.puts("Failed to delete: #{error.message}")
end

Advanced Usage

Custom Configuration per Request

# Create a custom config for a specific tenant
eu_config = QlikElixir.new_config(
  api_key: System.get_env("EU_QLIK_API_KEY"),
  tenant_url: "https://eu-tenant.qlikcloud.com"
)

# Use it for specific operations
{:ok, file} = QlikElixir.upload_csv("eu_data.csv", config: eu_config)
{:ok, files} = QlikElixir.list_files(config: eu_config)

Error Handling

QlikElixir provides detailed error information:

case QlikElixir.upload_csv("data.csv") do
  {:ok, file} ->
    IO.puts("Uploaded successfully: #{file["id"]}")
  
  {:error, %QlikElixir.Error{} = error} ->
    case error.type do
      :file_exists_error ->
        IO.puts("File already exists. Use overwrite: true to replace it.")
      
      :file_too_large ->
        IO.puts("File is too large. Maximum size is 500MB.")
      
      :authentication_error ->
        IO.puts("Invalid API key. Please check your configuration.")
      
      :validation_error ->
        IO.puts("Validation failed: #{error.message}")
      
      _ ->
        IO.puts("Upload failed: #{error.message}")
    end
end

Batch Operations

# Upload multiple files
files = ["data1.csv", "data2.csv", "data3.csv"]

results = Enum.map(files, fn file ->
  case QlikElixir.upload_csv(file) do
    {:ok, result} -> {:ok, file, result["id"]}
    {:error, error} -> {:error, file, error}
  end
end)

# Process results
Enum.each(results, fn
  {:ok, file, id} -> IO.puts("✓ #{file} uploaded as #{id}")
  {:error, file, error} -> IO.puts("✗ #{file} failed: #{error.message}")
end)

Progress Monitoring

For large file uploads, you might want to show progress:

defmodule UploadProgress do
  def upload_with_progress(file_path) do
    %{size: file_size} = File.stat!(file_path)
    
    IO.puts("Uploading #{Path.basename(file_path)} (#{format_bytes(file_size)})...")
    
    case QlikElixir.upload_csv(file_path) do
      {:ok, result} ->
        IO.puts("✓ Upload complete! File ID: #{result["id"]}")
        {:ok, result}
      
      {:error, error} ->
        IO.puts("✗ Upload failed: #{error.message}")
        {:error, error}
    end
  end
  
  defp format_bytes(bytes) when bytes < 1024, do: "#{bytes} B"
  defp format_bytes(bytes) when bytes < 1024 * 1024, do: "#{Float.round(bytes / 1024, 1)} KB"
  defp format_bytes(bytes), do: "#{Float.round(bytes / (1024 * 1024), 1)} MB"
end

HTTP Options

You can customize HTTP behavior through configuration:

config = QlikElixir.new_config(
  api_key: "your-key",
  tenant_url: "https://your-tenant.qlikcloud.com",
  http_options: [
    timeout: :timer.minutes(10),     # 10 minute timeout for large files
    retry: :transient,               # Retry on transient errors
    max_retries: 5,                  # Maximum number of retries
    retry_delay: fn n -> n * 2000 end # Exponential backoff
  ]
)

Error Types

QlikElixir defines the following error types:

  • :validation_error - Invalid input parameters
  • :upload_error - General upload failure
  • :authentication_error - Invalid or missing API key
  • :configuration_error - Invalid configuration
  • :file_exists_error - File already exists (when overwrite is false)
  • :file_not_found - File or resource not found
  • :file_too_large - File exceeds 500MB limit
  • :network_error - Network connectivity issues
  • :unknown_error - Unexpected errors

Testing

Run the test suite:

mix test

Run with coverage:

mix coveralls.html

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built with Req for HTTP client functionality
  • Inspired by the Qlik Cloud API documentation
  • Thanks to the Elixir community for the amazing ecosystem