QlikElixir

Hex.pmDocsLicense

A comprehensive Elixir client for Qlik Cloud REST APIs and QIX Engine.

Features

REST APIs - Full coverage of Qlik Cloud management APIs:

QIX Engine - Real-time data extraction via WebSocket:

Installation

Add qlik_elixir to your dependencies in mix.exs:

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

Quick Start

Configuration

# Option 1: Environment variables (recommended for production)
# QLIK_API_KEY=your-api-key
# QLIK_TENANT_URL=https://your-tenant.region.qlikcloud.com

# Option 2: Application config
config :qlik_elixir,
  api_key: "your-api-key",
  tenant_url: "https://your-tenant.region.qlikcloud.com"

# Option 3: Runtime config (for multiple tenants)
config = QlikElixir.new_config(
  api_key: "your-api-key",
  tenant_url: "https://your-tenant.region.qlikcloud.com"
)

REST API Examples

# List apps
{:ok, %{"data" => apps}} = QlikElixir.REST.Apps.list()

# Get app details
{:ok, app} = QlikElixir.REST.Apps.get("app-id")

# Trigger a reload
{:ok, reload} = QlikElixir.REST.Reloads.create("app-id")

# Upload a CSV file
{:ok, file} = QlikElixir.REST.DataFiles.upload_file("sales_data.csv")

# List spaces
{:ok, %{"data" => spaces}} = QlikElixir.REST.Spaces.list()

QIX Engine - Data Extraction

alias QlikElixir.QIX.{Session, App}

# Connect to an app
{:ok, session} = Session.connect("app-id", config: config)

# List sheets
{:ok, sheets} = App.list_sheets(session)

# Get visualization data (the main event!)
{:ok, data} = App.get_hypercube_data(session, "object-id")
# Returns:
# %{
#   headers: ["Country", "Sales", "Margin"],
#   rows: [
#     %{text: ["USA", "$1.2M", "23%"], values: ["USA", 1200000, 0.23]},
#     %{text: ["Germany", "$900K", "19%"], values: ["Germany", 900000, 0.19]}
#   ],
#   total_rows: 50,
#   truncated: false
# }

# Make selections
:ok = App.select_values(session, "Country", ["USA", "Germany"])

# Evaluate expressions
{:ok, total} = App.evaluate(session, "Sum(Sales)")

# Disconnect
:ok = Session.disconnect(session)

API Reference

REST APIs

Module Description Qlik API Reference
QlikElixir.REST.Apps App management, publishing, export/import Apps API
QlikElixir.REST.Spaces Spaces and role assignments Spaces API
QlikElixir.REST.DataFiles File upload and management Data Files API
QlikElixir.REST.Reloads Trigger and monitor reloads Reloads API
QlikElixir.REST.Users User management Users API
QlikElixir.REST.Groups Group management Groups API
QlikElixir.REST.APIKeys API key management API Keys API
QlikElixir.REST.Automations Automation workflows Automations API
QlikElixir.REST.Webhooks Event notifications Webhooks API
QlikElixir.REST.DataConnections External data sources Data Connections API
QlikElixir.REST.Items Unified resource listing Items API
QlikElixir.REST.Collections Content organization Collections API
QlikElixir.REST.Reports Report generation Reports API
QlikElixir.REST.Tenants Tenant configuration Tenants API
QlikElixir.REST.Roles Role definitions Roles API
QlikElixir.REST.Audits Audit logging Audits API
QlikElixir.REST.NaturalLanguage Conversational analytics Insight Advisor API

QIX Engine (WebSocket)

Module Description Qlik API Reference
QlikElixir.QIX.Session WebSocket connection management QIX Overview
QlikElixir.QIX.App High-level data extraction API Doc API
QlikElixir.QIX.Protocol JSON-RPC protocol handling GenericObject API

Core Modules

Module Description
QlikElixir.Config Configuration management
QlikElixir.Error Error types and handling
QlikElixir.Pagination Cursor-based pagination helpers

API Testing Status

All 362 tests pass (100% coverage with Bypass HTTP mocking).

The following table shows integration testing status against real Qlik Cloud APIs:

Module Read Write Notes
Apps create, get, update, copy, delete, get_metadata, get_lineage, get_script, validate_script, list_media, get_thumbnail, export
Spaces create, get, update, delete, list_types, list_assignments
DataFiles list, get, upload, delete, find_by_name
Reloads list, get, create, cancel
Collections create, get, update, delete, list_items, add_item, remove_item, get_favorites
Items - list, get, find_by_resource, get_published_items, get_collections
Users - me, list, count
Groups - list, list_settings
Roles - list, get
APIKeys - get_config requires tenant_id
Automations - list, list_runs
Webhooks - list, list_event_types
DataConnections - list, get
NaturalLanguage - get_model, list_analysis_types, ask, recommend
Audits - list, get, list_sources, list_types
Tenants - me
Reports ⚠️ - API returns 404 (may require entitlement)

