@datawheel/olap-client

A multiclient library to handle requests with mondrian-rest and tesseract servers

Usage no npm install needed!

<script type="module">
  import datawheelOlapClient from 'https://cdn.skypack.dev/@datawheel/olap-client';
</script>

README

@datawheel/olap-client

Javascript client to interact with mondrian-rest and tesseract-olap servers.
This package is the sucessor of the mondrian-rest-client and the @datawheel/tesseract-client packages.

NPM

Installation

npm install @datawheel/olap-client

Client initialization

import {Client, MondrianDataSource, TesseractDataSource} from "@datawheel/olap-client"

// Create an instance of the DataSource according to the type of server
const datasource = new MondrianDataSource("https://your.mondrian-rest.server/");

// ...and pass it as the parameter to initialize your client
const client = new Client(datasource);

The constructor parameter is optional. You can also set/reset the datasource afterwards:

const client = new Client();

// ...

const datasource = new TesseractDataSource("https://another.tesseract-olap.server/olap/");
client.setDataSource(datasource);

If you don't know beforehand, use the Client.fromURL(url) static method. It will do some requests to try and guess the type of datasource.

// Using Promise chaining
let unknownClient;
Client.fromURL("https://unknown.server/olap/").then(client => {
  unknownClient = client;
});

// Using async/await
const unknownClient = await Client.fromURL("https://unknown.server/olap/");

If you really need the client object directly, you can initialize the instance and add the DataSource later:

const client = new Client();

// ...

Client.dataSourceFromURL("https://unknown.server/olap/").then(datasource => {
  client.setDataSource(datasource);
});

Notice if you try to interact with a method of an IClient instance without a configured DataSource, it will throw an error.

MultiClient initialization

import {MultiClient, TesseractDataSource} from "@datawheel/olap-client"

// MultiClient instances can also be initialized with or without a datasource
const client = new MultiClient();

// To add a datasource, use the #addDataSource() method instead of #setDataSource()
const datasource = new TesseractDataSource("https://your.mondrian-rest.server/");
client.addDataSource(datasource);

You can pass multiple datasources to the MultiClient#addDataSource() method:

client.addDataSource(datasourceA, datasourceB, datasourceC);

And likewise Client, if you don't know beforehand, you can let the client guess the type of datasource:

const unknownClient = MultiClient.fromURL("https://unknown.server/olap/", "https://another.server/", ...);

Usage

Both Client and MultiClient instances follow the IClient interface. Client and MultiClient-specific methods are at the end.

interface IClient

IClient#getCubes

getCubes(): Promise<Cube[]>

Returns a promise that resolves to a Cube array.
In a MultiClient instance, the cubes from all the subscribed datasources are concatenated.

IClient#getCube

getCube(cubeName: string, selectorFn?: (cubes: Cube[]) => Cube): Promise<Cube>

Returns a promise that resolves to a single Cube instance, whose .name is equal to the cubeName parameter.
In a MultiClient instance, if there's more than one cube with the same cubeName, a selectorFn function can be used to pick the right cube.

IClient#getMembers

getMembers(levelRef: Level | LevelDescriptor, options?: any): Promise<Member[]>

Returns a promise that resolves to a list of the members available for the level referenced.
levelRef can be a Level instance, or an object describing how to find a Level in a DataSource. For more information on this object, see the specification of the LevelDescriptor interface.
the properties options

IClient#getMember

getMember(levelRef: Level | LevelDescriptor, key: string | number, options?: any): Promise<Member>

Returns a promise that resolves to a member for the level referenced, specified by its key.

IClient#execQuery

execQuery(query: Query, endpoint?: string): Promise<Aggregation>

Execute a query in all the available datasources. The returned object implements the Aggregation interface.
The query parameter must be a Query instance, usually obtained from a Cube instance.

class Client

Client#checkStatus

checkStatus(): Promise<ServerStatus>

Returns an object with information about the server. For now, only supports detailed info about Tesseract OLAP, but tries its best with Mondrian REST anyway.

Client#setDataSource

