UNPKG

virool-pivot

Version:

A web-based exploratory visualization UI for Druid.io

373 lines (308 loc) 12.6 kB
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); }); }