virool-pivot
Version:
A web-based exploratory visualization UI for Druid.io
373 lines (308 loc) • 12.6 kB
text/typescript
import * as path from 'path';
import * as Q from 'q';
import * as nopt from 'nopt';
import { DruidRequestDecorator } from 'plywood-druid-requester';
import { DataSource, DataSourceJS, Dimension, Measure, LinkViewConfig, LinkViewConfigJS, Customization } from '../common/models/index';
import { dataSourceToYAML } from '../common/utils/yaml-helper/yaml-helper';
import { DataSourceManager, dataSourceManagerFactory, loadFileSync, properDruidRequesterFactory, dataSourceLoaderFactory, SourceListScan } from './utils/index';
export interface ServerConfig {
iframe?: "allow" | "deny";
}
export interface PivotConfig {
port?: number;
verbose?: boolean;
brokerHost?: string;
druidHost?: string;
timeout?: number;
introspectionStrategy?: string;
pageMustLoadTimeout?: number;
sourceListScan?: SourceListScan;
sourceListRefreshOnLoad?: boolean;
sourceListRefreshInterval?: number;
sourceReintrospectOnLoad?: boolean;
sourceReintrospectInterval?: number;
auth?: string;
druidRequestDecorator?: string;
dataSources?: DataSourceJS[];
linkViewConfig?: LinkViewConfigJS;
serverConfig?: ServerConfig;
customization?: Customization;
}
export interface RequestDecoratorFactoryOptions {
config: any;
}
export interface DruidRequestDecoratorModule {
druidRequestDecorator: (log: (line: string) => void, options: RequestDecoratorFactoryOptions) => DruidRequestDecorator;
}
function errorExit(message: string): void {
console.error(message);
process.exit(1);
}
var packageObj: any = null;
try {
packageObj = loadFileSync(path.join(__dirname, '../../package.json'), 'json');
} catch (e) {
errorExit(`Could not read package.json: ${e.message}`);
}
export const VERSION = packageObj.version;
function printUsage() {
console.log(`
Usage: pivot [options]
Possible usage:
pivot --example wiki
pivot --druid your.broker.host:8082
--help Print this help message
--version Display the version number
-v, --verbose Display the DB queries that are being made
-p, --port The port pivot will run on
--example Start pivot with some example data (overrides all other options)
-c, --config The configuration YAML files to use
--print-config Prints out the auto generated config
--with-comments Adds comments when printing the auto generated config
--data-sources-only Only print the data sources in the auto generated config
-f, --file Start pivot on top of this file based data source (must be JSON, CSV, or TSV)
-d, --druid The Druid broker node to connect to
--introspection-strategy Druid introspection strategy
Possible values:
* segment-metadata-fallback - (default) use the segmentMetadata and fallback to GET route
* segment-metadata-only - only use the segmentMetadata query
* datasource-get - only use GET /druid/v2/datasources/DATASOURCE route
`
);
}
function parseArgs() {
return nopt(
{
"help": Boolean,
"version": Boolean,
"verbose": Boolean,
"port": Number,
"example": String,
"config": String,
"print-config": Boolean,
"with-comments": Boolean,
"data-sources-only": Boolean,
"file": String,
"druid": String,
"introspection-strategy": String
},
{
"v": ["--verbose"],
"p": ["--port"],
"c": ["--config"],
"f": ["--file"],
"d": ["--druid"]
},
process.argv
);
}
var parsedArgs = parseArgs();
//console.log(parsedArgs);
if (parsedArgs['help']) {
printUsage();
process.exit();
}
if (parsedArgs['version']) {
console.log(VERSION);
process.exit();
}
const DEFAULT_CONFIG: PivotConfig = {
port: 9090,
sourceListScan: 'auto',
sourceListRefreshInterval: 10000,
dataSources: []
};
if (!parsedArgs['example'] && !parsedArgs['config'] && !parsedArgs['druid'] && !parsedArgs['file']) {
printUsage();
process.exit();
}
var exampleConfig: PivotConfig = null;
if (parsedArgs['example']) {
delete parsedArgs['druid'];
var example = parsedArgs['example'];
if (example === 'wiki') {
try {
exampleConfig = loadFileSync(path.join(__dirname, `../../config-example-${example}.yaml`), 'yaml');
} catch (e) {
errorExit(`Could not load example config for '${example}': ${e.message}`);
}
} else {
console.log(`Unknown example '${example}'. Possible examples are: wiki`);
process.exit();
}
}
var configFilePath = parsedArgs['config'];
var configFileDir: string = null;
var config: PivotConfig;
if (configFilePath) {
configFileDir = path.dirname(configFilePath);
try {
config = loadFileSync(configFilePath, 'yaml');
console.log(`Using config ${configFilePath}`);
} catch (e) {
errorExit(`Could not load config from '${configFilePath}': ${e.message}`);
}
} else {
config = DEFAULT_CONFIG;
}
// If there is an example config take its dataSources
if (exampleConfig && Array.isArray(exampleConfig.dataSources)) {
config.dataSources = exampleConfig.dataSources;
}
// If a file is specified add it as a dataSource
var file = parsedArgs['file'];
if (file) {
config.dataSources.push({
name: path.basename(file, path.extname(file)),
engine: 'native',
source: file
});
}
export const PRINT_CONFIG = Boolean(parsedArgs['print-config']);
export const START_SERVER = !PRINT_CONFIG;
export const VERBOSE = Boolean(parsedArgs['verbose'] || config.verbose);
export const PORT = parseInt(parsedArgs['port'] || config.port, 10) || 9090;
export const SERVER_ROOT: string = (config as any).serverRoot;
export const DRUID_HOST = parsedArgs['druid'] || config.brokerHost || config.druidHost;
export const TIMEOUT = parseInt(<any>config.timeout, 10) || 30000;
export const INTROSPECTION_STRATEGY = String(parsedArgs["introspection-strategy"] || config.introspectionStrategy || 'segment-metadata-fallback');
export const PAGE_MUST_LOAD_TIMEOUT = START_SERVER ? (parseInt(<any>config.pageMustLoadTimeout, 10) || 800) : 0;
export const SOURCE_LIST_SCAN: SourceListScan = config.sourceListScan;
export const SOURCE_LIST_REFRESH_ON_LOAD = START_SERVER ? Boolean(<any>config.sourceListRefreshOnLoad) : false;
export const SOURCE_LIST_REFRESH_INTERVAL = START_SERVER ? (parseInt(<any>config.sourceListRefreshInterval, 10) || 15000) : 0;
if (SOURCE_LIST_REFRESH_INTERVAL && SOURCE_LIST_REFRESH_INTERVAL < 1000) {
errorExit(`can not set sourceListRefreshInterval to < 1000 (is ${SOURCE_LIST_REFRESH_INTERVAL})`);
}
export const SOURCE_REINTROSPECT_ON_LOAD = START_SERVER ? Boolean(<any>config.sourceReintrospectOnLoad) : false;
export const SOURCE_REINTROSPECT_INTERVAL = START_SERVER ? (parseInt(<any>config.sourceReintrospectInterval, 10) || 0) : 0;
if (SOURCE_REINTROSPECT_INTERVAL && SOURCE_REINTROSPECT_INTERVAL < 1000) {
errorExit(`can not set sourceReintrospectInterval to < 1000 (is ${SOURCE_REINTROSPECT_INTERVAL})`);
}
var auth = config.auth;
var authModule: any = null;
if (auth) {
auth = path.resolve(configFileDir, auth);
console.log(`Using auth ${auth}`);
try {
authModule = require(auth);
} catch (e) {
errorExit(`error loading auth module: ${e.message}`);
}
if (typeof authModule.auth !== 'function') errorExit('Invalid auth module');
}
export const AUTH = authModule;
var druidRequestDecorator = config.druidRequestDecorator;
var druidRequestDecoratorModule: DruidRequestDecoratorModule = null;
if (druidRequestDecorator) {
druidRequestDecorator = path.resolve(configFileDir, druidRequestDecorator);
console.log(`Using druidRequestDecorator ${druidRequestDecorator}`);
try {
druidRequestDecoratorModule = require(druidRequestDecorator);
} catch (e) {
errorExit(`error loading druidRequestDecorator module: ${e.message}`);
}
if (typeof druidRequestDecoratorModule.druidRequestDecorator !== 'function') errorExit('Invalid druidRequestDecorator module');
}
export const DRUID_REQUEST_DECORATOR = druidRequestDecoratorModule;
export const DATA_SOURCES: DataSource[] = (config.dataSources || []).map((dataSourceJS: DataSourceJS, i: number) => {
if (typeof dataSourceJS !== 'object') errorExit(`DataSource ${i} is not valid`);
var dataSourceName = dataSourceJS.name;
if (typeof dataSourceName !== 'string') errorExit(`DataSource ${i} must have a name`);
// Convert maxTime into refreshRule if a maxTime exists
if (dataSourceJS.maxTime && (typeof dataSourceJS.maxTime === 'string' || (<any>dataSourceJS.maxTime).toISOString)) {
dataSourceJS.refreshRule = { rule: 'fixed', time: <any>dataSourceJS.maxTime };
console.warn('maxTime found in config, this is deprecated please convert it to a refreshRule like so:', dataSourceJS.refreshRule);
delete dataSourceJS.maxTime;
}
try {
return DataSource.fromJS(dataSourceJS);
} catch (e) {
errorExit(`Could not parse data source '${dataSourceJS.name}': ${e.message}`);
return;
}
});
export const LINK_VIEW_CONFIG = config.linkViewConfig || null;
export const SERVER_CONFIG = config.serverConfig || {};
export const CUSTOMIZATION = config.customization ? Customization.fromJS(config.customization) : null;
var druidRequester: Requester.PlywoodRequester<any> = null;
if (DRUID_HOST) {
var requestDecorator: DruidRequestDecorator = null;
if (druidRequestDecoratorModule) {
var logger = (str: string) => console.log(str);
requestDecorator = druidRequestDecoratorModule.druidRequestDecorator(logger, {
config
});
}
druidRequester = properDruidRequesterFactory({
druidHost: DRUID_HOST,
timeout: TIMEOUT,
verbose: VERBOSE,
concurrentLimit: 5,
requestDecorator
});
}
var configDirectory = configFileDir || path.join(__dirname, '../..');
if (!PRINT_CONFIG) {
console.log(`Starting Pivot v${VERSION}`);
}
export const DATA_SOURCE_MANAGER: DataSourceManager = dataSourceManagerFactory({
dataSources: DATA_SOURCES,
druidRequester,
dataSourceLoader: dataSourceLoaderFactory(druidRequester, configDirectory, TIMEOUT, INTROSPECTION_STRATEGY),
pageMustLoadTimeout: PAGE_MUST_LOAD_TIMEOUT,
sourceListScan: SOURCE_LIST_SCAN,
sourceListRefreshOnLoad: SOURCE_LIST_REFRESH_ON_LOAD,
sourceListRefreshInterval: SOURCE_LIST_REFRESH_INTERVAL,
sourceReintrospectOnLoad: SOURCE_REINTROSPECT_ON_LOAD,
sourceReintrospectInterval: SOURCE_REINTROSPECT_INTERVAL,
log: PRINT_CONFIG ? null : (line: string) => console.log(line)
});
if (PRINT_CONFIG) {
var withComments = Boolean(parsedArgs['with-comments']);
var dataSourcesOnly = Boolean(parsedArgs['data-sources-only']);
DATA_SOURCE_MANAGER.getQueryableDataSources().then((dataSources) => {
if (!dataSources.length) throw new Error('Could not find any data sources please verify network connectivity');
var lines = [
`# generated by Pivot version ${VERSION}`,
`# for a more detailed walk-through go to: https://github.com/implydata/pivot/blob/master/docs/configuration.md`,
''
];
if (!dataSourcesOnly) {
if (VERBOSE) {
if (withComments) {
lines.push("# Run Pivot in verbose mode so it prints out the queries that it issues");
}
lines.push(`verbose: true`, '');
}
if (withComments) {
lines.push("# The port on which the Pivot server will listen on");
}
lines.push(`port: ${PORT}`, '');
if (DRUID_HOST) {
if (withComments) {
lines.push("# A Druid broker node that can serve data (only used if you have Druid based data source)");
}
lines.push(`druidHost: ${DRUID_HOST}`, '');
if (withComments) {
lines.push("# A timeout for the Druid queries in ms (default: 30000 = 30 seconds)");
lines.push("#timeout: 30000", '');
}
}
if (INTROSPECTION_STRATEGY !== 'segment-metadata-fallback') {
if (withComments) {
lines.push("# The introspection strategy for the Druid external");
}
lines.push(`introspectionStrategy: ${INTROSPECTION_STRATEGY}`, '');
}
if (withComments) {
lines.push("# Should new datasources automatically be added?");
}
lines.push(`sourceListScan: disable`, '');
}
lines.push('dataSources:');
lines = lines.concat.apply(lines, dataSources.map(d => dataSourceToYAML(d, withComments)));
console.log(lines.join('\n'));
}).catch((e: Error) => {
console.error("There was an error generating a config: " + e.message);
});
}