setDataSource(datasource: IDataSource): void

Sets the datasource the client instance will work with. The datasource parameter must be an object compatible with the IDataSource interface.

Client.dataSourceFromURL

static dataSourceFromURL(url: string): Promise<IDataSource>

Tries to guess the type of server from a request to the serverUrl. The parameter must be a string.
Since a request must be done beforehand, this static method returns a Promise that resolves to a object compatible with the IDataSource interface.

Client.fromURL

static fromURL(url: string): Promise<Client>

Using the result from Client.dataSourceFromURL(serverUrl), generates a new Client(datasource) instance.

class MultiClient

MultiClient#addDataSource

addDataSource(...datasources: IDataSource[]): void

Adds datasources to the client internal directory. The datasources must be objects compatible with the IDataSource interface.

MultiClient#checkStatus

checkStatus(): Promise<ServerStatus[]>

Returns an array of objects with information about each datasource server. These objects have the same structure of response as Client#checkStatus.

MultiClient.dataSourcesFromURL

static dataSourcesFromURL(...serverUrls: string[]): Promise<IDataSource[]>

Does a request to each url in serverUrls, tries to guess the type of server, and returns the respective datasource.
This method returns a Promise that resolves to an array of IDataSource.

MultiClient.fromURL

static fromURL(...serverUrls: string[]): Promise<MultiClient>

From the result from MultiClient.dataSourcesFromURL(...serverUrls), generates a single new MultiClient(...datasources) instance.
This method returns a Promise that resolves to a MultiClient instance.

Other interfaces

interface Aggregation

interface Aggregation<T = any> {
    data: T;
    query: Query;
    status?: number;
    url?: string;
}

The result of executing a Query is represented by an object implementing the Aggregation interface. The type of the data property depends on the format set on the Query:

  • Format.jsonrecords returns an array of tidy data objects
  • All other Formats return the raw data returned by the server

interface LevelDescriptor

interface LevelDescriptor {
    server?: string; // Server URL
    cube?: string; // Cube name
    dimension?: string; // Dimension name
    hierarchy?: string; // Hierarchy name
    level: string; // Level name, required
}

A LevelDescriptor is an ordinary object with enough info to differentiate a Level in a list of DataSources. Depending on the circumstances (e.g. some name is shared in more than one object) some Levels might need more information on a LevelDescriptor to be differentiated. All the properties are the names of the parents, except for server that maps to the URL of the DataSource. Level is the only required at all times. It is suggested to fill the properties with as much information as possible to prevent getting a different level.

interface ServerStatus

interface ServerStatus {
    software: string;
    online: boolean;
    url: string;
    version: string;
}

Contains information about the current state of the server.
Due to server implementation, version isn't available from MondrianDataSource.

interface IDataSource

This package integrates the TesseractDataSource and MondrianDataSource classes, but the clients can work with any object that implements the IDataSource interface correctly:

interface IDataSource {
  serverOnline: boolean;
  serverSoftware: string;
  serverVersion: string;
  serverUrl: string;
  checkStatus(): Promise<ServerStatus>;
  execQuery(query: Query, endpoint?: string): Promise<Aggregation>;
  fetchCube(cubeName: string): Promise<AdaptedCube>;
  fetchCubes(): Promise<AdaptedCube[]>;
  fetchMember(parent: Level, key: string | number, options?: any): Promise<AdaptedMember>;
  fetchMembers(parent: Level, options?: any): Promise<AdaptedMember[]>;
}

Check the source code to see the requirements of the AdaptedObjects interfaces and how the current data sources are implemented.

Example

Client.fromURL("https://chilecube.datachile.io")
  .then(client => {
    return client.getCube("tax_data").then(cube => {
      const query = cube.query;
      query
        .addMeasure("Labour")
        .addDrilldown("Year")
        .setOption("debug", false)
        .setOption("distinct", false)
        .setOption("nonempty", true);
      return client.execQuery(query);
    });
  })
  .then(aggregation => {
    console.log(aggregation);
  });

License

MIT © 2019 Datawheel