Untested Write Operations

The following write operations have unit tests but have not been integration tested:

Module Untested Operations Reason
Apps publish, import_app Requires published app setup
Spaces create_assignment, delete_assignment Requires user IDs
DataFiles update, changeowner, change_space, batch* Requires specific setup
Items update, delete Affects catalog items
Users create, update, delete, invite Tenant admin operations
Groups create, update, delete, update_settings Group management
APIKeys create, update, delete, update_config Security sensitive
Automations create, update, delete, run, enable, disable, etc. Complex setup
Webhooks create, update, delete, resend_delivery Requires callback URL
DataConnections create, update, delete Requires datasourceID
Tenants get, create, update, deactivate, reactivate Tenant admin only

QIX Engine (WebSocket): ✅ Fully integration tested - Session, App, data extraction

Common Patterns

Pagination

All list endpoints support cursor-based pagination:

# First page
{:ok, %{"data" => apps, "links" => %{"next" => %{"href" => next_url}}}} =
  QlikElixir.REST.Apps.list(limit: 20)

# Get cursor from next URL and fetch next page
{:ok, page2} = QlikElixir.REST.Apps.list(limit: 20, next: cursor)

# Or use the Pagination helper
QlikElixir.Pagination.stream(fn cursor ->
  QlikElixir.REST.Apps.list(limit: 100, next: cursor)
end)
|> Enum.take(500)  # Get up to 500 apps

Error Handling

case QlikElixir.REST.Apps.get("app-id") do
  {:ok, app} ->
    IO.puts("Found app: #{app["name"]}")

  {:error, %QlikElixir.Error{type: :not_found}} ->
    IO.puts("App not found")

  {:error, %QlikElixir.Error{type: :authentication_error}} ->
    IO.puts("Invalid API key")

  {:error, %QlikElixir.Error{} = error} ->
    IO.puts("Error: #{error.message}")
end

Multiple Tenants

# Create configs for different tenants
us_config = QlikElixir.new_config(
  api_key: System.fetch_env!("US_QLIK_API_KEY"),
  tenant_url: "https://us-tenant.us.qlikcloud.com"
)

eu_config = QlikElixir.new_config(
  api_key: System.fetch_env!("EU_QLIK_API_KEY"),
  tenant_url: "https://eu-tenant.eu.qlikcloud.com"
)

# Use specific config per request
{:ok, us_apps} = QlikElixir.REST.Apps.list(config: us_config)
{:ok, eu_apps} = QlikElixir.REST.Apps.list(config: eu_config)

Streaming Large Datasets

alias QlikElixir.QIX.{Session, App}

{:ok, session} = Session.connect("app-id", config: config)

# Stream hypercube data in pages
App.stream_hypercube_data(session, "object-id", page_size: 1000)
|> Stream.flat_map(& &1)
|> Stream.each(fn row ->
  # Process each row
  IO.inspect(row)
end)
|> Stream.run()

Configuration Options

config = QlikElixir.new_config(
  # Required
  api_key: "your-api-key",
  tenant_url: "https://your-tenant.region.qlikcloud.com",

  # Optional
  connection_id: "default-connection-id",  # For data files
  http_options: [
    timeout: :timer.minutes(5),    # Request timeout
    retry: :transient,              # Retry strategy
    max_retries: 3,                 # Max retry attempts
    retry_delay: fn n -> n * 1000 end  # Backoff function
  ]
)

Development

# Install dependencies
mix deps.get

# Run tests
mix test

# Run with coverage
mix test --cover

# Check code quality
mix format --check-formatted
mix credo --strict
mix dialyzer

# Generate docs
mix docs

Roadmap / TODO

PRs welcome! Here are some areas that could use contribution:

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests first (TDD encouraged)
  4. Ensure all checks pass (mix format && mix credo --strict && mix test)
  5. Commit your changes
  6. Push to the branch
  7. Open a Pull Request

License

MIT License - see LICENSE for details.

Links


Sponsored by

This project is proudly sponsored by Balneario - Clínica de Longevidad de Cofrentes, a world-class longevity clinic and thermal spa in Valencia, Spain. Their support makes open source development like this possible.

Thank you for investing in the developer community!